package handler import ( "net/http" "strings" "dbview/service/internal/common/response" "dbview/service/internal/modules/mcp_ai/api" "dbview/service/internal/modules/mcp_ai/service" "github.com/gin-gonic/gin" ) // Handler 提供 AI 聊天接口的 HTTP 处理器。 type Handler struct { svc *service.AIService } // NewHandler 创建 Handler。 func NewHandler(svc *service.AIService) *Handler { return &Handler{svc: svc} } // Chat 提供 POST /ai/chat 接口。 // 支持三种模式: // - sync(默认):同步返回完整文本。 // - stream:SSE 流式返回(同一路径,header 设为 text/event-stream)。 // - async:创建后台任务立即返回 task_id/request_id。 func (h *Handler) Chat(c *gin.Context) { var req api.ChatRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "参数错误", Error: err.Error()}) return } mode := strings.ToLower(strings.TrimSpace(req.Mode)) if mode == "" { mode = "sync" } ctx := c.Request.Context() switch mode { case "async": taskObj, requestID, err := h.svc.StartChatTask(ctx, req.SessionID, req.Prompt, req.System, req.Model, req.ConfigID, req.RequestID, req.TimeoutMs) if err != nil { c.JSON(http.StatusBadRequest, response.Response{Code: 2, Msg: "创建任务失败", Error: err.Error()}) return } c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: api.ChatResponse{Mode: "async", TaskID: taskObj.ID, RequestID: requestID}}) return case "stream": c.Writer.Header().Set("Content-Type", "text/event-stream") c.Writer.Header().Set("Cache-Control", "no-cache") c.Writer.Header().Set("Connection", "keep-alive") flusher, ok := c.Writer.(http.Flusher) if !ok { c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "AI 调用失败", Error: "stream not supported"}) return } resultCh := make(chan struct { text string err error }, 1) go func() { text, err := h.svc.Chat(ctx, req.SessionID, req.Prompt, req.System, req.Model, req.ConfigID, req.RequestID, req.TimeoutMs) resultCh <- struct { text string err error }{text: text, err: err} }() c.SSEvent("start", gin.H{"request_id": req.RequestID}) flusher.Flush() select { case <-ctx.Done(): c.SSEvent("error", gin.H{"error": ctx.Err().Error()}) flusher.Flush() return case res := <-resultCh: if res.err != nil { c.SSEvent("error", gin.H{"error": res.err.Error()}) flusher.Flush() return } c.SSEvent("message", gin.H{"text": res.text}) flusher.Flush() c.SSEvent("done", gin.H{"request_id": req.RequestID}) flusher.Flush() } return default: text, err := h.svc.Chat(ctx, req.SessionID, req.Prompt, req.System, req.Model, req.ConfigID, req.RequestID, req.TimeoutMs) if err != nil { c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "AI 调用失败", Error: err.Error()}) return } c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: api.ChatResponse{Mode: "sync", Text: text, RequestID: req.RequestID}}) } } // CancelChat 提供 POST /ai/chat/cancel 接口,取消指定聊天请求。 func (h *Handler) CancelChat(c *gin.Context) { var req api.CancelChatRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "参数错误", Error: err.Error()}) return } if req.RequestID == "" && req.SessionID == "" { c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "参数错误", Error: "request_id 或 session_id 必填其一"}) return } if err := h.svc.CancelChat(req.RequestID, req.SessionID); err != nil { c.JSON(http.StatusBadRequest, response.Response{Code: 2, Msg: "取消失败", Error: err.Error()}) return } c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok"}) } // History 提供 POST /ai/history 接口,查询指定 session 的消息历史,按时间正序返回。 func (h *Handler) History(c *gin.Context) { var req api.HistoryRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "参数错误", Error: err.Error()}) return } msgs, err := h.svc.ListHistory(c.Request.Context(), req.SessionID, req.Limit, req.Offset) if err != nil { c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "获取历史失败", Error: err.Error()}) return } c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: api.HistoryResponse{Messages: msgs}}) } // DeleteHistory 提供 POST /ai/history/delete 接口,删除指定 session 的全部历史。 func (h *Handler) DeleteHistory(c *gin.Context) { var req api.DeleteHistoryRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "参数错误", Error: err.Error()}) return } if err := h.svc.DeleteHistory(c.Request.Context(), req.SessionID); err != nil { c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "删除历史失败", Error: err.Error()}) return } c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok"}) } // GetConfig 提供 POST /ai/config/get 接口,返回当前 AI 配置。 func (h *Handler) GetConfig(c *gin.Context) { cfg, err := h.svc.GetConfig(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "获取 AI 配置失败", Error: err.Error()}) return } c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: api.ConfigResponse{AISettings: *cfg}}) } // ListConfigs 提供 POST /ai/config/list 接口,返回全部 AI 配置记录。 func (h *Handler) ListConfigs(c *gin.Context) { items, err := h.svc.ListConfigs(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "获取 AI 配置列表失败", Error: err.Error()}) return } c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: api.ConfigListResponse{Items: items}}) } // UpdateConfig 提供 POST /ai/config/update 接口,允许用户覆盖 AI 配置。 func (h *Handler) UpdateConfig(c *gin.Context) { var req api.UpdateConfigRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "参数错误", Error: err.Error()}) return } updated, err := h.svc.UpdateConfig(c.Request.Context(), req.AISettings) if err != nil { c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "更新 AI 配置失败", Error: err.Error()}) return } c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: api.ConfigResponse{AISettings: *updated}}) } // CreateConfig 提供 POST /ai/config/create 接口,创建/覆盖 AI 配置。 func (h *Handler) CreateConfig(c *gin.Context) { var req api.CreateConfigRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "参数错误", Error: err.Error()}) return } created, err := h.svc.CreateConfig(c.Request.Context(), req.AISettings) if err != nil { c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "创建 AI 配置失败", Error: err.Error()}) return } c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: api.ConfigResponse{AISettings: *created}}) } // DeleteConfig 提供 POST /ai/config/delete 接口,删除指定 AI 配置(默认 default)。 func (h *Handler) DeleteConfig(c *gin.Context) { var req api.DeleteConfigRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "参数错误", Error: err.Error()}) return } if err := h.svc.DeleteConfig(c.Request.Context(), req.ID); err != nil { c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "删除 AI 配置失败", Error: err.Error()}) return } c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok"}) }