diff --git a/controller/channel.go b/controller/channel.go index acaf2977..13ed72b3 100644 --- a/controller/channel.go +++ b/controller/channel.go @@ -102,14 +102,14 @@ func GetAllChannels(c *gin.Context) { typeCounts, _ := model.CountChannelsGroupByType() c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": gin.H{ - "items": channelData, - "total": total, - "page": p, - "page_size": pageSize, - "type_counts": typeCounts, + "success": true, + "message": "", + "data": gin.H{ + "items": channelData, + "total": total, + "page": p, + "page_size": pageSize, + "type_counts": typeCounts, }, }) return @@ -237,10 +237,20 @@ func SearchChannels(c *gin.Context) { } channelData = channels } + + // calculate type counts for search results + typeCounts := make(map[int64]int64) + for _, channel := range channelData { + typeCounts[int64(channel.Type)]++ + } + c.JSON(http.StatusOK, gin.H{ "success": true, "message": "", - "data": channelData, + "data": gin.H{ + "items": channelData, + "type_counts": typeCounts, + }, }) return } diff --git a/model/main.go b/model/main.go index 965bba93..d46a21cf 100644 --- a/model/main.go +++ b/model/main.go @@ -46,6 +46,15 @@ func initCol() { logGroupCol = commonGroupCol logKeyCol = commonKeyCol } + } else { + // LOG_SQL_DSN 为空时,日志数据库与主数据库相同 + if common.UsingPostgreSQL { + logGroupCol = `"group"` + logKeyCol = `"key"` + } else { + logGroupCol = commonGroupCol + logKeyCol = commonKeyCol + } } // log sql type and database type common.SysLog("Using Log SQL Type: " + common.LogSqlType) diff --git a/model/utils.go b/model/utils.go index e6b09aa5..1f8a0963 100644 --- a/model/utils.go +++ b/model/utils.go @@ -2,11 +2,12 @@ package model import ( "errors" - "github.com/bytedance/gopkg/util/gopool" - "gorm.io/gorm" "one-api/common" "sync" "time" + + "github.com/bytedance/gopkg/util/gopool" + "gorm.io/gorm" ) const ( @@ -48,6 +49,22 @@ func addNewRecord(type_ int, id int, value int) { } func batchUpdate() { + // check if there's any data to update + hasData := false + for i := 0; i < BatchUpdateTypeCount; i++ { + batchUpdateLocks[i].Lock() + if len(batchUpdateStores[i]) > 0 { + hasData = true + batchUpdateLocks[i].Unlock() + break + } + batchUpdateLocks[i].Unlock() + } + + if !hasData { + return + } + common.SysLog("batch update started") for i := 0; i < BatchUpdateTypeCount; i++ { batchUpdateLocks[i].Lock() diff --git a/relay/channel/gemini/relay-gemini-native.go b/relay/channel/gemini/relay-gemini-native.go index c9a11aa6..bf87eafd 100644 --- a/relay/channel/gemini/relay-gemini-native.go +++ b/relay/channel/gemini/relay-gemini-native.go @@ -35,19 +35,6 @@ func GeminiTextGenerationHandler(c *gin.Context, resp *http.Response, info *rela return nil, service.OpenAIErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError) } - // 检查是否有候选响应 - if len(geminiResponse.Candidates) == 0 { - return nil, &dto.OpenAIErrorWithStatusCode{ - Error: dto.OpenAIError{ - Message: "No candidates returned", - Type: "server_error", - Param: "", - Code: 500, - }, - StatusCode: resp.StatusCode, - } - } - // 计算使用量(基于 UsageMetadata) usage := dto.Usage{ PromptTokens: geminiResponse.UsageMetadata.PromptTokenCount, diff --git a/relay/relay-gemini.go b/relay/relay-gemini.go index 9edbe5c2..80e5a694 100644 --- a/relay/relay-gemini.go +++ b/relay/relay-gemini.go @@ -165,8 +165,23 @@ func GeminiHelper(c *gin.Context) (openaiErr *dto.OpenAIErrorWithStatusCode) { return service.OpenAIErrorWrapperLocal(err, "do_request_failed", http.StatusInternalServerError) } + statusCodeMappingStr := c.GetString("status_code_mapping") + + var httpResp *http.Response + if resp != nil { + httpResp = resp.(*http.Response) + relayInfo.IsStream = relayInfo.IsStream || strings.HasPrefix(httpResp.Header.Get("Content-Type"), "text/event-stream") + if httpResp.StatusCode != http.StatusOK { + openaiErr = service.RelayErrorHandler(httpResp, false) + // reset status code 重置状态码 + service.ResetStatusCode(openaiErr, statusCodeMappingStr) + return openaiErr + } + } + usage, openaiErr := adaptor.DoResponse(c, resp.(*http.Response), relayInfo) if openaiErr != nil { + service.ResetStatusCode(openaiErr, statusCodeMappingStr) return openaiErr } diff --git a/web/src/pages/TopUp/index.js b/web/src/pages/TopUp/index.js index e327178d..a3a911cc 100644 --- a/web/src/pages/TopUp/index.js +++ b/web/src/pages/TopUp/index.js @@ -588,11 +588,10 @@ const TopUp = () => { selectPresetAmount(preset)} - className={`cursor-pointer !rounded-2xl transition-all hover:shadow-md ${ - selectedPreset === preset.value - ? 'border-blue-500' - : 'border-gray-200 hover:border-gray-300' - }`} + className={`cursor-pointer !rounded-2xl transition-all hover:shadow-md ${selectedPreset === preset.value + ? 'border-blue-500' + : 'border-gray-200 hover:border-gray-300' + }`} bodyStyle={{ textAlign: 'center' }} >
@@ -661,54 +660,139 @@ const TopUp = () => { />
-
- {/* - */} - {payMethods.map((payMethod) => ( - - ))} +
+ + {t('选择支付方式')} + + {payMethods.length === 2 ? ( +
+ {payMethods.map((payMethod) => ( + + ))} +
+ ) : payMethods.length === 3 ? ( +
+ {payMethods.map((payMethod) => ( + + ))} +
+ ) : payMethods.length > 3 ? ( +
+ {payMethods.map((payMethod) => ( + preTopUp(payMethod.type)} + disabled={!enableOnlineTopUp} + className={`cursor-pointer !rounded-xl p-0 transition-all hover:shadow-md ${paymentLoading && payWay === payMethod.type + ? 'border-blue-400' + : 'border-gray-200 hover:border-gray-300' + }`} + bodyStyle={{ + padding: '10px', + textAlign: 'center', + opacity: !enableOnlineTopUp ? 0.5 : 1 + }} + > + {paymentLoading && payWay === payMethod.type ? ( +
+
+
+
+
{t('处理中')}
+
+ ) : ( + <> +
+ {payMethod.type === 'zfb' ? ( + + ) : payMethod.type === 'wx' ? ( + + ) : ( + + )} +
+
{payMethod.name}
+ + )} +
+ ))} +
+ ) : ( +
+ {payMethods.map((payMethod) => ( + + ))} +
+ )}
@@ -941,48 +1025,71 @@ const TopUp = () => { /> -
- {/* - */} - {payMethods.map((payMethod) => ( - - ))} +
+ {payMethods.length === 2 ? ( +
+ {payMethods.map((payMethod) => ( + + ))} +
+ ) : ( +
+ {payMethods.map((payMethod) => ( + preTopUp(payMethod.type)} + disabled={!enableOnlineTopUp} + className={`cursor-pointer !rounded-xl p-0 transition-all ${paymentLoading && payWay === payMethod.type + ? 'border-blue-400' + : 'border-gray-200' + }`} + bodyStyle={{ + padding: '8px', + textAlign: 'center', + opacity: !enableOnlineTopUp ? 0.5 : 1 + }} + > + {paymentLoading && payWay === payMethod.type ? ( +
+ ) : ( + <> +
+ {payMethod.type === 'zfb' ? ( + + ) : payMethod.type === 'wx' ? ( + + ) : ( + + )} +
+
{payMethod.name}
+ + )} +
+ ))} +
+ )}