Files
likedream/internal/service/config/ai_config_service.go
T
sugarBecomer d1b954ab8e 🐛 学习通答题
2025-12-11 16:49:02 +08:00

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] + "..."
}