package controllers import ( "bufio" "encoding/json" "fmt" "fwh-toolbox-server/middleware" "fwh-toolbox-server/services" "github.com/gofiber/fiber/v2" ) var acpManager *services.ACPManager // InitAIChat 初始化 AI Chat 模块 func InitAIChat(serviceDir string) { acpManager = services.NewACPManager(serviceDir, []string{"HOME=/home/jerry-remote"}) } // AiChat 发送消息(SSE 流式响应) // POST /ai/chat func AiChat(c *fiber.Ctx) error { var req struct { Message string `json:"message"` SessionId string `json:"sessionId"` } if err := c.BodyParser(&req); err != nil { return Fail(c, "Invalid request body") } if req.Message == "" { return Fail(c, "message 不能为空") } userId := middleware.AuthGetUserName(c) sess, err := acpManager.GetOrCreateSession(userId) if err != nil { return Error(c, fmt.Sprintf("AI 会话创建失败: %v", err)) } // SSE 流式响应 c.Set("Content-Type", "text/event-stream") c.Set("Cache-Control", "no-cache") c.Set("Connection", "keep-alive") c.Set("X-Accel-Buffering", "no") c.Context().SetBodyStreamWriter(func(w *bufio.Writer) { // 如果有欢迎消息且是新会话,先发送 welcome 事件 if sess.WelcomeMessage != "" { welcomeEvent, _ := json.Marshal(services.AIChatEvent{Type: "welcome", Content: sess.WelcomeMessage}) fmt.Fprintf(w, "event: welcome\ndata: %s\n\n", string(welcomeEvent)) w.Flush() } eventCh := make(chan services.AIChatEvent, 50) go acpManager.Prompt(sess, req.Message, eventCh) for event := range eventCh { data, _ := json.Marshal(event) fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event.Type, string(data)) w.Flush() } }) return nil } // GetAiSessions 获取当前用户的会话信息 // GET /ai/sessions func GetAiSessions(c *fiber.Ctx) error { userId := middleware.AuthGetUserName(c) sess, err := acpManager.GetOrCreateSession(userId) if err != nil { return Error(c, fmt.Sprintf("AI 会话创建失败: %v", err)) } if sess == nil { return Success(c, fiber.Map{ "active": false, "sessionId": "", }) } return Success(c, fiber.Map{ "active": true, "sessionId": sess.SessionId, "title": sess.Title, "welcomeMessage": sess.WelcomeMessage, "lastActive": sess.LastActive, }) } // GetAiSessionList 获取用户的会话历史列表 // GET /ai/sessions/list func GetAiSessionList(c *fiber.Ctx) error { userId := middleware.AuthGetUserName(c) list := acpManager.GetSessionList(userId) return Success(c, list) } // CreateAiSession 创建新的 AI 会话(保留旧会话到历史) // POST /ai/sessions/new func CreateAiSession(c *fiber.Ctx) error { userId := middleware.AuthGetUserName(c) // 创建新会话(旧会话自动归档到历史) sess, err := acpManager.CreateNewSession(userId) if err != nil { return Error(c, fmt.Sprintf("创建会话失败: %v", err)) } return Success(c, fiber.Map{ "sessionId": sess.SessionId, }) } // LoadAiSession 加载历史会话(恢复 AI 上下文) // POST /ai/sessions/:id/load func LoadAiSession(c *fiber.Ctx) error { sessionId := c.Params("id") if sessionId == "" { return Fail(c, "sessionId 不能为空") } userId := middleware.AuthGetUserName(c) sess, err := acpManager.LoadSession(userId, sessionId) if err != nil { return Error(c, fmt.Sprintf("加载会话失败: %v", err)) } return Success(c, fiber.Map{ "sessionId": sess.SessionId, "title": sess.Title, "replayMessages": sess.ReplayMessages, }) } // UpdateAiSessionTitle 更新会话标题 // PATCH /ai/sessions/:id func UpdateAiSessionTitle(c *fiber.Ctx) error { sessionId := c.Params("id") if sessionId == "" { return Fail(c, "sessionId 不能为空") } var req struct { Title string `json:"title"` } if err := c.BodyParser(&req); err != nil { return Fail(c, "Invalid request body") } userId := middleware.AuthGetUserName(c) acpManager.UpdateSessionTitle(userId, sessionId, req.Title) return Success[any](c, nil, "标题已更新") } // DeleteAiSession 删除 AI 会话 // DELETE /ai/sessions/:id func DeleteAiSession(c *fiber.Ctx) error { sessionId := c.Params("id") userId := middleware.AuthGetUserName(c) if sessionId == "" { // 无 id 参数,删除当前活跃会话 acpManager.DeleteSession(userId) } else { // 删除指定会话 acpManager.DeleteSessionFromHistory(userId, sessionId) } return Success[any](c, nil, "会话已删除") } // CancelAiChat 取消当前 AI 操作 // POST /ai/cancel func CancelAiChat(c *fiber.Ctx) error { userId := middleware.AuthGetUserName(c) sess := acpManager.GetSessionInfo(userId) if sess == nil { return Fail(c, "没有活跃的会话") } acpManager.Cancel(sess) return Success[any](c, nil, "已取消") } // ResolvePermission 前端回传写权限选择 // POST /ai/permission func ResolvePermission(c *fiber.Ctx) error { var req struct { OptionId string `json:"optionId"` } if err := c.BodyParser(&req); err != nil { return Fail(c, "Invalid request body") } if req.OptionId == "" { return Fail(c, "optionId 不能为空") } userId := middleware.AuthGetUserName(c) if err := acpManager.ResolvePermission(userId, req.OptionId); err != nil { return Fail(c, err.Error()) } return Success[any](c, nil, "已回复") } // DownloadAiFile 下载 AI 生成的文件 // GET /ai/files/download?key=xxx func DownloadAiFile(c *fiber.Ctx) error { key := c.Query("key") if key == "" { return Fail(c, "key 不能为空") } userId := middleware.AuthGetUserName(c) sess := acpManager.GetSessionInfo(userId) if sess == nil { return Fail(c, "没有活跃的会话") } // 从 FileStore 查找文件记录 record := sess.Client.FileStore.GetFileByKey(key) if record == nil { return Fail(c, "文件不存在") } // 从 MinIO 读取文件内容 content, err := sess.Client.FileStore.ReadFile(record.Path) if err != nil { return Error(c, err.Error()) } // 设置下载头(文件名为原始名,不含 uuid 前缀) c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, record.FileName)) c.Set("Content-Type", "application/octet-stream") return c.SendString(content) }