package handler import ( "net/http" "os" "path/filepath" "x-agents/server/internal/model" "x-agents/server/internal/service" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // SkillHandler 技能处理器 type SkillHandler struct { skillService *service.SkillService } // NewSkillHandler 创建技能处理器 func NewSkillHandler(skillService *service.SkillService) *SkillHandler { return &SkillHandler{skillService: skillService} } // List 获取技能列表 // @Summary 获取技能列表 // @Description 获取所有技能列表,支持按类型筛选(system/user) // @Tags 技能管理 // @Accept json // @Produce json // @Param type query string false "技能类型: system(系统技能)/user(用户技能)" // @Success 200 {object} map[string]interface{} "{"list": [], "total": 0}" // @Router /skill/list [get] func (h *SkillHandler) List(c *gin.Context) { skillType := c.Query("type") var skills []model.Skill var err error if skillType != "" { skills, err = h.skillService.GetSkillsByType(skillType) } else { skills, err = h.skillService.GetAllSkills() } if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"list": skills, "total": len(skills)}) } // Sync 手动同步 skills // @Summary 手动同步技能 // @Description 从文件系统扫描 skills 目录并同步到数据库。扫描 account/admin/skills(系统技能) 和 account/{username}/skills(用户技能) // @Tags 技能管理 // @Accept json // @Produce json // @Success 200 {object} map[string]interface{} "{"message": "skills synced", "count": 0}" // @Router /skill/sync [get] func (h *SkillHandler) Sync(c *gin.Context) { if err := h.skillService.InitSkills(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } skills, _ := h.skillService.GetAllSkills() c.JSON(http.StatusOK, gin.H{"message": "skills synced", "count": len(skills)}) } // GetByID 根据ID获取技能 // @Summary 获取技能详情 // @Description 根据ID获取技能详情 // @Tags 技能管理 // @Accept json // @Produce json // @Param id path string true "技能ID" // @Success 200 {object} map[string]interface{} "{"skill": {}}" // @Router /skill/{id} [get] func (h *SkillHandler) GetByID(c *gin.Context) { id := c.Param("id") if id == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "skill id is required"}) return } skill, err := h.skillService.GetSkillByID(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "skill not found"}) return } c.JSON(http.StatusOK, gin.H{"skill": skill}) } // Create 创建技能 // @Summary 创建技能 // @Description 创建新的技能,支持文件上传。管理员用户(admin)上传为system技能,存到core/agents/skills/system/;其他用户上传为user技能,存到core/agents/skills/user/ // @Tags 技能管理 // @Accept multipart/form-data // @Produce json // @Param skill_name formData string true "技能名称" // @Param skill_desc formData string false "技能描述" // @Param skill_type formData string false "技能类型(system/user),不传则根据用户角色自动判断" // @Param file formData file false "技能文件(SKILL.md)" // @Success 200 {object} map[string]interface{} "{"message": "skill created", "skill": {}}" // @Router /skill/add [post] func (h *SkillHandler) Create(c *gin.Context) { // 获取当前用户信息 username, exists := c.Get("username") if !exists { username = "" } userID, _ := c.Get("userID") skillName := c.PostForm("skill_name") skillDesc := c.PostForm("skill_desc") skillType := c.PostForm("skill_type") if skillName == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "skill_name is required"}) return } // 确定技能类型:优先使用传入的type,否则根据用户角色判断 if skillType == "" { // 判断是否为管理员用户 if username == "admin" { skillType = "system" } else { skillType = "user" } } // 获取项目根目录 projectRoot := h.getProjectRoot() if projectRoot == "" { c.JSON(http.StatusInternalServerError, gin.H{"error": "cannot find project root"}) return } // 根据技能类型确定存储路径 skillDir := "user" if skillType == "system" { skillDir = "system" } // 创建技能目录 skillPath := filepath.Join(projectRoot, "core", "agents", "skills", skillDir, skillName) if err := os.MkdirAll(skillPath, 0755); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create skill directory: " + err.Error()}) return } // 处理文件上传 file, err := c.FormFile("file") if err == nil { // 保存上传的文件为 SKILL.md skillFilePath := filepath.Join(skillPath, "SKILL.md") if err := c.SaveUploadedFile(file, skillFilePath); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save skill file: " + err.Error()}) return } } else { // 如果没有上传文件,创建一个默认的 SKILL.md defaultContent := "---\nname: " + skillName + "\ndescription: " + skillDesc + "\n---\n\n# " + skillName + "\n\n" + skillDesc skillFilePath := filepath.Join(skillPath, "SKILL.md") if err := os.WriteFile(skillFilePath, []byte(defaultContent), 0644); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create skill file: " + err.Error()}) return } } // 创建技能记录 skill := &model.Skill{ ID: uuid.New().String(), SkillName: skillName, SkillType: skillType, SkillDesc: skillDesc, Path: filepath.Join(skillPath, "SKILL.md"), Status: "active", } // 记录创建者 if userIDStr, ok := userID.(string); ok { skill.CreatedBy = userIDStr } if err := h.skillService.CreateSkill(skill); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "skill created", "skill": skill}) } // getProjectRoot 获取项目根目录 func (h *SkillHandler) 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 } // Update 更新技能 // @Summary 更新技能 // @Description 更新技能信息 // @Tags 技能管理 // @Accept json // @Produce json // @Param id path string true "技能ID" // @Param skill body model.Skill true "技能信息" // @Success 200 {object} map[string]interface{} "{"message": "skill updated"}" // @Router /skill/{id} [put] func (h *SkillHandler) Update(c *gin.Context) { id := c.Param("id") if id == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "skill id is required"}) return } var skill model.Skill if err := c.ShouldBindJSON(&skill); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } skill.ID = id if err := h.skillService.UpdateSkill(&skill); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "skill updated"}) } // Delete 删除技能 // @Summary 删除技能 // @Description 删除技能 // @Tags 技能管理 // @Accept json // @Produce json // @Param id path string true "技能ID" // @Success 200 {object} map[string]interface{} "{"message": "skill deleted"}" // @Router /skill/{id} [delete] func (h *SkillHandler) Delete(c *gin.Context) { id := c.Param("id") if id == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "skill id is required"}) return } if err := h.skillService.DeleteSkill(id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "skill deleted"}) }