Files
X-Agents/server/internal/handler/skill_handler.go
DESKTOP-72TV0V4\caoxiaozhu 414147911a feat: 更新后端skill服务
- 更新 skill_handler.go handler层
- 更新 skill.go model层
- 更新 skill_repo.go repository层
- 更新 skill_service.go service层
- 更新 main.go 入口文件

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 15:23:22 +08:00

282 lines
8.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"})
}