feat: 更新数据库和后端服务

- 新增chat_sessions和chat_groups数据库表
- 更新skill_handler和model相关接口
- 修改main.go注册新路由

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 14:33:54 +08:00
parent e19a0ba673
commit e5ea4ff359
5 changed files with 310 additions and 49 deletions

View File

@@ -1,7 +1,9 @@
package handler
import (
"archive/zip"
"fmt"
"io"
"log"
"net/http"
"os"
@@ -155,7 +157,7 @@ func (h *SkillHandler) Create(c *gin.Context) {
// 处理文件上传
file, err := c.FormFile("file")
if err == nil {
// 读取文件内容,解析 YAML front matter 获取 name
// 重新打开文件以便多次读取
fileContent, err := file.Open()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read file: " + err.Error()})
@@ -163,51 +165,172 @@ func (h *SkillHandler) Create(c *gin.Context) {
}
defer fileContent.Close()
content := make([]byte, 1024*1024) // 最多读取 1MB
n, _ := fileContent.Read(content)
contentStr := string(content[:n])
// 检测是否是 zip 文件
isZip := strings.HasSuffix(strings.ToLower(file.Filename), ".zip")
// 解析 name
parsedName, parsedDesc := parseSkillMeta(contentStr)
log.Printf("[SkillHandler] Original skill_name from form: %s, parsed name from file: %s", originalSkillName, parsedName)
var contentStr string
// 优先使用文件解析出的 name
if parsedName != "" {
skillName = parsedName
} else if skillName == "" {
// 如果解析不到且表单也没传,用文件名
skillName = filepath.Base(file.Filename)
}
if isZip {
// 解压 zip 文件
log.Printf("[SkillHandler] Processing ZIP file: %s", file.Filename)
if parsedDesc != "" {
skillDesc = parsedDesc
}
// 读取整个 zip 内容
zipData, err := io.ReadAll(fileContent)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read zip file: " + err.Error()})
return
}
// 清理 skillName只保留纯文件名去除所有路径
skillName = filepath.Base(skillName)
// 去除 .md 后缀
skillName = strings.TrimSuffix(skillName, ".md")
// 去除空格
skillName = strings.TrimSpace(skillName)
// 如果包含路径分隔符,取最后一部分
if idx := strings.LastIndexAny(skillName, "/\\"); idx >= 0 {
skillName = skillName[idx+1:]
}
// 创建临时 zip reader
zipReader, err := zip.NewReader(strings.NewReader(string(zipData)), int64(len(zipData)))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to parse zip file: " + err.Error()})
return
}
log.Printf("[SkillHandler] Final skill name: %s", skillName)
// 先创建技能目录(使用传入的 skill_name 或从 zip 文件名推断)
if skillName == "" {
skillName = strings.TrimSuffix(filepath.Base(file.Filename), ".zip")
}
skillName = strings.TrimSpace(skillName)
// 创建技能目录
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
}
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
}
// 保存文件(使用之前读取的内容)
skillFilePath := filepath.Join(skillPath, "SKILL.md")
if err := os.WriteFile(skillFilePath, []byte(contentStr), 0644); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save skill file: " + err.Error()})
return
// 解压所有文件
for _, zipFile := range zipReader.File {
fileName := zipFile.Name
// 跳过目录
if strings.HasSuffix(fileName, "/") {
continue
}
// 确保文件路径安全,去除任何绝对路径或路径遍历
fileName = strings.TrimPrefix(fileName, "/")
if strings.Contains(fileName, "..") {
continue
}
targetPath := filepath.Join(skillPath, fileName)
// 创建父目录
parentDir := filepath.Dir(targetPath)
if err := os.MkdirAll(parentDir, 0755); err != nil {
log.Printf("[SkillHandler] Warning: failed to create directory %s: %v", parentDir, err)
continue
}
// 读取并解压文件
zipFileContent, err := zipFile.Open()
if err != nil {
log.Printf("[SkillHandler] Warning: failed to open %s in zip: %v", fileName, err)
continue
}
content, err := io.ReadAll(zipFileContent)
zipFileContent.Close()
if err != nil {
log.Printf("[SkillHandler] Warning: failed to read %s from zip: %v", fileName, err)
continue
}
if err := os.WriteFile(targetPath, content, 0644); err != nil {
log.Printf("[SkillHandler] Warning: failed to write %s: %v", targetPath, err)
continue
}
log.Printf("[SkillHandler] Extracted: %s", targetPath)
// 如果是 SKILL.md解析元数据
if strings.HasSuffix(strings.ToLower(fileName), "skill.md") {
contentStr = string(content)
}
}
// 如果没有找到 SKILL.md尝试找其他 .md 文件
if contentStr == "" {
files, _ := filepath.Glob(filepath.Join(skillPath, "*.md"))
if len(files) > 0 {
content, err := os.ReadFile(files[0])
if err == nil {
contentStr = string(content)
}
}
}
// 解析元数据
if contentStr != "" {
parsedName, parsedDesc := parseSkillMeta(contentStr)
if parsedName != "" {
// 如果从 zip 中解析到 skill name可能需要重命名目录
if parsedName != skillName {
newSkillPath := filepath.Join(projectRoot, "core", "agents", "skills", skillDir, parsedName)
if err := os.Rename(skillPath, newSkillPath); err != nil {
log.Printf("[SkillHandler] Warning: failed to rename skill directory: %v", err)
} else {
skillPath = newSkillPath
}
skillName = parsedName
}
}
if parsedDesc != "" {
skillDesc = parsedDesc
}
}
log.Printf("[SkillHandler] ZIP imported successfully: %s", skillName)
} else {
// 普通 md 文件处理(原有逻辑)
content := make([]byte, 1024*1024) // 最多读取 1MB
n, _ := fileContent.Read(content)
contentStr = string(content[:n])
// 解析 name
parsedName, parsedDesc := parseSkillMeta(contentStr)
log.Printf("[SkillHandler] Original skill_name from form: %s, parsed name from file: %s", originalSkillName, parsedName)
// 优先使用文件解析出的 name
if parsedName != "" {
skillName = parsedName
} else if skillName == "" {
// 如果解析不到且表单也没传,用文件名
skillName = filepath.Base(file.Filename)
}
if parsedDesc != "" {
skillDesc = parsedDesc
}
// 清理 skillName只保留纯文件名去除所有路径
skillName = filepath.Base(skillName)
// 去除 .md 后缀
skillName = strings.TrimSuffix(skillName, ".md")
// 去除空格
skillName = strings.TrimSpace(skillName)
// 如果包含路径分隔符,取最后一部分
if idx := strings.LastIndexAny(skillName, "/\\"); idx >= 0 {
skillName = skillName[idx+1:]
}
log.Printf("[SkillHandler] Final skill name: %s", skillName)
// 创建技能目录
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
}
// 保存文件(使用之前读取的内容)
skillFilePath := filepath.Join(skillPath, "SKILL.md")
if err := os.WriteFile(skillFilePath, []byte(contentStr), 0644); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save skill file: " + err.Error()})
return
}
}
} else {
// 如果没有上传文件但提供了 name 和 desc创建默认文件