diff --git a/.gitignore b/.gitignore index 66d88d5..b8a7761 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /backend/.idea /bin +.aider* diff --git a/backend/api/handlers.go b/backend/api/handlers.go index 13eb07f..4b4a519 100644 --- a/backend/api/handlers.go +++ b/backend/api/handlers.go @@ -399,15 +399,15 @@ func (h *APIHandler) GetRequestLogStatsHandler(c *gin.Context) { func (h *APIHandler) ClearRequestLogsHandler(c *gin.Context) { // 获取查询参数 olderThan := c.Query("older_than") // 清空多少天前的日志 - + // 先查询当前总记录数(包括软删除的记录) var totalCountBefore int64 h.DB.Unscoped().Model(&models.RequestLog{}).Count(&totalCountBefore) - + // 查询活跃记录数(不包括软删除的记录) var activeCountBefore int64 h.DB.Model(&models.RequestLog{}).Count(&activeCountBefore) - + var startTime time.Time if olderThan != "" { // 解析天数 @@ -416,12 +416,12 @@ func (h *APIHandler) ClearRequestLogsHandler(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid older_than parameter, must be a number representing days"}) return } - + if days < 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "older_than must be 0 or greater"}) return } - + // 如果是0天,要删除所有日志,不需要时间限制 if days == 0 { // 使用Unscoped进行硬删除,真正删除记录而非软删除 @@ -430,81 +430,80 @@ func (h *APIHandler) ClearRequestLogsHandler(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear logs"}) return } - + // 执行VACUUM来回收数据库空间 vacuumError := "" if err := h.DB.Exec("VACUUM").Error; err != nil { vacuumError = fmt.Sprintf("VACUUM failed: %v", err) fmt.Printf("Warning: %s\n", vacuumError) } - + // 查询删除后的记录数 var totalCountAfter int64 h.DB.Unscoped().Model(&models.RequestLog{}).Count(&totalCountAfter) - + // 返回详细的删除信息 response := gin.H{ - "message": "All logs cleared successfully with hard delete and vacuum", - "deleted_count": result.RowsAffected, - "older_than_days": olderThan, - "total_count_before": totalCountBefore, + "message": "All logs cleared successfully with hard delete and vacuum", + "deleted_count": result.RowsAffected, + "older_than_days": olderThan, + "total_count_before": totalCountBefore, "active_count_before": activeCountBefore, - "total_count_after": totalCountAfter, - "hard_delete": true, - "vacuum_executed": vacuumError == "", + "total_count_after": totalCountAfter, + "hard_delete": true, + "vacuum_executed": vacuumError == "", } if vacuumError != "" { response["vacuum_error"] = vacuumError } - + c.JSON(http.StatusOK, response) return } - + startTime = time.Now().AddDate(0, 0, -days) } else { // 如果没有指定天数,默认清空30天前的日志 startTime = time.Now().AddDate(0, 0, -30) } - + // 使用Unscoped执行硬删除操作,真正删除记录而非软删除 result := h.DB.Unscoped().Where("created_at < ?", startTime).Delete(&models.RequestLog{}) if result.Error != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear logs"}) return } - + // 执行VACUUM来回收数据库空间 vacuumError := "" if err := h.DB.Exec("VACUUM").Error; err != nil { vacuumError = fmt.Sprintf("VACUUM failed: %v", err) fmt.Printf("Warning: %s\n", vacuumError) } - + // 查询删除后的记录数 var totalCountAfter int64 h.DB.Unscoped().Model(&models.RequestLog{}).Count(&totalCountAfter) - + // 返回详细的删除信息 response := gin.H{ - "message": "Logs cleared successfully with hard delete and vacuum", - "deleted_count": result.RowsAffected, - "older_than_days": olderThan, - "cutoff_time": startTime, - "total_count_before": totalCountBefore, + "message": "Logs cleared successfully with hard delete and vacuum", + "deleted_count": result.RowsAffected, + "older_than_days": olderThan, + "cutoff_time": startTime, + "total_count_before": totalCountBefore, "active_count_before": activeCountBefore, - "total_count_after": totalCountAfter, - "hard_delete": true, - "vacuum_executed": vacuumError == "", + "total_count_after": totalCountAfter, + "hard_delete": true, + "vacuum_executed": vacuumError == "", } if vacuumError != "" { response["vacuum_error"] = vacuumError } - + c.JSON(http.StatusOK, response) } - // GetLogCleanerStatusHandler 获取日志清理器状态 func (h *APIHandler) GetLogCleanerStatusHandler(c *gin.Context) { if h.LogCleaner == nil { @@ -542,21 +541,20 @@ func (h *APIHandler) ForceLogCleanupHandler(c *gin.Context) { // 返回清理报告 c.JSON(http.StatusOK, gin.H{ - "message": "Manual log cleanup completed", - "execute_time": report.ExecuteTime.Format("2006-01-02 15:04:05"), - "duration": report.Duration.String(), - "deleted_count": report.DeletedCount, - "total_count_before": report.TotalCountBefore, + "message": "Manual log cleanup completed", + "execute_time": report.ExecuteTime.Format("2006-01-02 15:04:05"), + "duration": report.Duration.String(), + "deleted_count": report.DeletedCount, + "total_count_before": report.TotalCountBefore, "active_count_before": report.ActiveCountBefore, - "total_count_after": report.TotalCountAfter, - "cutoff_time": report.CutoffTime.Format("2006-01-02 15:04:05"), - "vacuum_duration": report.VacuumDuration.String(), - "vacuum_error": report.VacuumError, - "success": report.Success, + "total_count_after": report.TotalCountAfter, + "cutoff_time": report.CutoffTime.Format("2006-01-02 15:04:05"), + "vacuum_duration": report.VacuumDuration.String(), + "vacuum_error": report.VacuumError, + "success": report.Success, }) } - // ============ API Key 管理相关处理器 ============ // APIKeyListResponse API Key列表响应 @@ -569,13 +567,13 @@ type APIKeyListResponse struct { // GetAPIKeysHandler 获取所有API Key列表 func (h *APIHandler) GetAPIKeysHandler(c *gin.Context) { var apiKeys []models.APIKey - + // 查询所有API Key if err := h.DB.Order("created_at DESC").Find(&apiKeys).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch API keys"}) return } - + // 转换为响应格式 response := make([]APIKeyListResponse, len(apiKeys)) for i, key := range apiKeys { @@ -585,7 +583,7 @@ func (h *APIHandler) GetAPIKeysHandler(c *gin.Context) { CreatedAt: key.CreatedAt, } } - + c.JSON(http.StatusOK, gin.H{ "api_keys": response, "total": len(response), @@ -600,36 +598,36 @@ type CreateAPIKeyRequest struct { // CreateAPIKeyHandler 创建新的API Key func (h *APIHandler) CreateAPIKeyHandler(c *gin.Context) { var req CreateAPIKeyRequest - + // 解析请求 if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request format: " + err.Error()}) return } - + // 验证Key不为空 if req.Key == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "API key cannot be empty"}) return } - + // 检查Key是否已存在 var existingKey models.APIKey if err := h.DB.Where("key = ?", req.Key).First(&existingKey).Error; err == nil { c.JSON(http.StatusConflict, gin.H{"error": "API key already exists"}) return } - + // 创建新的API Key newAPIKey := models.APIKey{ Key: req.Key, } - + if err := h.DB.Create(&newAPIKey).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create API key"}) return } - + // 返回创建的API Key c.JSON(http.StatusCreated, gin.H{ "message": "API key created successfully", @@ -649,7 +647,7 @@ func (h *APIHandler) DeleteAPIKeyHandler(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": "API key ID is required"}) return } - + // 查找API Key var apiKey models.APIKey if err := h.DB.First(&apiKey, keyID).Error; err != nil { @@ -660,13 +658,13 @@ func (h *APIHandler) DeleteAPIKeyHandler(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to find API key"}) return } - + // 删除API Key if err := h.DB.Delete(&apiKey).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete API key"}) return } - + c.JSON(http.StatusOK, gin.H{ "message": "API key deleted successfully", "id": apiKey.ID, diff --git a/backend/api/openai_handlers.go b/backend/api/openai_handlers.go index bdcd198..e24e57c 100644 --- a/backend/api/openai_handlers.go +++ b/backend/api/openai_handlers.go @@ -77,17 +77,7 @@ func (h *APIHandler) ChatCompletions(c *gin.Context) { if messagesRaw, ok := jsonBody["messages"].([]interface{}); ok { for _, msgRaw := range messagesRaw { if msgMap, ok := msgRaw.(map[string]interface{}); ok { - if reasoning, exists := msgMap["reasoning"]; exists { - // 如果 reasoning 是对象,提取 content 字段作为字符串 - if reasoningMap, ok := reasoning.(map[string]interface{}); ok { - if reasoningContent, ok := reasoningMap["content"].(string); ok { - msgMap["reasoning"] = reasoningContent - } else { - // 如果无法提取 content,移除 reasoning 字段 - delete(msgMap, "reasoning") - } - } - } + delete(msgMap, "reasoning") } } } @@ -138,8 +128,11 @@ func (h *APIHandler) ChatCompletions(c *gin.Context) { return } + log.Printf("接收到请求, 开始使用 %s 请求后端, 请求处理花费: %v", backendModel.Name, time.Since(requestTimestamp)) + // 构建后端API URL backendURL := backendModel.Provider.BaseURL + "/v1/chat/completions" + forwardRequestTimestamp := time.Now() // 转发请求到后端 resp, err := forwardRequest(backendURL, backendModel.Provider.ApiKey, requestBody, c.GetHeader("User-Agent")) @@ -153,18 +146,22 @@ func (h *APIHandler) ChatCompletions(c *gin.Context) { return } defer resp.Body.Close() - + // 记录响应时间 responseTimestamp := time.Now() + log.Printf("后端模型已经回应: %s, 后端响应时间: %v", backendModel.Name, time.Since(forwardRequestTimestamp)) // 获取API Key ID apiKeyID := getAPIKeyID(c) // 处理流式响应 if isStream { + streamTimestamp := time.Now() + log.Printf("后端模型开始流送: %s", backendModel.Name) handleStreamingResponse(c, resp, requestTimestamp, responseTimestamp, apiKeyID, modelName, backendModel, requestTokenCount, string(requestBody), h.DB) + log.Printf("流送请求处理完成, 模型: %s, 流送处理时间: %v", backendModel.Name, time.Since(streamTimestamp)) return } @@ -180,9 +177,6 @@ func (h *APIHandler) ChatCompletions(c *gin.Context) { return } - log.Printf("Backend Response Status: %s", resp.Status) - log.Printf("Backend Response Body: %s", string(responseBody)) - // 计算响应token数 responseTokenCount := extractResponseTokenCount(responseBody) @@ -195,6 +189,9 @@ func (h *APIHandler) ChatCompletions(c *gin.Context) { requestTimestamp, responseTimestamp, requestTokenCount, responseTokenCount, cost, string(requestBody), string(responseBody)) logger.LogRequest(h.DB, logEntry) + + // 记录处理时间 + log.Printf("请求处理完成, 模型: %s, 总时间: %v", backendModel.Name, time.Since(requestTimestamp)) // 复制响应头并返回响应 copyResponseHeaders(c, resp) @@ -225,25 +222,6 @@ func (h *APIHandler) ResponsesCompletions(c *gin.Context) { // 从 map 中提取需要的字段 modelName, _ := jsonBody["model"].(string) - // 处理 input 中的 reasoning 字段,将对象转换为字符串 - if inputRaw, ok := jsonBody["input"].([]interface{}); ok { - for _, msgRaw := range inputRaw { - if msgMap, ok := msgRaw.(map[string]interface{}); ok { - if reasoning, exists := msgMap["reasoning"]; exists { - // 如果 reasoning 是对象,提取 content 字段作为字符串 - if reasoningMap, ok := reasoning.(map[string]interface{}); ok { - if reasoningContent, ok := reasoningMap["content"].(string); ok { - msgMap["reasoning"] = reasoningContent - } else { - // 如果无法提取 content,移除 reasoning 字段 - delete(msgMap, "reasoning") - } - } - } - } - } - } - // 提取 input 并转换为计算 token 所需的格式 var messages []billing.ChatCompletionMessage if inputRaw, ok := jsonBody["input"].([]interface{}); ok { @@ -290,6 +268,8 @@ func (h *APIHandler) ResponsesCompletions(c *gin.Context) { return } + log.Printf("接收到请求, 开始使用: %s", backendModel.Name) + // 构建后端API URL backendURL := backendModel.Provider.BaseURL + "/v1/responses" @@ -308,6 +288,7 @@ func (h *APIHandler) ResponsesCompletions(c *gin.Context) { // 记录响应时间 responseTimestamp := time.Now() + log.Printf("后端模型已经回应: %s, 后端响应时间: %v", backendModel.Name, responseTimestamp.Sub(requestTimestamp)) // 读取响应体 responseBody, err := io.ReadAll(resp.Body) @@ -336,6 +317,10 @@ func (h *APIHandler) ResponsesCompletions(c *gin.Context) { requestTimestamp, responseTimestamp, requestTokenCount, responseTokenCount, cost, string(requestBody), string(responseBody)) logger.LogRequest(h.DB, logEntry) + + // 记录处理时间 + processingTime := responseTimestamp.Sub(requestTimestamp) + log.Printf("请求处理完成, 模型: %s, 处理时间: %v", backendModel.Name, processingTime) // 复制响应头并返回响应 copyResponseHeaders(c, resp) @@ -419,6 +404,8 @@ func (h *APIHandler) Embeddings(c *gin.Context) { return } + log.Printf("接收到请求, 开始使用: %s", backendModel.Name) + // 构建后端API URL backendURL := backendModel.Provider.BaseURL + "/v1/embeddings" @@ -437,6 +424,7 @@ func (h *APIHandler) Embeddings(c *gin.Context) { // 记录响应时间 responseTimestamp := time.Now() + log.Printf("后端模型已经回应: %s, 后端响应时间: %v", backendModel.Name, responseTimestamp.Sub(requestTimestamp)) // 读取响应体 responseBody, err := io.ReadAll(resp.Body) @@ -450,9 +438,6 @@ func (h *APIHandler) Embeddings(c *gin.Context) { return } - log.Printf("Backend Response Status: %s", resp.Status) - log.Printf("Backend Response Body: %s", string(responseBody)) - // 从响应中提取 token 使用量 responseTokenCount := extractEmbeddingsTokenCount(responseBody, requestTokenCount) @@ -468,6 +453,10 @@ func (h *APIHandler) Embeddings(c *gin.Context) { requestTimestamp, responseTimestamp, requestTokenCount, responseTokenCount, cost, string(requestBody), string(responseBody)) logger.LogRequest(h.DB, logEntry) + + // 记录处理时间 + processingTime := responseTimestamp.Sub(requestTimestamp) + log.Printf("请求处理完成, 模型: %s, 处理时间: %v", backendModel.Name, processingTime) // 复制响应头并返回响应 copyResponseHeaders(c, resp) diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index c08a15a..45e251f 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -41,6 +41,7 @@ type AppConfig struct { Version string `json:"version"` Environment string `json:"environment"` LogLevel string `json:"log_level"` + LogInDB bool `json:"log_in_db"` } // DefaultConfig 默认配置 @@ -51,10 +52,10 @@ func DefaultConfig() *Config { Host: "0.0.0.0", }, LogCleaner: scheduler.LogCleanerConfig{ - Enabled: true, // 默认启用自动清理 - ExecuteTime: "02:00", // 凌晨2点执行 - RetentionDays: 7, // 保留7天的日志 - CheckInterval: 5, // 每5分钟检查一次 + Enabled: true, // 默认启用自动清理 + ExecuteTime: "02:00", // 凌晨2点执行 + RetentionDays: 7, // 保留7天的日志 + CheckInterval: 5, // 每5分钟检查一次 }, App: AppConfig{ Name: "AI Gateway", @@ -165,4 +166,4 @@ func (c *Config) Print() { log.Printf(" 📱 应用名称: %s v%s", c.App.Name, c.App.Version) log.Printf(" 🌍 运行环境: %s", c.App.Environment) log.Printf(" 📝 日志级别: %s", c.App.LogLevel) -} \ No newline at end of file +} diff --git a/backend/internal/logger/logger.go b/backend/internal/logger/logger.go index 7e98ea0..af5af1c 100644 --- a/backend/internal/logger/logger.go +++ b/backend/internal/logger/logger.go @@ -7,11 +7,21 @@ import ( "gorm.io/gorm" ) +var saveRequestLog = false + // LogRequest 异步记录API请求日志 func LogRequest(db *gorm.DB, logEntry *models.RequestLog) { + if !saveRequestLog { + return + } + go func() { if err := db.Create(logEntry).Error; err != nil { log.Printf("Failed to save request log: %v", err) } }() -} \ No newline at end of file +} + +func SetSaveRequestLog(save bool) { + saveRequestLog = save +} diff --git a/backend/main.go b/backend/main.go index f0895e3..869bc3a 100644 --- a/backend/main.go +++ b/backend/main.go @@ -4,6 +4,7 @@ import ( "ai-gateway/api" "ai-gateway/internal/config" "ai-gateway/internal/db" + "ai-gateway/internal/logger" "ai-gateway/internal/middleware" "ai-gateway/internal/scheduler" "log" @@ -28,6 +29,8 @@ func main() { cfg = config.DefaultConfig() } + logger.SetSaveRequestLog(cfg.App.LogInDB) + // 初始化数据库连接(使用固定数据库路径) database, err := db.InitDB(config.DatabasePath) if err != nil { diff --git a/frontend/src/features/virtual-models/components/VirtualModelForm.jsx b/frontend/src/features/virtual-models/components/VirtualModelForm.jsx index 1067ef5..8c5de8b 100644 --- a/frontend/src/features/virtual-models/components/VirtualModelForm.jsx +++ b/frontend/src/features/virtual-models/components/VirtualModelForm.jsx @@ -41,9 +41,12 @@ const VirtualModelForm = ({ model, onSave, onCancel }) => { }, [model]); const handleBackendModelChange = (index, field, value) => { - const newBackendModels = [...backendModels]; - newBackendModels[index] = { ...newBackendModels[index], [field]: value }; - setBackendModels(newBackendModels); + // 使用函数式setState来确保获取最新的状态 + setBackendModels(prevModels => { + const newBackendModels = [...prevModels]; + newBackendModels[index] = { ...newBackendModels[index], [field]: value }; + return newBackendModels; + }); }; const addBackendModel = () => { diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 7795784..7fee411 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -12,7 +12,7 @@ export default defineConfig({ proxy: { '/api': { target: 'http://localhost:8080', // 后端服务器地址 - // target: 'http://10.1.39.104:9130', + // target: 'http://82.156.222.20:6233', changeOrigin: true, }, },