Files
X-Agents/server/internal/service/skill_service.go

227 lines
5.9 KiB
Go
Raw Normal View History

package service
import (
"log"
"os"
"path/filepath"
"strings"
"x-agents/server/internal/model"
"x-agents/server/internal/repository"
"gopkg.in/yaml.v3"
)
type SkillService struct {
skillRepo *repository.SkillRepository
}
func NewSkillService(skillRepo *repository.SkillRepository) *SkillService {
return &SkillService{skillRepo: skillRepo}
}
func (s *SkillService) GetAllSkills() ([]model.Skill, error) {
return s.skillRepo.FindAll()
}
func (s *SkillService) GetSkillByID(id string) (*model.Skill, error) {
return s.skillRepo.FindByID(id)
}
func (s *SkillService) GetSkillsByType(skillType string) ([]model.Skill, error) {
return s.skillRepo.FindByType(skillType)
}
func (s *SkillService) CreateSkill(skill *model.Skill) error {
return s.skillRepo.Create(skill)
}
func (s *SkillService) UpdateSkill(skill *model.Skill) error {
return s.skillRepo.Update(skill)
}
func (s *SkillService) DeleteSkill(id string) error {
return s.skillRepo.Delete(id)
}
// InitSkills 初始化扫描所有 skills 目录
func (s *SkillService) InitSkills() error {
log.Println("[SkillService] Starting init skills...")
// 获取项目根目录
projectRoot := s.getProjectRoot()
if projectRoot == "" {
log.Println("[SkillService] Cannot determine project root, skipping skill init")
return nil
}
// 扫描 system skills: account/admin/skills
systemSkillsPath := filepath.Join(projectRoot, "account", "admin", "skills")
if _, err := os.Stat(systemSkillsPath); err == nil {
log.Printf("[SkillService] Scanning system skills from: %s", systemSkillsPath)
systemSkills, err := s.scanSkillsDirectory(systemSkillsPath, "system")
if err != nil {
log.Printf("[SkillService] Error scanning system skills: %v", err)
} else {
log.Printf("[SkillService] Found %d system skills", len(systemSkills))
// 先删除旧的 system skills
s.skillRepo.DeleteByType("system")
// 批量插入
if err := s.skillRepo.UpsertBatch(systemSkills); err != nil {
log.Printf("[SkillService] Error saving system skills: %v", err)
}
}
}
// 扫描 user skills: account/{username}/skills (除了 admin)
accountPath := filepath.Join(projectRoot, "account")
entries, err := os.ReadDir(accountPath)
if err != nil {
log.Printf("[SkillService] Error reading account directory: %v", err)
} else {
for _, entry := range entries {
if !entry.IsDir() || entry.Name() == "admin" {
continue
}
userSkillsPath := filepath.Join(accountPath, entry.Name(), "skills")
if _, err := os.Stat(userSkillsPath); err == nil {
log.Printf("[SkillService] Scanning user skills for %s from: %s", entry.Name(), userSkillsPath)
userSkills, err := s.scanSkillsDirectory(userSkillsPath, "user")
if err != nil {
log.Printf("[SkillService] Error scanning user skills for %s: %v", entry.Name(), err)
} else {
log.Printf("[SkillService] Found %d user skills for %s", len(userSkills), entry.Name())
// 批量插入
if err := s.skillRepo.UpsertBatch(userSkills); err != nil {
log.Printf("[SkillService] Error saving user skills for %s: %v", entry.Name(), err)
}
}
}
}
}
log.Println("[SkillService] Skills initialized successfully")
return nil
}
// scanSkillsDirectory 扫描指定目录下的所有 skill
func (s *SkillService) scanSkillsDirectory(basePath string, skillType string) ([]model.Skill, error) {
var skills []model.Skill
entries, err := os.ReadDir(basePath)
if err != nil {
return nil, err
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
skillDir := filepath.Join(basePath, entry.Name())
skillPath := filepath.Join(skillDir, "SKILL.md")
// 尝试 skill.md大小写不敏感
if _, err := os.Stat(skillPath); os.IsNotExist(err) {
skillPath = filepath.Join(skillDir, "skill.md")
}
skillInfo, err := s.parseSkillFile(skillPath)
if err != nil {
log.Printf("[SkillService] Error parsing skill file %s: %v", skillPath, err)
continue
}
// 如果没有从文件解析到名称,使用目录名
if skillInfo.SkillName == "" {
skillInfo.SkillName = entry.Name()
}
skill := model.Skill{
SkillName: skillInfo.SkillName,
SkillType: skillType,
SkillDesc: skillInfo.SkillDesc,
Path: skillPath,
Status: "active",
}
skills = append(skills, skill)
}
return skills, nil
}
// parseSkillFile 解析 SKILL.md 文件,提取 YAML front matter
func (s *SkillService) parseSkillFile(skillPath string) (*model.Skill, error) {
content, err := os.ReadFile(skillPath)
if err != nil {
return nil, err
}
// 检查是否有 YAML front matter
contentStr := string(content)
if !strings.HasPrefix(contentStr, "---") {
// 没有 front matter返回空
return &model.Skill{}, nil
}
// 找到结束标记
lines := strings.Split(contentStr, "\n")
var endIdx int
for i := 1; i < len(lines); i++ {
if strings.TrimSpace(lines[i]) == "---" {
endIdx = i
break
}
}
if endIdx == 0 {
return &model.Skill{}, nil
}
// 提取 YAML 内容
yamlContent := strings.Join(lines[1:endIdx], "\n")
// 解析 YAML
var frontMatter map[string]string
if err := yaml.Unmarshal([]byte(yamlContent), &frontMatter); err != nil {
return nil, err
}
skill := &model.Skill{
SkillName: frontMatter["name"],
SkillDesc: frontMatter["description"],
}
return skill, nil
}
// getProjectRoot 获取项目根目录
func (s *SkillService) getProjectRoot() string {
execPath, _ := os.Getwd()
projectRoot := execPath
// 如果当前目录名为 server向上找一级
baseName := filepath.Base(execPath)
if baseName == "server" {
projectRoot = filepath.Dir(execPath)
}
// 尝试向上查找包含 .git 的目录
if _, err := os.Stat(filepath.Join(projectRoot, ".git")); os.IsNotExist(err) {
for i := 0; i < 3; i++ {
parent := filepath.Dir(projectRoot)
if parent == projectRoot {
break
}
if _, err := os.Stat(filepath.Join(parent, ".git")); err == nil {
projectRoot = parent
break
}
projectRoot = parent
}
}
return projectRoot
}