188 lines
5.2 KiB
Go
188 lines
5.2 KiB
Go
package config
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
v1 "likedream-goframe/internal/api/v1"
|
|
"likedream-goframe/internal/application/quesbank"
|
|
"likedream-goframe/internal/common/db"
|
|
errors2 "likedream-goframe/internal/common/errors"
|
|
"likedream-goframe/internal/ent"
|
|
"likedream-goframe/internal/ent/sysconfig"
|
|
"likedream-goframe/internal/utils"
|
|
"strings"
|
|
)
|
|
|
|
// AI配置键常量
|
|
const (
|
|
AIConfigKey = "ai.config"
|
|
)
|
|
|
|
// AIConfigServiceV1 AI配置服务
|
|
type AIConfigServiceV1 struct{}
|
|
|
|
// NewAIConfigServiceV1 创建AI配置服务
|
|
func NewAIConfigServiceV1() *AIConfigServiceV1 {
|
|
return &AIConfigServiceV1{}
|
|
}
|
|
|
|
// Get 获取AI配置
|
|
func (s *AIConfigServiceV1) Get(ctx context.Context, req *v1.AIConfigGetReq) *utils.Response[*v1.AIConfigGetResp] {
|
|
config, err := db.Global.SysConfig.Query().
|
|
Where(sysconfig.KeyEQ(AIConfigKey)).
|
|
Only(ctx)
|
|
|
|
if err != nil {
|
|
if ent.IsNotFound(err) {
|
|
return utils.SuccessWithData(&v1.AIConfigGetResp{
|
|
Type: "chatglm",
|
|
Model: "glm-4",
|
|
APIKey: "",
|
|
})
|
|
}
|
|
utils.OkOrPanic(err)
|
|
}
|
|
|
|
var aiConfig quesbank.AIConfig
|
|
if err := json.Unmarshal([]byte(config.Value), &aiConfig); err != nil {
|
|
utils.OkOrPanic(errors2.NewBusinessError(500, "AI配置解析失败"))
|
|
}
|
|
|
|
maskedKey := maskAPIKey(aiConfig.APIKey)
|
|
|
|
return utils.SuccessWithData(&v1.AIConfigGetResp{
|
|
Type: string(aiConfig.Type),
|
|
URL: aiConfig.URL,
|
|
Model: aiConfig.Model,
|
|
APIKey: maskedKey,
|
|
})
|
|
}
|
|
|
|
// Update 更新AI配置
|
|
func (s *AIConfigServiceV1) Update(ctx context.Context, req *v1.AIConfigUpdateReq) *utils.Response[any] {
|
|
if req.Type == "other" && req.URL == "" {
|
|
utils.OkOrPanic(errors2.NewBusinessError(400, "自定义AI类型必须填写API地址"))
|
|
}
|
|
|
|
aiConfig := quesbank.AIConfig{
|
|
Type: quesbank.AIType(req.Type),
|
|
URL: req.URL,
|
|
Model: req.Model,
|
|
APIKey: req.APIKey,
|
|
}
|
|
|
|
configValue, err := json.Marshal(aiConfig)
|
|
if err != nil {
|
|
utils.OkOrPanic(errors2.NewBusinessError(500, "配置序列化失败"))
|
|
}
|
|
|
|
config, err := db.Global.SysConfig.Query().
|
|
Where(sysconfig.KeyEQ(AIConfigKey)).
|
|
Only(ctx)
|
|
|
|
if err != nil {
|
|
if ent.IsNotFound(err) {
|
|
db.Global.SysConfig.Create().
|
|
SetKey(AIConfigKey).
|
|
SetValue(string(configValue)).
|
|
SetDescription("AI答题配置").
|
|
SetType("json").
|
|
SaveX(ctx)
|
|
return utils.SuccessWithMsg("AI配置创建成功")
|
|
}
|
|
utils.OkOrPanic(err)
|
|
}
|
|
|
|
db.Global.SysConfig.UpdateOne(config).
|
|
SetValue(string(configValue)).
|
|
ExecX(ctx)
|
|
|
|
return utils.SuccessWithMsg("AI配置更新成功")
|
|
}
|
|
|
|
// Test 测试AI配置
|
|
func (s *AIConfigServiceV1) Test(ctx context.Context, req *v1.AIConfigTestReq) *utils.Response[*v1.AIConfigTestResp] {
|
|
if req.Type == "other" && req.URL == "" {
|
|
return utils.SuccessWithData(&v1.AIConfigTestResp{
|
|
Success: false,
|
|
Message: "自定义AI类型必须填写API地址",
|
|
})
|
|
}
|
|
|
|
aiConfig := quesbank.AIConfig{
|
|
Type: quesbank.AIType(req.Type),
|
|
URL: req.URL,
|
|
Model: req.Model,
|
|
APIKey: req.APIKey,
|
|
}
|
|
|
|
aiService := quesbank.NewAIAnswerService(aiConfig)
|
|
testMessages := quesbank.AIChatMessages{
|
|
Messages: []quesbank.AIChatMessage{
|
|
{Role: "user", Content: "你好,请回复\"测试成功\""},
|
|
},
|
|
}
|
|
|
|
response, err := aiService.GetAnswer(ctx, testMessages)
|
|
if err != nil {
|
|
return utils.SuccessWithData(&v1.AIConfigTestResp{
|
|
Success: false,
|
|
Message: fmt.Sprintf("AI连接测试失败: %v", err),
|
|
})
|
|
}
|
|
|
|
return utils.SuccessWithData(&v1.AIConfigTestResp{
|
|
Success: true,
|
|
Message: fmt.Sprintf("AI连接测试成功,响应: %s", truncateString(response, 100)),
|
|
})
|
|
}
|
|
|
|
// GetTypeList 获取支持的AI类型列表
|
|
func (s *AIConfigServiceV1) GetTypeList(ctx context.Context, req *v1.AITypeListReq) *utils.Response[*v1.AITypeListResp] {
|
|
types := []v1.AITypeItem{
|
|
{Type: "chatglm", Name: "智谱AI (ChatGLM)", Description: "智谱AI大模型,支持glm-4等模型", NeedURL: false},
|
|
{Type: "xinghuo", Name: "讯飞星火", Description: "讯飞星火认知大模型", NeedURL: false},
|
|
{Type: "tongyi", Name: "通义千问", Description: "阿里云通义千问大模型", NeedURL: false},
|
|
{Type: "doubao", Name: "豆包", Description: "字节跳动豆包大模型", NeedURL: false},
|
|
{Type: "openai", Name: "OpenAI", Description: "OpenAI GPT系列模型", NeedURL: false},
|
|
{Type: "other", Name: "其他 (兼容OpenAI格式)", Description: "支持任何兼容OpenAI API格式的服务", NeedURL: true},
|
|
}
|
|
return utils.SuccessWithData(&v1.AITypeListResp{Types: types})
|
|
}
|
|
|
|
// GetAIConfig 获取AI配置(内部使用)
|
|
func (s *AIConfigServiceV1) GetAIConfig(ctx context.Context) (*quesbank.AIConfig, error) {
|
|
config, err := db.Global.SysConfig.Query().
|
|
Where(sysconfig.KeyEQ(AIConfigKey)).
|
|
Only(ctx)
|
|
|
|
if err != nil {
|
|
if ent.IsNotFound(err) {
|
|
return nil, fmt.Errorf("AI配置未设置")
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
var aiConfig quesbank.AIConfig
|
|
if err := json.Unmarshal([]byte(config.Value), &aiConfig); err != nil {
|
|
return nil, fmt.Errorf("AI配置解析失败: %w", err)
|
|
}
|
|
|
|
return &aiConfig, nil
|
|
}
|
|
|
|
func maskAPIKey(key string) string {
|
|
if len(key) <= 8 {
|
|
return strings.Repeat("*", len(key))
|
|
}
|
|
return key[:4] + strings.Repeat("*", len(key)-8) + key[len(key)-4:]
|
|
}
|
|
|
|
func truncateString(s string, maxLen int) string {
|
|
if len(s) <= maxLen {
|
|
return s
|
|
}
|
|
return s[:maxLen] + "..."
|
|
}
|