Files
LaodingBot/internal/knowledge/loader.go

214 lines
4.6 KiB
Go
Raw Permalink Normal View History

2026-02-21 23:01:39 +08:00
package knowledge
import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"
)
type Skill struct {
Name string
Content string
Source string
}
type SkillSummary struct {
DirName string
Name string
Description string
Source string
}
type scannedSkill struct {
dirName string
name string
description string
content string
source string
}
2026-02-21 23:01:39 +08:00
func LoadSoul(path string) (string, error) {
b, err := os.ReadFile(path)
if err != nil {
return "", fmt.Errorf("read soul file failed: %w", err)
}
content := strings.TrimSpace(string(b))
if content == "" {
return "", fmt.Errorf("soul file is empty")
}
return content, nil
}
func LoadSkillSet(dir string) ([]Skill, error) {
scanned, err := scanSkills(dir)
if err != nil {
return nil, err
}
out := make([]Skill, 0, len(scanned))
for _, s := range scanned {
out = append(out, Skill{
Name: s.name,
Content: s.content,
Source: s.source,
})
}
return out, nil
}
func LoadSkillSummaries(dir string) ([]SkillSummary, error) {
scanned, err := scanSkills(dir)
if err != nil {
return nil, err
}
out := make([]SkillSummary, 0, len(scanned))
for _, s := range scanned {
out = append(out, SkillSummary{
DirName: s.dirName,
Name: s.name,
Description: s.description,
Source: s.source,
})
}
return out, nil
}
func scanSkills(dir string) ([]scannedSkill, error) {
2026-02-21 23:01:39 +08:00
entries, err := os.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("read skills dir failed: %w", err)
2026-02-21 23:01:39 +08:00
}
skillDirs := make([]string, 0)
2026-02-21 23:01:39 +08:00
for _, entry := range entries {
if entry.IsDir() {
skillDirs = append(skillDirs, entry.Name())
2026-02-21 23:01:39 +08:00
}
}
sort.Strings(skillDirs)
2026-02-21 23:01:39 +08:00
out := make([]scannedSkill, 0, len(skillDirs))
for _, skillDir := range skillDirs {
file := filepath.Join(dir, skillDir, "skill.md")
2026-02-21 23:01:39 +08:00
b, err := os.ReadFile(file)
if err != nil {
if os.IsNotExist(err) {
continue
}
return nil, fmt.Errorf("read skill file failed: %w", err)
2026-02-21 23:01:39 +08:00
}
content := strings.TrimSpace(string(b))
if content == "" {
continue
}
name, description := parseSkillNameDescription(skillDir, content)
if strings.TrimSpace(name) == "" {
continue
}
out = append(out, scannedSkill{
dirName: skillDir,
name: name,
description: description,
content: content,
source: file,
})
2026-02-21 23:01:39 +08:00
}
if len(out) == 0 {
return nil, fmt.Errorf("no valid skills loaded from %s (expected: skills/<skill_name>/skill.md)", dir)
2026-02-21 23:01:39 +08:00
}
return out, nil
}
func extractSkillName(fileName, markdown string) string {
for _, line := range strings.Split(markdown, "\n") {
line = strings.TrimSpace(line)
if !strings.HasPrefix(line, "#") {
continue
}
title := strings.TrimSpace(strings.TrimLeft(line, "#"))
title = strings.TrimSpace(strings.TrimPrefix(title, "Skill:"))
title = strings.TrimSpace(strings.TrimPrefix(title, "skill:"))
if title != "" {
return title
}
}
base := strings.TrimSuffix(fileName, filepath.Ext(fileName))
if base == "" {
return fileName
}
return base
}
func parseSkillNameDescription(fileName, markdown string) (string, string) {
name := ""
description := ""
fm := parseFrontMatter(markdown)
if v, ok := fm["name"]; ok {
name = strings.TrimSpace(v)
}
if v, ok := fm["description"]; ok {
description = strings.TrimSpace(v)
}
if name == "" {
name = extractSkillName(fileName, markdown)
}
if description == "" {
description = extractSkillDescription(markdown)
}
return name, description
}
func parseFrontMatter(markdown string) map[string]string {
lines := strings.Split(markdown, "\n")
out := map[string]string{}
if len(lines) < 3 {
return out
}
if strings.TrimSpace(lines[0]) != "---" {
return out
}
for i := 1; i < len(lines); i++ {
line := strings.TrimSpace(lines[i])
if line == "---" {
break
}
idx := strings.Index(line, ":")
if idx <= 0 {
continue
}
k := strings.ToLower(strings.TrimSpace(line[:idx]))
v := strings.TrimSpace(line[idx+1:])
v = strings.Trim(v, "\"'")
if k != "" && v != "" {
out[k] = v
}
}
return out
}
func extractSkillDescription(markdown string) string {
lines := strings.Split(markdown, "\n")
for _, raw := range lines {
line := strings.TrimSpace(raw)
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "---") {
continue
}
if strings.Contains(line, ":") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
left := strings.ToLower(strings.TrimSpace(parts[0]))
if left == "name" || left == "description" || left == "source" || left == "generated_at" {
continue
}
}
}
if len(line) > 200 {
line = line[:200]
}
return line
}
return ""
}