317 lines
8.1 KiB
Go
317 lines
8.1 KiB
Go
package app
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"time"
|
||
|
||
"git.echol.cn/loser/st/server/global"
|
||
"git.echol.cn/loser/st/server/model/app"
|
||
"git.echol.cn/loser/st/server/model/app/request"
|
||
"git.echol.cn/loser/st/server/model/app/response"
|
||
"gorm.io/datatypes"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
type AIConfigService struct{}
|
||
|
||
// CreateAIConfig 创建AI配置
|
||
func (s *AIConfigService) CreateAIConfig(req *request.CreateAIConfigRequest) (*response.AIConfigResponse, error) {
|
||
// 序列化 JSON 字段
|
||
settingsJSON, _ := json.Marshal(req.Settings)
|
||
|
||
config := app.AIConfig{
|
||
Name: req.Name,
|
||
Provider: req.Provider,
|
||
BaseURL: req.BaseURL,
|
||
APIKey: req.APIKey,
|
||
DefaultModel: req.DefaultModel,
|
||
Settings: datatypes.JSON(settingsJSON),
|
||
Models: datatypes.JSON("[]"),
|
||
IsActive: true,
|
||
IsDefault: false,
|
||
}
|
||
|
||
err := global.GVA_DB.Create(&config).Error
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
resp := response.ToAIConfigResponse(&config)
|
||
return &resp, nil
|
||
}
|
||
|
||
// GetAIConfigList 获取AI配置列表
|
||
func (s *AIConfigService) GetAIConfigList() (*response.AIConfigListResponse, error) {
|
||
var configs []app.AIConfig
|
||
var total int64
|
||
|
||
db := global.GVA_DB.Model(&app.AIConfig{})
|
||
|
||
err := db.Count(&total).Error
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
err = db.Order("is_default DESC, created_at DESC").Find(&configs).Error
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
list := make([]response.AIConfigResponse, len(configs))
|
||
for i, config := range configs {
|
||
list[i] = response.ToAIConfigResponse(&config)
|
||
}
|
||
|
||
return &response.AIConfigListResponse{
|
||
List: list,
|
||
Total: total,
|
||
}, nil
|
||
}
|
||
|
||
// UpdateAIConfig 更新AI配置
|
||
func (s *AIConfigService) UpdateAIConfig(id uint, req *request.UpdateAIConfigRequest) error {
|
||
var config app.AIConfig
|
||
|
||
err := global.GVA_DB.First(&config, id).Error
|
||
if err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return errors.New("配置不存在")
|
||
}
|
||
return err
|
||
}
|
||
|
||
updates := map[string]interface{}{}
|
||
|
||
if req.Name != "" {
|
||
updates["name"] = req.Name
|
||
}
|
||
if req.BaseURL != "" {
|
||
updates["base_url"] = req.BaseURL
|
||
}
|
||
// 只有当 API Key 不是脱敏格式时才更新
|
||
// 脱敏格式: xxxx****xxxx
|
||
if req.APIKey != "" && !isMaskedAPIKey(req.APIKey) {
|
||
updates["api_key"] = req.APIKey
|
||
}
|
||
if req.DefaultModel != "" {
|
||
updates["default_model"] = req.DefaultModel
|
||
}
|
||
if req.Settings != nil {
|
||
settingsJSON, _ := json.Marshal(req.Settings)
|
||
updates["settings"] = datatypes.JSON(settingsJSON)
|
||
}
|
||
if req.IsActive != nil {
|
||
updates["is_active"] = *req.IsActive
|
||
}
|
||
if req.IsDefault != nil && *req.IsDefault {
|
||
// 如果设置为默认,先取消其他配置的默认状态
|
||
global.GVA_DB.Model(&app.AIConfig{}).Where("id != ?", id).Update("is_default", false)
|
||
updates["is_default"] = true
|
||
}
|
||
|
||
return global.GVA_DB.Model(&config).Updates(updates).Error
|
||
}
|
||
|
||
// DeleteAIConfig 删除AI配置
|
||
func (s *AIConfigService) DeleteAIConfig(id uint) error {
|
||
result := global.GVA_DB.Delete(&app.AIConfig{}, id)
|
||
if result.Error != nil {
|
||
return result.Error
|
||
}
|
||
if result.RowsAffected == 0 {
|
||
return errors.New("配置不存在")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// GetModels 获取可用模型列表
|
||
func (s *AIConfigService) GetModels(req *request.GetModelsRequest) (*response.GetModelsResponse, error) {
|
||
client := &http.Client{Timeout: 20 * time.Second}
|
||
|
||
// 构建请求
|
||
modelsURL := req.BaseURL + "/models"
|
||
httpReq, err := http.NewRequest("GET", modelsURL, nil)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 设置认证头
|
||
if req.Provider == "openai" || req.Provider == "custom" {
|
||
httpReq.Header.Set("Authorization", "Bearer "+req.APIKey)
|
||
} else if req.Provider == "anthropic" {
|
||
httpReq.Header.Set("x-api-key", req.APIKey)
|
||
httpReq.Header.Set("anthropic-version", "2023-06-01")
|
||
}
|
||
|
||
// 发送请求
|
||
resp, err := client.Do(httpReq)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("请求失败: %v", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
body, _ := io.ReadAll(resp.Body)
|
||
return nil, fmt.Errorf("API返回错误 %d: %s", resp.StatusCode, string(body))
|
||
}
|
||
|
||
// 解析响应
|
||
var result struct {
|
||
Data []struct {
|
||
ID string `json:"id"`
|
||
Object string `json:"object"`
|
||
OwnedBy string `json:"owned_by"`
|
||
} `json:"data"`
|
||
}
|
||
|
||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("解析响应失败: %v", err)
|
||
}
|
||
|
||
// 转换为模型信息
|
||
models := make([]response.ModelInfo, 0, len(result.Data))
|
||
for _, model := range result.Data {
|
||
models = append(models, response.ModelInfo{
|
||
ID: model.ID,
|
||
Name: model.ID,
|
||
OwnedBy: model.OwnedBy,
|
||
})
|
||
}
|
||
|
||
return &response.GetModelsResponse{Models: models}, nil
|
||
}
|
||
|
||
// GetModelsByID 通过配置ID获取模型列表(使用数据库中的完整API Key)
|
||
func (s *AIConfigService) GetModelsByID(id uint) (*response.GetModelsResponse, error) {
|
||
var config app.AIConfig
|
||
err := global.GVA_DB.First(&config, id).Error
|
||
if err != nil {
|
||
return nil, errors.New("配置不存在")
|
||
}
|
||
|
||
// 使用数据库中的完整 API Key
|
||
req := &request.GetModelsRequest{
|
||
Provider: config.Provider,
|
||
BaseURL: config.BaseURL,
|
||
APIKey: config.APIKey,
|
||
}
|
||
|
||
return s.GetModels(req)
|
||
}
|
||
|
||
// TestAIConfig 测试AI配置
|
||
func (s *AIConfigService) TestAIConfig(req *request.TestAIConfigRequest) (*response.TestAIConfigResponse, error) {
|
||
startTime := time.Now()
|
||
client := &http.Client{Timeout: 60 * time.Second}
|
||
|
||
// 构建测试请求
|
||
var requestBody map[string]interface{}
|
||
var endpoint string
|
||
|
||
if req.Provider == "openai" || req.Provider == "custom" {
|
||
endpoint = req.BaseURL + "/chat/completions"
|
||
model := req.Model
|
||
if model == "" {
|
||
model = "gpt-3.5-turbo"
|
||
}
|
||
requestBody = map[string]interface{}{
|
||
"model": model,
|
||
"messages": []map[string]string{
|
||
{"role": "user", "content": "Hello"},
|
||
},
|
||
"max_tokens": 10,
|
||
}
|
||
} else if req.Provider == "anthropic" {
|
||
endpoint = req.BaseURL + "/messages"
|
||
model := req.Model
|
||
if model == "" {
|
||
model = "claude-3-haiku-20240307"
|
||
}
|
||
requestBody = map[string]interface{}{
|
||
"model": model,
|
||
"messages": []map[string]string{
|
||
{"role": "user", "content": "Hello"},
|
||
},
|
||
"max_tokens": 10,
|
||
}
|
||
}
|
||
|
||
bodyBytes, _ := json.Marshal(requestBody)
|
||
httpReq, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(bodyBytes))
|
||
if err != nil {
|
||
return &response.TestAIConfigResponse{
|
||
Success: false,
|
||
Message: fmt.Sprintf("创建请求失败: %v", err),
|
||
}, nil
|
||
}
|
||
|
||
// 设置请求头
|
||
httpReq.Header.Set("Content-Type", "application/json")
|
||
if req.Provider == "openai" || req.Provider == "custom" {
|
||
httpReq.Header.Set("Authorization", "Bearer "+req.APIKey)
|
||
} else if req.Provider == "anthropic" {
|
||
httpReq.Header.Set("x-api-key", req.APIKey)
|
||
httpReq.Header.Set("anthropic-version", "2023-06-01")
|
||
}
|
||
|
||
// 发送请求
|
||
resp, err := client.Do(httpReq)
|
||
latency := time.Since(startTime).Milliseconds()
|
||
|
||
if err != nil {
|
||
return &response.TestAIConfigResponse{
|
||
Success: false,
|
||
Message: fmt.Sprintf("连接失败: %v", err),
|
||
Latency: latency,
|
||
}, nil
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, _ := io.ReadAll(resp.Body)
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
return &response.TestAIConfigResponse{
|
||
Success: false,
|
||
Message: fmt.Sprintf("API返回错误 %d: %s", resp.StatusCode, string(body)),
|
||
Latency: latency,
|
||
}, nil
|
||
}
|
||
|
||
return &response.TestAIConfigResponse{
|
||
Success: true,
|
||
Message: "连接成功,AI响应正常",
|
||
Latency: latency,
|
||
}, nil
|
||
}
|
||
|
||
// TestAIConfigByID 通过ID测试AI配置(使用数据库中的完整API Key)
|
||
func (s *AIConfigService) TestAIConfigByID(id uint) (*response.TestAIConfigResponse, error) {
|
||
var config app.AIConfig
|
||
err := global.GVA_DB.First(&config, id).Error
|
||
if err != nil {
|
||
return nil, errors.New("配置不存在")
|
||
}
|
||
|
||
// 使用数据库中的完整 API Key 进行测试
|
||
req := &request.TestAIConfigRequest{
|
||
Provider: config.Provider,
|
||
BaseURL: config.BaseURL,
|
||
APIKey: config.APIKey, // 使用完整的 API Key,而不是脱敏后的
|
||
Model: config.DefaultModel,
|
||
}
|
||
|
||
return s.TestAIConfig(req)
|
||
}
|
||
|
||
// isMaskedAPIKey 检查是否是脱敏的 API Key
|
||
func isMaskedAPIKey(apiKey string) bool {
|
||
// 脱敏格式: xxxx****xxxx 或 ****
|
||
return len(apiKey) > 0 && (apiKey == "****" || (len(apiKey) > 8 && apiKey[4:len(apiKey)-4] == "****"))
|
||
}
|