- 更新 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>
282 lines
8.0 KiB
Go
282 lines
8.0 KiB
Go
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"})
|
||
}
|