From e9c93d40299e9bdb5daf736f5e5504d31928f2c5 Mon Sep 17 00:00:00 2001 From: Blizzard Date: Thu, 23 Apr 2026 11:15:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=99=BE=E7=A7=91rag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/v1/plant/ai_chat_api.go | 84 ++++++++++++++++++++++++ api/v1/plant/enter.go | 1 + initialize/gorm.go | 2 + model/plant/ai_chat_history.go | 15 +++++ router/plant/ai_chat_router.go | 7 +- service/plant/ai_chat_history_service.go | 48 ++++++++++++++ service/plant/ai_rag_service.go | 2 +- service/plant/enter.go | 1 + 8 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 model/plant/ai_chat_history.go create mode 100644 service/plant/ai_chat_history_service.go diff --git a/api/v1/plant/ai_chat_api.go b/api/v1/plant/ai_chat_api.go index 98f54ec..777a022 100644 --- a/api/v1/plant/ai_chat_api.go +++ b/api/v1/plant/ai_chat_api.go @@ -3,6 +3,7 @@ package plant import ( "sundynix-go/global" "sundynix-go/model/commom/response" + "sundynix-go/utils/auth" "github.com/gin-gonic/gin" "go.uber.org/zap" @@ -32,7 +33,9 @@ func (a *AiChatApi) ChatStreamPlant(c *gin.Context) { header.Set("Transfer-Encoding", "chunked") w.WriteHeader(200) + var fullAnswer string err := aiRagService.PlantChatStreamRAG(c.Request.Context(), query, func(chunk string) error { + fullAnswer += chunk _, writeErr := w.WriteString("data: " + chunk + "\n\n") w.Flush() return writeErr @@ -47,6 +50,16 @@ func (a *AiChatApi) ChatStreamPlant(c *gin.Context) { _, _ = w.WriteString("data: [DONE]\n\n") w.Flush() } + + // 异步保存问答历史 + if fullAnswer != "" { + userId := auth.GetUserId(c) + go func() { + if saveErr := chatHistoryService.SaveHistory(userId, query, fullAnswer); saveErr != nil { + global.Logger.Error("Save chat history failed", zap.Error(saveErr)) + } + }() + } } // SyncWikiToQdrant 手动触发全量植物百科同步到 Qdrant @@ -63,3 +76,74 @@ func (a *AiChatApi) SyncWikiToQdrant(c *gin.Context) { } response.OkWithMsg("同步成功", c) } + +// GetChatHistory 获取AI问答历史列表 +// @Tags Plant-AiChat +// @Param current query int false "页码" +// @Param pageSize query int false "每页条数" +// @Router /plant/chat/history [get] +func (a *AiChatApi) GetChatHistory(c *gin.Context) { + userId := auth.GetUserId(c) + current := 1 + pageSize := 20 + if v, ok := c.GetQuery("current"); ok { + if n := parseInt(v); n > 0 { + current = n + } + } + if v, ok := c.GetQuery("pageSize"); ok { + if n := parseInt(v); n > 0 { + pageSize = n + } + } + list, total, err := chatHistoryService.GetHistoryList(userId, current, pageSize) + if err != nil { + response.FailWithMsg("查询失败", c) + return + } + response.OkWithData(map[string]interface{}{ + "list": list, + "total": total, + }, c) +} + +// DeleteChatHistory 删除单条历史 +// @Tags Plant-AiChat +// @Router /plant/chat/history/delete [post] +func (a *AiChatApi) DeleteChatHistory(c *gin.Context) { + var req struct { + Id string `json:"id"` + } + if err := c.ShouldBindJSON(&req); err != nil || req.Id == "" { + response.FailWithMsg("参数错误", c) + return + } + userId := auth.GetUserId(c) + if err := chatHistoryService.DeleteHistory(userId, req.Id); err != nil { + response.FailWithMsg("删除失败", c) + return + } + response.OkWithMsg("删除成功", c) +} + +// ClearChatHistory 清空所有历史 +// @Tags Plant-AiChat +// @Router /plant/chat/history/clear [post] +func (a *AiChatApi) ClearChatHistory(c *gin.Context) { + userId := auth.GetUserId(c) + if err := chatHistoryService.ClearHistory(userId); err != nil { + response.FailWithMsg("清空失败", c) + return + } + response.OkWithMsg("已清空", c) +} + +func parseInt(s string) int { + n := 0 + for _, c := range s { + if c >= '0' && c <= '9' { + n = n*10 + int(c-'0') + } + } + return n +} diff --git a/api/v1/plant/enter.go b/api/v1/plant/enter.go index 19f7d88..2224a84 100644 --- a/api/v1/plant/enter.go +++ b/api/v1/plant/enter.go @@ -30,4 +30,5 @@ var ( callbackService = service.GroupApp.PlantServiceGroup.CallbackService exchangeService = service.GroupApp.PlantServiceGroup.ExchangeService aiRagService = service.GroupApp.PlantServiceGroup.AiRagService + chatHistoryService = service.GroupApp.PlantServiceGroup.AiChatHistoryService ) diff --git a/initialize/gorm.go b/initialize/gorm.go index 1a1986a..3d4b748 100644 --- a/initialize/gorm.go +++ b/initialize/gorm.go @@ -62,6 +62,8 @@ func MigrateTable() { plant.ExchangeItem{}, //兑换商品 plant.ExchangeOrder{}, //兑换订单 + + plant.AiChatHistory{}, //AI问答历史 ) if err != nil { global.Logger.Error("Migrate table failed,err:", zap.Error(err)) diff --git a/model/plant/ai_chat_history.go b/model/plant/ai_chat_history.go new file mode 100644 index 0000000..c34caf4 --- /dev/null +++ b/model/plant/ai_chat_history.go @@ -0,0 +1,15 @@ +package plant + +import "sundynix-go/global" + +// AiChatHistory AI问答历史记录 +type AiChatHistory struct { + global.BaseModel + UserId string `json:"userId" gorm:"size:50;index;comment:用户ID"` + Question string `json:"question" gorm:"type:text;comment:用户提问"` + Answer string `json:"answer" gorm:"type:longtext;comment:AI回答"` +} + +func (AiChatHistory) TableName() string { + return "sundynix_ai_chat_history" +} diff --git a/router/plant/ai_chat_router.go b/router/plant/ai_chat_router.go index 54c6c5e..1554c01 100644 --- a/router/plant/ai_chat_router.go +++ b/router/plant/ai_chat_router.go @@ -8,8 +8,11 @@ func (s *AiChatRouter) InitAiChatRouter(Router *gin.RouterGroup) (R gin.IRoutes) aiChatRouter := Router.Group("plant/chat") aiChatApi := aiChatApi { - aiChatRouter.GET("stream", aiChatApi.ChatStreamPlant) // SSE 对话流 - aiChatRouter.POST("sync", aiChatApi.SyncWikiToQdrant) // 后台知识库同步 + aiChatRouter.GET("stream", aiChatApi.ChatStreamPlant) // SSE 对话流 + aiChatRouter.POST("sync", aiChatApi.SyncWikiToQdrant) // 后台知识库同步 + aiChatRouter.GET("history", aiChatApi.GetChatHistory) // 问答历史列表 + aiChatRouter.POST("history/delete", aiChatApi.DeleteChatHistory) // 删除单条 + aiChatRouter.POST("history/clear", aiChatApi.ClearChatHistory) // 清空历史 } return aiChatRouter } diff --git a/service/plant/ai_chat_history_service.go b/service/plant/ai_chat_history_service.go new file mode 100644 index 0000000..ed1360f --- /dev/null +++ b/service/plant/ai_chat_history_service.go @@ -0,0 +1,48 @@ +package plant + +import ( + "sundynix-go/global" + plantModel "sundynix-go/model/plant" +) + +type AiChatHistoryService struct{} + +// SaveHistory 保存一条问答记录 +func (s *AiChatHistoryService) SaveHistory(userId, question, answer string) error { + record := plantModel.AiChatHistory{ + UserId: userId, + Question: question, + Answer: answer, + } + return global.DB.Create(&record).Error +} + +// GetHistoryList 获取用户问答历史(分页,最新在前) +func (s *AiChatHistoryService) GetHistoryList(userId string, current, pageSize int) ([]plantModel.AiChatHistory, int64, error) { + var list []plantModel.AiChatHistory + var total int64 + + db := global.DB.Model(&plantModel.AiChatHistory{}).Where("user_id = ?", userId) + db.Count(&total) + + if current <= 0 { + current = 1 + } + if pageSize <= 0 || pageSize > 50 { + pageSize = 20 + } + offset := (current - 1) * pageSize + + err := db.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&list).Error + return list, total, err +} + +// DeleteHistory 删除单条记录 +func (s *AiChatHistoryService) DeleteHistory(userId, id string) error { + return global.DB.Where("id = ? AND user_id = ?", id, userId).Delete(&plantModel.AiChatHistory{}).Error +} + +// ClearHistory 清空用户所有历史 +func (s *AiChatHistoryService) ClearHistory(userId string) error { + return global.DB.Where("user_id = ?", userId).Delete(&plantModel.AiChatHistory{}).Error +} diff --git a/service/plant/ai_rag_service.go b/service/plant/ai_rag_service.go index fd4f837..a5660d6 100644 --- a/service/plant/ai_rag_service.go +++ b/service/plant/ai_rag_service.go @@ -357,7 +357,7 @@ func (s *AiRagService) PlantChatStreamRAG(ctx context.Context, userQuery string, global.Logger.Warn("Qdrant connect failed, skipping RAG", zap.Error(connErr)) } - systemPrompt := "你是一个专业的植物百科助手,请基于以下知识库信息回答用户问题。如果知识库无相关信息,结合你的通用知识作答。\n" + systemPrompt := "你是一个专业的植物百科助手。回答规则:1.基于知识库信息回答,不够则结合通用知识。2.严禁使用Markdown语法(不要用#、*、-、```等符号)。3.用纯文本回答,段落之间空一行。4.分类用「一、二、三」或emoji开头,重点内容直接加书名号《》或【】强调。5.回答简洁专业、条理清晰。\n" if contextText != "" { systemPrompt += "--- 知识库 ---\n" + contextText + "\n--------------\n" } diff --git a/service/plant/enter.go b/service/plant/enter.go index 400b8b4..6971acf 100644 --- a/service/plant/enter.go +++ b/service/plant/enter.go @@ -13,4 +13,5 @@ type ServiceGroup struct { CallbackService ExchangeService AiRagService + AiChatHistoryService }