feat: 百科知识库存入向量
This commit is contained in:
@@ -0,0 +1,65 @@
|
|||||||
|
package plant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sundynix-go/global"
|
||||||
|
"sundynix-go/model/commom/response"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AiChatApi struct{}
|
||||||
|
|
||||||
|
// ChatStreamPlant 接收用户输入,基于植物百科 RAG 产生流式 SSE 回答
|
||||||
|
// @Summary 植物助手聊天(SSE 流式输出)
|
||||||
|
// @Tags Plant-AiChat
|
||||||
|
// @Param query query string true "用户提问内容"
|
||||||
|
// @Produce text/event-stream
|
||||||
|
// @Router /plant/chat/stream [get]
|
||||||
|
func (a *AiChatApi) ChatStreamPlant(c *gin.Context) {
|
||||||
|
query := c.Query("query")
|
||||||
|
if query == "" {
|
||||||
|
response.FailWithMsg("参数 query 不能为空", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSE Headers(微信小程序通过 enableChunked: true 配合实现打字机效果)
|
||||||
|
w := c.Writer
|
||||||
|
header := w.Header()
|
||||||
|
header.Set("Content-Type", "text/event-stream")
|
||||||
|
header.Set("Cache-Control", "no-cache")
|
||||||
|
header.Set("Connection", "keep-alive")
|
||||||
|
header.Set("Transfer-Encoding", "chunked")
|
||||||
|
w.WriteHeader(200)
|
||||||
|
|
||||||
|
err := aiRagService.PlantChatStreamRAG(c.Request.Context(), query, func(chunk string) error {
|
||||||
|
_, writeErr := w.WriteString("data: " + chunk + "\n\n")
|
||||||
|
w.Flush()
|
||||||
|
return writeErr
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
global.Logger.Error("PlantChatStreamRAG error", zap.Error(err))
|
||||||
|
_, _ = w.WriteString("data: [ERROR]" + err.Error() + "\n\n")
|
||||||
|
w.Flush()
|
||||||
|
} else {
|
||||||
|
// 流结束标志
|
||||||
|
_, _ = w.WriteString("data: [DONE]\n\n")
|
||||||
|
w.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncWikiToQdrant 手动触发全量植物百科同步到 Qdrant
|
||||||
|
// @Summary 同步植物百科数据到 Qdrant 向量库
|
||||||
|
// @Tags System-SysAiConfig
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Router /plant/chat/sync [post]
|
||||||
|
func (a *AiChatApi) SyncWikiToQdrant(c *gin.Context) {
|
||||||
|
if err := aiRagService.SyncWikiToQdrant(); err != nil {
|
||||||
|
global.Logger.Error("SyncWikiToQdrant error", zap.Error(err))
|
||||||
|
response.FailWithMsg("同步失败: "+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMsg("同步成功", c)
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ type ApiGroup struct {
|
|||||||
BadgeConfigApi
|
BadgeConfigApi
|
||||||
CallbackApi
|
CallbackApi
|
||||||
ExchangeApi
|
ExchangeApi
|
||||||
|
AiChatApi
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -28,4 +29,5 @@ var (
|
|||||||
badgeConfigService = service.GroupApp.PlantServiceGroup.BadgeConfigService
|
badgeConfigService = service.GroupApp.PlantServiceGroup.BadgeConfigService
|
||||||
callbackService = service.GroupApp.PlantServiceGroup.CallbackService
|
callbackService = service.GroupApp.PlantServiceGroup.CallbackService
|
||||||
exchangeService = service.GroupApp.PlantServiceGroup.ExchangeService
|
exchangeService = service.GroupApp.PlantServiceGroup.ExchangeService
|
||||||
|
aiRagService = service.GroupApp.PlantServiceGroup.AiRagService
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -139,6 +139,48 @@ func (a *WikiApi) DeleteWiki(c *gin.Context) {
|
|||||||
response.OkWithMsg("删除成功", c)
|
response.OkWithMsg("删除成功", c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SyncWikiQdrant 单条百科同步到 Qdrant
|
||||||
|
// @Tags 百科
|
||||||
|
// @Summary 百科同步到Qdrant
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body common.GetById true "单条百科"
|
||||||
|
// @Router /wiki/sync-qdrant [post]
|
||||||
|
func (a *WikiApi) SyncWikiQdrant(c *gin.Context) {
|
||||||
|
var req common.GetById
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.FailWithMsg("请求参数错误", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := aiRagService.SyncSingleWiki(req.ID); err != nil {
|
||||||
|
global.Logger.Error("同步 Qdrant 失败", zap.Error(err))
|
||||||
|
response.FailWithMsg("同步失败: "+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMsg("同步成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteWikiQdrant 从 Qdrant 移除单条百科向量
|
||||||
|
// @Tags 百科
|
||||||
|
// @Summary 百科移除Qdrant
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body common.GetById true "单条百科"
|
||||||
|
// @Router /wiki/delete-qdrant [post]
|
||||||
|
func (a *WikiApi) DeleteWikiQdrant(c *gin.Context) {
|
||||||
|
var req common.GetById
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.FailWithMsg("请求参数错误", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := aiRagService.DeleteFromQdrant(req.ID); err != nil {
|
||||||
|
global.Logger.Error("从 Qdrant 删除向量失败", zap.Error(err))
|
||||||
|
response.FailWithMsg("移除失败: "+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMsg("移除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
// StarWiki 收藏百科
|
// StarWiki 收藏百科
|
||||||
// @Tags 百科
|
// @Tags 百科
|
||||||
// @Summary 收藏百科
|
// @Summary 收藏百科
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type ApiGroup struct {
|
|||||||
MenuApi
|
MenuApi
|
||||||
OperationRecordApi
|
OperationRecordApi
|
||||||
OssApi
|
OssApi
|
||||||
|
SysAiConfigApi
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -20,4 +21,5 @@ var (
|
|||||||
menuService = service.GroupApp.SystemServiceGroup.MenuService
|
menuService = service.GroupApp.SystemServiceGroup.MenuService
|
||||||
operationRecordService = service.GroupApp.SystemServiceGroup.OperationRecordService
|
operationRecordService = service.GroupApp.SystemServiceGroup.OperationRecordService
|
||||||
ossService = service.GroupApp.SystemServiceGroup.OssService
|
ossService = service.GroupApp.SystemServiceGroup.OssService
|
||||||
|
sysAiConfigService = service.GroupApp.SystemServiceGroup.SysAiConfigService
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"sundynix-go/global"
|
||||||
|
"sundynix-go/model/commom/response"
|
||||||
|
"sundynix-go/model/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SysAiConfigApi struct{}
|
||||||
|
|
||||||
|
// CreateAiConfig 创建 AI 配置
|
||||||
|
// @Summary 创建 AI 配置
|
||||||
|
// @Tags System-SysAiConfig
|
||||||
|
// @accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body system.SysAiConfig true "配置模型"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Router /aiConfig/create [post]
|
||||||
|
func (a *SysAiConfigApi) CreateAiConfig(c *gin.Context) {
|
||||||
|
var cfg system.SysAiConfig
|
||||||
|
if err := c.ShouldBindJSON(&cfg); err != nil {
|
||||||
|
response.FailWithMsg("参数错误:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := sysAiConfigService.Create(&cfg); err != nil {
|
||||||
|
global.Logger.Error("创建AI配置失败", zap.Error(err))
|
||||||
|
response.FailWithMsg("创建失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMsg("创建成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAiConfig 更新 AI 配置
|
||||||
|
// @Summary 更新 AI 配置
|
||||||
|
// @Tags System-SysAiConfig
|
||||||
|
// @accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body system.SysAiConfig true "配置模型"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Router /aiConfig/update [put]
|
||||||
|
func (a *SysAiConfigApi) UpdateAiConfig(c *gin.Context) {
|
||||||
|
var cfg system.SysAiConfig
|
||||||
|
if err := c.ShouldBindJSON(&cfg); err != nil {
|
||||||
|
response.FailWithMsg("参数错误:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := sysAiConfigService.Update(&cfg); err != nil {
|
||||||
|
global.Logger.Error("更新AI配置失败", zap.Error(err))
|
||||||
|
response.FailWithMsg("更新失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMsg("更新成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetActive 设置激活配置
|
||||||
|
// @Summary 设置激活配置(同一时间只有一条激活)
|
||||||
|
// @Tags System-SysAiConfig
|
||||||
|
// @accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body object true "{ \"id\": \"xxx\" }"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Router /aiConfig/setActive [post]
|
||||||
|
func (a *SysAiConfigApi) SetActive(c *gin.Context) {
|
||||||
|
var body struct {
|
||||||
|
Id string `json:"id" binding:"required"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&body); err != nil {
|
||||||
|
response.FailWithMsg("参数错误:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := sysAiConfigService.SetActive(body.Id); err != nil {
|
||||||
|
global.Logger.Error("设置激活AI配置失败", zap.Error(err))
|
||||||
|
response.FailWithMsg("设置失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMsg("设置成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetList 获取配置列表
|
||||||
|
// @Summary 获取 AI 配置列表
|
||||||
|
// @Tags System-SysAiConfig
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Router /aiConfig/list [get]
|
||||||
|
func (a *SysAiConfigApi) GetList(c *gin.Context) {
|
||||||
|
list, err := sysAiConfigService.GetList()
|
||||||
|
if err != nil {
|
||||||
|
global.Logger.Error("获取AI配置列表失败", zap.Error(err))
|
||||||
|
response.FailWithMsg("获取失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithData(response.PageResult{
|
||||||
|
List: list,
|
||||||
|
Total: int64(len(list)),
|
||||||
|
}, c)
|
||||||
|
}
|
||||||
@@ -13,8 +13,10 @@ require (
|
|||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/minio/minio-go/v7 v7.0.95
|
github.com/minio/minio-go/v7 v7.0.95
|
||||||
github.com/mojocn/base64Captcha v1.3.8
|
github.com/mojocn/base64Captcha v1.3.8
|
||||||
|
github.com/qdrant/go-client v1.17.1
|
||||||
github.com/redis/go-redis/v9 v9.7.3
|
github.com/redis/go-redis/v9 v9.7.3
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
|
github.com/sashabaranov/go-openai v1.41.2
|
||||||
github.com/spf13/viper v1.20.1
|
github.com/spf13/viper v1.20.1
|
||||||
github.com/swaggo/files v1.0.1
|
github.com/swaggo/files v1.0.1
|
||||||
github.com/swaggo/gin-swagger v1.6.1
|
github.com/swaggo/gin-swagger v1.6.1
|
||||||
@@ -22,8 +24,9 @@ require (
|
|||||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.70
|
github.com/tencentyun/cos-go-sdk-v5 v0.7.70
|
||||||
github.com/wechatpay-apiv3/wechatpay-go v0.2.21
|
github.com/wechatpay-apiv3/wechatpay-go v0.2.21
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
golang.org/x/crypto v0.45.0
|
golang.org/x/crypto v0.48.0
|
||||||
golang.org/x/sync v0.18.0
|
golang.org/x/sync v0.19.0
|
||||||
|
google.golang.org/grpc v1.78.0
|
||||||
gorm.io/driver/mysql v1.5.7
|
gorm.io/driver/mysql v1.5.7
|
||||||
gorm.io/driver/postgres v1.5.11
|
gorm.io/driver/postgres v1.5.11
|
||||||
gorm.io/gorm v1.26.0
|
gorm.io/gorm v1.26.0
|
||||||
@@ -75,7 +78,7 @@ require (
|
|||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.4 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mailru/easyjson v0.9.1 // indirect
|
github.com/mailru/easyjson v0.9.1 // indirect
|
||||||
@@ -104,12 +107,13 @@ require (
|
|||||||
golang.org/x/arch v0.21.0 // indirect
|
golang.org/x/arch v0.21.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||||
golang.org/x/image v0.26.0 // indirect
|
golang.org/x/image v0.26.0 // indirect
|
||||||
golang.org/x/mod v0.29.0 // indirect
|
golang.org/x/mod v0.32.0 // indirect
|
||||||
golang.org/x/net v0.47.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
golang.org/x/tools v0.38.0 // indirect
|
golang.org/x/tools v0.41.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.9 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
modernc.org/libc v1.64.0 // indirect
|
modernc.org/libc v1.64.0 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GM
|
|||||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM=
|
github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM=
|
||||||
github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU=
|
github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU=
|
||||||
github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA=
|
github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA=
|
||||||
@@ -96,6 +100,8 @@ github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJD
|
|||||||
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
@@ -123,8 +129,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
|
|||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
@@ -163,6 +169,8 @@ github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
|||||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/qdrant/go-client v1.17.1 h1:7QmPwDddrHL3hC4NfycwtQlraVKRLcRi++BX6TTm+3g=
|
||||||
|
github.com/qdrant/go-client v1.17.1/go.mod h1:n1h6GhkdAzcohoXt/5Z19I2yxbCkMA6Jejob3S6NZT8=
|
||||||
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
|
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
|
||||||
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
|
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
@@ -176,6 +184,8 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
|||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
||||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||||
|
github.com/sashabaranov/go-openai v1.41.2 h1:vfPRBZNMpnqu8ELsclWcAvF19lDNgh1t6TVfFFOPiSM=
|
||||||
|
github.com/sashabaranov/go-openai v1.41.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
||||||
@@ -218,6 +228,18 @@ github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2W
|
|||||||
github.com/wechatpay-apiv3/wechatpay-go v0.2.21 h1:uIyMpzvcaHA33W/QPtHstccw+X52HO1gFdvVL9O6Lfs=
|
github.com/wechatpay-apiv3/wechatpay-go v0.2.21 h1:uIyMpzvcaHA33W/QPtHstccw+X52HO1gFdvVL9O6Lfs=
|
||||||
github.com/wechatpay-apiv3/wechatpay-go v0.2.21/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q=
|
github.com/wechatpay-apiv3/wechatpay-go v0.2.21/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
|
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||||
|
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||||
|
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||||
|
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||||
|
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||||
|
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
@@ -231,8 +253,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||||
@@ -243,8 +265,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
@@ -254,8 +276,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|||||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -263,8 +285,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -276,8 +298,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@@ -295,19 +317,25 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ func MigrateTable() {
|
|||||||
system.Menu{},
|
system.Menu{},
|
||||||
system.SysOperationRecord{},
|
system.SysOperationRecord{},
|
||||||
system.Oss{},
|
system.Oss{},
|
||||||
|
system.SysAiConfig{},
|
||||||
|
|
||||||
plant.MyPlant{}, //我的植物
|
plant.MyPlant{}, //我的植物
|
||||||
plant.CarePlan{}, //植物养护计划
|
plant.CarePlan{}, //植物养护计划
|
||||||
|
|||||||
@@ -41,11 +41,12 @@ func Routers() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
//需要鉴权的路由
|
//需要鉴权的路由
|
||||||
systemRouter.InitUserRouter(NeedAuthGroup) //用户相关
|
systemRouter.InitUserRouter(NeedAuthGroup) //用户相关
|
||||||
systemRouter.InitClientRouter(NeedAuthGroup) //客户端相关
|
systemRouter.InitClientRouter(NeedAuthGroup) //客户端相关
|
||||||
systemRouter.InitRoleRouter(NeedAuthGroup) //角色相关
|
systemRouter.InitRoleRouter(NeedAuthGroup) //角色相关
|
||||||
systemRouter.InitMenuRouter(NeedAuthGroup) //菜单相关
|
systemRouter.InitMenuRouter(NeedAuthGroup) //菜单相关
|
||||||
systemRouter.InitOssRouter(NeedAuthGroup) //OSS相关
|
systemRouter.InitOssRouter(NeedAuthGroup) //OSS相关
|
||||||
|
systemRouter.InitSysAiConfigRouter(NeedAuthGroup) //AI配置相关
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -60,6 +61,7 @@ func Routers() {
|
|||||||
plantGroup.InitBadgeConfigRouter(NeedAuthGroup) //徽章配置
|
plantGroup.InitBadgeConfigRouter(NeedAuthGroup) //徽章配置
|
||||||
plantGroup.InitUserProfileRouter(NeedAuthGroup) //用户资料
|
plantGroup.InitUserProfileRouter(NeedAuthGroup) //用户资料
|
||||||
plantGroup.InitExchangeRouter(NeedAuthGroup) //兑换中心
|
plantGroup.InitExchangeRouter(NeedAuthGroup) //兑换中心
|
||||||
|
plantGroup.InitAiChatRouter(NeedAuthGroup) //AI聊天
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -7,7 +7,8 @@ import (
|
|||||||
|
|
||||||
type Wiki struct {
|
type Wiki struct {
|
||||||
global.BaseModel
|
global.BaseModel
|
||||||
IsHot int `json:"isHot" form:"isHot" gorm:"column:is_hot;comment:是否推荐植物"`
|
IsHot int `json:"isHot" form:"isHot" gorm:"column:is_hot;comment:是否推荐植物"`
|
||||||
|
IsVectorSynced int `json:"isVectorSynced" form:"isVectorSynced" gorm:"column:is_vector_synced;type:tinyint;default:0;comment:是否已同步到向量库(0否1是)"`
|
||||||
//基本信息
|
//基本信息
|
||||||
Name string `json:"name" form:"name" gorm:"column:name;size:50;comment:名称"`
|
Name string `json:"name" form:"name" gorm:"column:name;size:50;comment:名称"`
|
||||||
LatinName string `json:"latinName" form:"latinName" gorm:"size:100;column:latin_name;comment:拉丁名"`
|
LatinName string `json:"latinName" form:"latinName" gorm:"size:100;column:latin_name;comment:拉丁名"`
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import "sundynix-go/global"
|
||||||
|
|
||||||
|
type SysAiConfig struct {
|
||||||
|
global.BaseModel
|
||||||
|
IsActive int `gorm:"column:is_active;type:tinyint;default:0;comment:是否激活(1是0否)" json:"isActive" form:"isActive"`
|
||||||
|
// Qdrant 向量库配置
|
||||||
|
QdrantUrl string `gorm:"column:qdrant_url;type:varchar(255);comment:Qdrant接口地址" json:"qdrantUrl" form:"qdrantUrl"`
|
||||||
|
QdrantApiKey string `gorm:"column:qdrant_api_key;type:varchar(255);comment:Qdrant密钥" json:"qdrantApiKey" form:"qdrantApiKey"`
|
||||||
|
QdrantCollection string `gorm:"column:qdrant_collection;type:varchar(100);comment:Qdrant集合名" json:"qdrantCollection" form:"qdrantCollection"`
|
||||||
|
VectorDimension int `gorm:"column:vector_dimension;type:int;comment:向量维度(默认104)" json:"vectorDimension" form:"vectorDimension"`
|
||||||
|
// 对话大模型配置(如 deepseek-chat、qwen-max、ollama 本地等)
|
||||||
|
ChatProvider string `gorm:"column:chat_provider;type:varchar(50);comment:对话模型供应商(deepseek/qwen/local等)" json:"chatProvider" form:"chatProvider"`
|
||||||
|
ChatApiUrl string `gorm:"column:chat_api_url;type:varchar(255);comment:对话模型接口地址" json:"chatApiUrl" form:"chatApiUrl"`
|
||||||
|
ChatApiKey string `gorm:"column:chat_api_key;type:varchar(255);comment:对话模型ApiKey" json:"chatApiKey" form:"chatApiKey"`
|
||||||
|
ChatModelName string `gorm:"column:chat_model_name;type:varchar(100);comment:对话模型名称" json:"chatModelName" form:"chatModelName"`
|
||||||
|
// Embedding 向量化模型配置(可与对话模型用不同供应商,如 bge-m3 本地 + deepseek 对话)
|
||||||
|
EmbeddingProvider string `gorm:"column:embedding_provider;type:varchar(50);comment:Embedding模型供应商" json:"embeddingProvider" form:"embeddingProvider"`
|
||||||
|
EmbeddingApiUrl string `gorm:"column:embedding_api_url;type:varchar(255);comment:Embedding模型接口地址" json:"embeddingApiUrl" form:"embeddingApiUrl"`
|
||||||
|
EmbeddingApiKey string `gorm:"column:embedding_api_key;type:varchar(255);comment:Embedding模型ApiKey" json:"embeddingApiKey" form:"embeddingApiKey"`
|
||||||
|
EmbeddingModelName string `gorm:"column:embedding_model_name;type:varchar(100);comment:Embedding模型名称" json:"embeddingModelName" form:"embeddingModelName"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package plant
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
type AiChatRouter struct{}
|
||||||
|
|
||||||
|
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) // 后台知识库同步
|
||||||
|
}
|
||||||
|
return aiChatRouter
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ type RouterGroup struct {
|
|||||||
UserProfileRouter
|
UserProfileRouter
|
||||||
CallbackRouter
|
CallbackRouter
|
||||||
ExchangeRouter
|
ExchangeRouter
|
||||||
|
AiChatRouter
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化路由
|
// 初始化路由
|
||||||
@@ -29,4 +30,5 @@ var (
|
|||||||
badgeConfigApi = v1.ApiGroupApp.PlantApiGroup.BadgeConfigApi
|
badgeConfigApi = v1.ApiGroupApp.PlantApiGroup.BadgeConfigApi
|
||||||
callbackApi = v1.ApiGroupApp.PlantApiGroup.CallbackApi
|
callbackApi = v1.ApiGroupApp.PlantApiGroup.CallbackApi
|
||||||
exchangeApi = v1.ApiGroupApp.PlantApiGroup.ExchangeApi
|
exchangeApi = v1.ApiGroupApp.PlantApiGroup.ExchangeApi
|
||||||
|
aiChatApi = v1.ApiGroupApp.PlantApiGroup.AiChatApi
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ func (p *WikiRouter) InitWikiRouter(Router *gin.RouterGroup) {
|
|||||||
wikiRouter.GET("/detail", wikiApi.WikiDetail)
|
wikiRouter.GET("/detail", wikiApi.WikiDetail)
|
||||||
wikiRouter.POST("/delete", wikiApi.DeleteWiki)
|
wikiRouter.POST("/delete", wikiApi.DeleteWiki)
|
||||||
wikiRouter.POST("/uploadImg", wikiApi.UploadImg)
|
wikiRouter.POST("/uploadImg", wikiApi.UploadImg)
|
||||||
|
wikiRouter.POST("/sync-qdrant", wikiApi.SyncWikiQdrant)
|
||||||
|
wikiRouter.POST("/delete-qdrant", wikiApi.DeleteWikiQdrant)
|
||||||
|
|
||||||
//用户端
|
//用户端
|
||||||
wikiRouter.GET("/star", wikiApi.StarWiki) //收藏或者取消收藏
|
wikiRouter.GET("/star", wikiApi.StarWiki) //收藏或者取消收藏
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type SysRouterGroup struct {
|
|||||||
MenuRouter
|
MenuRouter
|
||||||
OperationRecordRouter
|
OperationRecordRouter
|
||||||
OssRouter
|
OssRouter
|
||||||
|
SysAiConfigRouter
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化路由
|
// 初始化路由
|
||||||
@@ -21,4 +22,5 @@ var (
|
|||||||
menuApi = v1.ApiGroupApp.SystemApiGroup.MenuApi
|
menuApi = v1.ApiGroupApp.SystemApiGroup.MenuApi
|
||||||
operationRecordApi = v1.ApiGroupApp.SystemApiGroup.OperationRecordApi
|
operationRecordApi = v1.ApiGroupApp.SystemApiGroup.OperationRecordApi
|
||||||
ossApi = v1.ApiGroupApp.SystemApiGroup.OssApi
|
ossApi = v1.ApiGroupApp.SystemApiGroup.OssApi
|
||||||
|
sysAiConfigApi = v1.ApiGroupApp.SystemApiGroup.SysAiConfigApi
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
type SysAiConfigRouter struct{}
|
||||||
|
|
||||||
|
func (s *SysAiConfigRouter) InitSysAiConfigRouter(Router *gin.RouterGroup) (R gin.IRoutes) {
|
||||||
|
sysAiConfigRouter := Router.Group("aiConfig")
|
||||||
|
sysAiConfigApi := sysAiConfigApi
|
||||||
|
{
|
||||||
|
sysAiConfigRouter.POST("create", sysAiConfigApi.CreateAiConfig) // 创建配置
|
||||||
|
sysAiConfigRouter.PUT("update", sysAiConfigApi.UpdateAiConfig) // 更新配置
|
||||||
|
sysAiConfigRouter.POST("setActive", sysAiConfigApi.SetActive) // 设置激活状态
|
||||||
|
sysAiConfigRouter.GET("list", sysAiConfigApi.GetList) // 获取列表
|
||||||
|
}
|
||||||
|
return sysAiConfigRouter
|
||||||
|
}
|
||||||
@@ -0,0 +1,395 @@
|
|||||||
|
package plant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
qdrant "github.com/qdrant/go-client/qdrant"
|
||||||
|
openai "github.com/sashabaranov/go-openai"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"sundynix-go/global"
|
||||||
|
plantModel "sundynix-go/model/plant"
|
||||||
|
"sundynix-go/model/system"
|
||||||
|
systemService "sundynix-go/service/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AiRagService struct{}
|
||||||
|
|
||||||
|
var sysAiConfigService = systemService.SysAiConfigService{}
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
// OpenAI 客户端构建
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func getChatClient(cfg *system.SysAiConfig) *openai.Client {
|
||||||
|
config := openai.DefaultConfig(cfg.ChatApiKey)
|
||||||
|
if cfg.ChatApiUrl != "" {
|
||||||
|
config.BaseURL = cfg.ChatApiUrl
|
||||||
|
}
|
||||||
|
return openai.NewClientWithConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEmbeddingClient(cfg *system.SysAiConfig) *openai.Client {
|
||||||
|
config := openai.DefaultConfig(cfg.EmbeddingApiKey)
|
||||||
|
if cfg.EmbeddingApiUrl != "" {
|
||||||
|
config.BaseURL = cfg.EmbeddingApiUrl
|
||||||
|
}
|
||||||
|
return openai.NewClientWithConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
// Qdrant gRPC 连接
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func newQdrantConn(cfg *system.SysAiConfig) (*grpc.ClientConn, context.Context, error) {
|
||||||
|
addr := strings.TrimPrefix(cfg.QdrantUrl, "http://")
|
||||||
|
addr = strings.TrimPrefix(addr, "https://")
|
||||||
|
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("qdrant grpc dial failed: %w", err)
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
if cfg.QdrantApiKey != "" {
|
||||||
|
ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("api-key", cfg.QdrantApiKey))
|
||||||
|
}
|
||||||
|
return conn, ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnsureCollection 确保 Collection 存在,不存在则创建
|
||||||
|
func EnsureCollection(cfg *system.SysAiConfig) error {
|
||||||
|
conn, ctx, err := newQdrantConn(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
dim := uint64(cfg.VectorDimension)
|
||||||
|
if dim == 0 {
|
||||||
|
dim = 104
|
||||||
|
}
|
||||||
|
collClient := qdrant.NewCollectionsClient(conn)
|
||||||
|
if _, getErr := collClient.Get(ctx, &qdrant.GetCollectionInfoRequest{CollectionName: cfg.QdrantCollection}); getErr == nil {
|
||||||
|
return nil // 已存在
|
||||||
|
}
|
||||||
|
_, err = collClient.Create(ctx, &qdrant.CreateCollection{
|
||||||
|
CollectionName: cfg.QdrantCollection,
|
||||||
|
VectorsConfig: &qdrant.VectorsConfig{
|
||||||
|
Config: &qdrant.VectorsConfig_Params{
|
||||||
|
Params: &qdrant.VectorParams{Size: dim, Distance: qdrant.Distance_Cosine},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("qdrant create collection failed: %w", err)
|
||||||
|
}
|
||||||
|
global.Logger.Info("Qdrant collection created", zap.String("collection", cfg.QdrantCollection))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// wikiID → Qdrant point UUID(确保幂等)
|
||||||
|
func wikiToQdrantID(wikiId string) string {
|
||||||
|
return uuid.NewMD5(uuid.NameSpaceOID, []byte(wikiId)).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildWikiText 拼接用于向量化的文本语料
|
||||||
|
func buildWikiText(w plantModel.Wiki) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"植物名字:%s. 拉丁名:%s. 科属:%s. 生命周期:%s. 生长习性:%s. 病虫害:%s. 光照类型:%s. 最佳温度:%s.",
|
||||||
|
w.Name, w.LatinName, w.Genus, w.LifeCycle, w.GrowthHabit,
|
||||||
|
w.PestsDiseases, w.LightType, w.OptimalTempPeriod,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
// SyncSingleWikiAsync 异步同步单条百科到 Qdrant(新增/更新时调用)
|
||||||
|
// 同步成功后将 is_vector_synced 置为 1
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
func (s *AiRagService) SyncSingleWikiAsync(wikiId string) {
|
||||||
|
go func() {
|
||||||
|
if err := s.syncSingleWiki(wikiId); err != nil {
|
||||||
|
global.Logger.Error("Async sync wiki to Qdrant failed", zap.String("wiki_id", wikiId), zap.Error(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncSingleWiki 同步同步单条百科到 Qdrant(用于API直接调用)
|
||||||
|
func (s *AiRagService) SyncSingleWiki(wikiId string) error {
|
||||||
|
return s.syncSingleWiki(wikiId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AiRagService) syncSingleWiki(wikiId string) error {
|
||||||
|
cfg, err := sysAiConfigService.GetActiveAiConfig()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("no active ai config: %w", err)
|
||||||
|
}
|
||||||
|
if err = EnsureCollection(cfg); err != nil {
|
||||||
|
global.Logger.Warn("EnsureCollection warn", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
var w plantModel.Wiki
|
||||||
|
if err = global.DB.Where("id = ?", wikiId).First(&w).Error; err != nil {
|
||||||
|
return fmt.Errorf("wiki not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
text := buildWikiText(w)
|
||||||
|
embClient := getEmbeddingClient(cfg)
|
||||||
|
embResp, err := embClient.CreateEmbeddings(context.Background(), openai.EmbeddingRequest{
|
||||||
|
Input: []string{text},
|
||||||
|
Model: openai.EmbeddingModel(cfg.EmbeddingModelName),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("embedding failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, qdCtx, err := newQdrantConn(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
ptsClient := qdrant.NewPointsClient(conn)
|
||||||
|
|
||||||
|
_, err = ptsClient.Upsert(qdCtx, &qdrant.UpsertPoints{
|
||||||
|
CollectionName: cfg.QdrantCollection,
|
||||||
|
Points: []*qdrant.PointStruct{{
|
||||||
|
Id: qdrant.NewID(wikiToQdrantID(wikiId)),
|
||||||
|
Vectors: qdrant.NewVectors(embResp.Data[0].Embedding...),
|
||||||
|
Payload: map[string]*qdrant.Value{
|
||||||
|
"wiki_id": qdrant.NewValueString(w.Id),
|
||||||
|
"name": qdrant.NewValueString(w.Name),
|
||||||
|
"full_text": qdrant.NewValueString(text),
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("qdrant upsert failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新同步状态
|
||||||
|
_ = global.DB.Model(&plantModel.Wiki{}).Where("id = ?", wikiId).Update("is_vector_synced", 1).Error
|
||||||
|
global.Logger.Info("Wiki synced to Qdrant", zap.String("wiki_id", wikiId))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
// DeleteFromQdrant 从 Qdrant 删除单条植物的向量点位
|
||||||
|
// 删除成功后将 is_vector_synced 置为 0
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
func (s *AiRagService) DeleteFromQdrant(wikiId string) error {
|
||||||
|
cfg, err := sysAiConfigService.GetActiveAiConfig()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("no active ai config: %w", err)
|
||||||
|
}
|
||||||
|
conn, qdCtx, err := newQdrantConn(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
ptsClient := qdrant.NewPointsClient(conn)
|
||||||
|
|
||||||
|
qID := wikiToQdrantID(wikiId)
|
||||||
|
_, err = ptsClient.Delete(qdCtx, &qdrant.DeletePoints{
|
||||||
|
CollectionName: cfg.QdrantCollection,
|
||||||
|
Points: &qdrant.PointsSelector{
|
||||||
|
PointsSelectorOneOf: &qdrant.PointsSelector_Points{
|
||||||
|
Points: &qdrant.PointsIdsList{
|
||||||
|
Ids: []*qdrant.PointId{qdrant.NewID(qID)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("qdrant delete failed: %w", err)
|
||||||
|
}
|
||||||
|
_ = global.DB.Model(&plantModel.Wiki{}).Where("id = ?", wikiId).Update("is_vector_synced", 0).Error
|
||||||
|
global.Logger.Info("Wiki deleted from Qdrant", zap.String("wiki_id", wikiId))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFromQdrantBatch 批量从 Qdrant 删除(用于批量删除百科时)
|
||||||
|
func (s *AiRagService) DeleteFromQdrantBatch(wikiIds []string) {
|
||||||
|
go func() {
|
||||||
|
cfg, err := sysAiConfigService.GetActiveAiConfig()
|
||||||
|
if err != nil {
|
||||||
|
global.Logger.Warn("No active ai config for batch qdrant delete", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn, qdCtx, err := newQdrantConn(cfg)
|
||||||
|
if err != nil {
|
||||||
|
global.Logger.Warn("Qdrant connect failed for batch delete", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
ptsClient := qdrant.NewPointsClient(conn)
|
||||||
|
|
||||||
|
var ids []*qdrant.PointId
|
||||||
|
for _, wid := range wikiIds {
|
||||||
|
ids = append(ids, qdrant.NewID(wikiToQdrantID(wid)))
|
||||||
|
}
|
||||||
|
_, err = ptsClient.Delete(qdCtx, &qdrant.DeletePoints{
|
||||||
|
CollectionName: cfg.QdrantCollection,
|
||||||
|
Points: &qdrant.PointsSelector{
|
||||||
|
PointsSelectorOneOf: &qdrant.PointsSelector_Points{
|
||||||
|
Points: &qdrant.PointsIdsList{Ids: ids},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
global.Logger.Error("Qdrant batch delete failed", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
global.Logger.Info("Qdrant batch delete done", zap.Int("count", len(wikiIds)))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
// SyncWikiToQdrant 全量同步(后台操作/手动触发)
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
func (s *AiRagService) SyncWikiToQdrant() error {
|
||||||
|
cfg, err := sysAiConfigService.GetActiveAiConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = EnsureCollection(cfg); err != nil {
|
||||||
|
global.Logger.Warn("EnsureCollection failed, continuing", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
var wikis []plantModel.Wiki
|
||||||
|
if err = global.DB.Find(&wikis).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
embClient := getEmbeddingClient(cfg)
|
||||||
|
conn, qdCtx, err := newQdrantConn(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
ptsClient := qdrant.NewPointsClient(conn)
|
||||||
|
|
||||||
|
var successIds []string
|
||||||
|
for _, w := range wikis {
|
||||||
|
text := buildWikiText(w)
|
||||||
|
embResp, embErr := embClient.CreateEmbeddings(context.Background(), openai.EmbeddingRequest{
|
||||||
|
Input: []string{text},
|
||||||
|
Model: openai.EmbeddingModel(cfg.EmbeddingModelName),
|
||||||
|
})
|
||||||
|
if embErr != nil {
|
||||||
|
global.Logger.Error("Embedding failed", zap.String("wiki_id", w.Id), zap.Error(embErr))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, upsertErr := ptsClient.Upsert(qdCtx, &qdrant.UpsertPoints{
|
||||||
|
CollectionName: cfg.QdrantCollection,
|
||||||
|
Points: []*qdrant.PointStruct{{
|
||||||
|
Id: qdrant.NewID(wikiToQdrantID(w.Id)),
|
||||||
|
Vectors: qdrant.NewVectors(embResp.Data[0].Embedding...),
|
||||||
|
Payload: map[string]*qdrant.Value{
|
||||||
|
"wiki_id": qdrant.NewValueString(w.Id),
|
||||||
|
"name": qdrant.NewValueString(w.Name),
|
||||||
|
"full_text": qdrant.NewValueString(text),
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
if upsertErr != nil {
|
||||||
|
global.Logger.Error("Qdrant upsert failed", zap.String("wiki_id", w.Id), zap.Error(upsertErr))
|
||||||
|
} else {
|
||||||
|
successIds = append(successIds, w.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量更新同步状态
|
||||||
|
if len(successIds) > 0 {
|
||||||
|
_ = global.DB.Model(&plantModel.Wiki{}).Where("id IN ?", successIds).Update("is_vector_synced", 1).Error
|
||||||
|
}
|
||||||
|
global.Logger.Info("SyncWikiToQdrant done", zap.Int("total", len(wikis)), zap.Int("success", len(successIds)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
// PlantChatStreamRAG 向量检索 + 大模型流式对话
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
func (s *AiRagService) PlantChatStreamRAG(ctx context.Context, userQuery string, onData func(chunk string) error) error {
|
||||||
|
cfg, err := sysAiConfigService.GetActiveAiConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
embClient := getEmbeddingClient(cfg)
|
||||||
|
chatClient := getChatClient(cfg)
|
||||||
|
|
||||||
|
embResp, err := embClient.CreateEmbeddings(ctx, openai.EmbeddingRequest{
|
||||||
|
Input: []string{userQuery},
|
||||||
|
Model: openai.EmbeddingModel(cfg.EmbeddingModelName),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("向量化查询失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, qdCtx, connErr := newQdrantConn(cfg)
|
||||||
|
var contextText string
|
||||||
|
if connErr == nil {
|
||||||
|
defer conn.Close()
|
||||||
|
ptsClient := qdrant.NewPointsClient(conn)
|
||||||
|
limit := uint64(3)
|
||||||
|
searchRes, searchErr := ptsClient.Search(qdCtx, &qdrant.SearchPoints{
|
||||||
|
CollectionName: cfg.QdrantCollection,
|
||||||
|
Vector: embResp.Data[0].Embedding,
|
||||||
|
Limit: limit,
|
||||||
|
WithPayload: &qdrant.WithPayloadSelector{
|
||||||
|
SelectorOptions: &qdrant.WithPayloadSelector_Enable{Enable: true},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if searchErr != nil {
|
||||||
|
global.Logger.Warn("Qdrant search failed, using empty context", zap.Error(searchErr))
|
||||||
|
} else {
|
||||||
|
for _, pt := range searchRes.GetResult() {
|
||||||
|
if txt, ok := pt.GetPayload()["full_text"]; ok {
|
||||||
|
contextText += txt.GetStringValue() + "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
global.Logger.Warn("Qdrant connect failed, skipping RAG", zap.Error(connErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
systemPrompt := "你是一个专业的植物百科助手,请基于以下知识库信息回答用户问题。如果知识库无相关信息,结合你的通用知识作答。\n"
|
||||||
|
if contextText != "" {
|
||||||
|
systemPrompt += "--- 知识库 ---\n" + contextText + "\n--------------\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
stream, err := chatClient.CreateChatCompletionStream(ctx, openai.ChatCompletionRequest{
|
||||||
|
Model: cfg.ChatModelName,
|
||||||
|
Messages: []openai.ChatCompletionMessage{
|
||||||
|
{Role: openai.ChatMessageRoleSystem, Content: systemPrompt},
|
||||||
|
{Role: openai.ChatMessageRoleUser, Content: userQuery},
|
||||||
|
},
|
||||||
|
Stream: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("大模型调用失败: %w", err)
|
||||||
|
}
|
||||||
|
defer stream.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
resp, recvErr := stream.Recv()
|
||||||
|
if errors.Is(recvErr, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if recvErr != nil {
|
||||||
|
return recvErr
|
||||||
|
}
|
||||||
|
if len(resp.Choices) > 0 {
|
||||||
|
if content := resp.Choices[0].Delta.Content; content != "" {
|
||||||
|
if writeErr := onData(content); writeErr != nil {
|
||||||
|
return writeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -12,4 +12,5 @@ type ServiceGroup struct {
|
|||||||
UserProfileService
|
UserProfileService
|
||||||
CallbackService
|
CallbackService
|
||||||
ExchangeService
|
ExchangeService
|
||||||
|
AiRagService
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-2
@@ -11,13 +11,15 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var aiRagService = AiRagService{}
|
||||||
|
|
||||||
type WikiService struct{}
|
type WikiService struct{}
|
||||||
|
|
||||||
var WikiServiceApp = new(WikiClassService)
|
var WikiServiceApp = new(WikiClassService)
|
||||||
|
|
||||||
// CreateWiki 创建百科
|
// CreateWiki 创建百科
|
||||||
func (s *WikiService) CreateWiki(req plantReq.CreateWiki) error {
|
func (s *WikiService) CreateWiki(req plantReq.CreateWiki) error {
|
||||||
return global.DB.Transaction(func(tx *gorm.DB) error {
|
err := global.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
//1.先模糊查询name是否存在 如果存在 则返回错误
|
//1.先模糊查询name是否存在 如果存在 则返回错误
|
||||||
if !errors.Is(tx.Where("name like ?", "%"+req.Name+"%").First(&plant.Wiki{}).Error, gorm.ErrRecordNotFound) {
|
if !errors.Is(tx.Where("name like ?", "%"+req.Name+"%").First(&plant.Wiki{}).Error, gorm.ErrRecordNotFound) {
|
||||||
return errors.New("植物已经存在")
|
return errors.New("植物已经存在")
|
||||||
@@ -117,6 +119,14 @@ func (s *WikiService) CreateWiki(req plantReq.CreateWiki) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
if err == nil {
|
||||||
|
// 异步同步到 Qdrant(事务提交后,wiki.Id 已可用)
|
||||||
|
var created plant.Wiki
|
||||||
|
if dbErr := global.DB.Where("name = ?", req.Name).First(&created).Error; dbErr == nil {
|
||||||
|
aiRagService.SyncSingleWikiAsync(created.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateWiki 修改百科
|
// UpdateWiki 修改百科
|
||||||
@@ -288,7 +298,7 @@ func (s *WikiService) UploadImg(req common.UploadOss) error {
|
|||||||
|
|
||||||
// DeleteWiki 删除百科
|
// DeleteWiki 删除百科
|
||||||
func (s *WikiService) DeleteWiki(req common.IdsReq) error {
|
func (s *WikiService) DeleteWiki(req common.IdsReq) error {
|
||||||
return global.DB.Transaction(func(tx *gorm.DB) error {
|
err := global.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
var imgIds []string
|
var imgIds []string
|
||||||
tx.Table("sundynix_wiki_oss").Where("wiki_id IN ?", req.Ids).Pluck("oss_id", &imgIds)
|
tx.Table("sundynix_wiki_oss").Where("wiki_id IN ?", req.Ids).Pluck("oss_id", &imgIds)
|
||||||
// 3. 物理删除图片记录本身
|
// 3. 物理删除图片记录本身
|
||||||
@@ -311,4 +321,9 @@ func (s *WikiService) DeleteWiki(req common.IdsReq) error {
|
|||||||
//删除百科本身
|
//删除百科本身
|
||||||
return tx.Unscoped().Where("id IN ?", req.Ids).Delete(&plant.Wiki{}).Error
|
return tx.Unscoped().Where("id IN ?", req.Ids).Delete(&plant.Wiki{}).Error
|
||||||
})
|
})
|
||||||
|
if err == nil {
|
||||||
|
// 异步清理 Qdrant 向量点位
|
||||||
|
aiRagService.DeleteFromQdrantBatch(req.Ids)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ type ServiceGroup struct {
|
|||||||
MenuService
|
MenuService
|
||||||
OperationRecordService
|
OperationRecordService
|
||||||
OssService
|
OssService
|
||||||
|
SysAiConfigService
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"sundynix-go/global"
|
||||||
|
"sundynix-go/model/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SysAiConfigService struct{}
|
||||||
|
|
||||||
|
// Create 创建AI配置
|
||||||
|
func (s *SysAiConfigService) Create(cfg *system.SysAiConfig) error {
|
||||||
|
return global.DB.Create(cfg).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update 更新AI配置
|
||||||
|
func (s *SysAiConfigService) Update(cfg *system.SysAiConfig) error {
|
||||||
|
return global.DB.Updates(cfg).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetActive 设置激活配置
|
||||||
|
func (s *SysAiConfigService) SetActive(id string) error {
|
||||||
|
// 先将所有的设为 0
|
||||||
|
err := global.DB.Model(&system.SysAiConfig{}).Where("1 = 1").Update("is_active", 0).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 再将指定的设为 1
|
||||||
|
return global.DB.Model(&system.SysAiConfig{}).Where("id = ?", id).Update("is_active", 1).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActiveAiConfig 获取当前激活的AI配置
|
||||||
|
func (s *SysAiConfigService) GetActiveAiConfig() (*system.SysAiConfig, error) {
|
||||||
|
var cfg system.SysAiConfig
|
||||||
|
err := global.DB.Where("is_active = 1").First(&cfg).Error
|
||||||
|
if err != nil {
|
||||||
|
global.Logger.Error("No active AI Config found", zap.Error(err))
|
||||||
|
return nil, errors.New("无激活状态的AI配置")
|
||||||
|
}
|
||||||
|
return &cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetList 获取所有配置
|
||||||
|
func (s *SysAiConfigService) GetList() ([]system.SysAiConfig, error) {
|
||||||
|
var list []system.SysAiConfig
|
||||||
|
err := global.DB.Order("created_at desc").Find(&list).Error
|
||||||
|
return list, err
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user