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] == "****")) }