92 lines
2.4 KiB
Go
92 lines
2.4 KiB
Go
package services
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"sproutclaw-web/internal/models"
|
|
)
|
|
|
|
// builtinCommands are the slash commands the WebUI handles natively.
|
|
var builtinCommands = []models.SlashCommand{
|
|
{Name: "new", Description: "开启新会话", Source: "builtin"},
|
|
{Name: "compact", Description: "压缩会话历史", Source: "builtin"},
|
|
{Name: "reload", Description: "重载配置", Source: "builtin"},
|
|
{Name: "clone", Description: "克隆当前会话", Source: "builtin"},
|
|
{Name: "name", Description: "重命名当前会话", Source: "builtin"},
|
|
{Name: "model", Description: "切换模型", Source: "builtin"},
|
|
{Name: "session", Description: "显示会话统计", Source: "builtin"},
|
|
{Name: "export", Description: "导出为 HTML", Source: "builtin"},
|
|
{Name: "copy", Description: "复制最后一条助手消息", Source: "builtin"},
|
|
{Name: "webui", Description: "WebUI 控制", Source: "builtin"},
|
|
}
|
|
|
|
// ListSlashCommands aggregates all slash commands visible in the WebUI.
|
|
func ListSlashCommands(agentDir string) []models.SlashCommand {
|
|
cmds := make([]models.SlashCommand, len(builtinCommands))
|
|
copy(cmds, builtinCommands)
|
|
|
|
// scan skills dir for SKILL.md files
|
|
skillsDir := filepath.Join(agentDir, "skills")
|
|
cmds = append(cmds, scanSkillCommands(skillsDir)...)
|
|
|
|
return cmds
|
|
}
|
|
|
|
func scanSkillCommands(skillsDir string) []models.SlashCommand {
|
|
entries, err := os.ReadDir(skillsDir)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var cmds []models.SlashCommand
|
|
for _, e := range entries {
|
|
if !e.IsDir() {
|
|
continue
|
|
}
|
|
skillMd := filepath.Join(skillsDir, e.Name(), "SKILL.md")
|
|
name, desc := parseSkillMD(skillMd)
|
|
if name == "" {
|
|
name = e.Name()
|
|
}
|
|
cmds = append(cmds, models.SlashCommand{
|
|
Name: name,
|
|
Description: desc,
|
|
Source: "skill",
|
|
})
|
|
}
|
|
return cmds
|
|
}
|
|
|
|
func parseSkillMD(path string) (name, desc string) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return "", ""
|
|
}
|
|
content := string(data)
|
|
// parse frontmatter between --- delimiters
|
|
if !strings.HasPrefix(content, "---") {
|
|
return "", ""
|
|
}
|
|
parts := strings.SplitN(content, "---", 3)
|
|
if len(parts) < 3 {
|
|
return "", ""
|
|
}
|
|
fm := parts[1]
|
|
for _, line := range strings.Split(fm, "\n") {
|
|
kv := strings.SplitN(line, ":", 2)
|
|
if len(kv) != 2 {
|
|
continue
|
|
}
|
|
k := strings.TrimSpace(kv[0])
|
|
v := strings.TrimSpace(kv[1])
|
|
switch k {
|
|
case "name":
|
|
name = v
|
|
case "description":
|
|
desc = v
|
|
}
|
|
}
|
|
return name, desc
|
|
}
|