diff --git a/controller/channel.go b/controller/channel.go index 1cfb7906..acaf2977 100644 --- a/controller/channel.go +++ b/controller/channel.go @@ -52,6 +52,14 @@ func GetAllChannels(c *gin.Context) { channelData := make([]*model.Channel, 0) idSort, _ := strconv.ParseBool(c.Query("id_sort")) enableTagMode, _ := strconv.ParseBool(c.Query("tag_mode")) + // type filter + typeStr := c.Query("type") + typeFilter := -1 + if typeStr != "" { + if t, err := strconv.Atoi(typeStr); err == nil { + typeFilter = t + } + } var total int64 @@ -72,6 +80,14 @@ func GetAllChannels(c *gin.Context) { } // 计算 tag 总数用于分页 total, _ = model.CountAllTags() + } else if typeFilter >= 0 { + channels, err := model.GetChannelsByType((p-1)*pageSize, pageSize, idSort, typeFilter) + if err != nil { + c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()}) + return + } + channelData = channels + total, _ = model.CountChannelsByType(typeFilter) } else { channels, err := model.GetAllChannels((p-1)*pageSize, pageSize, false, idSort) if err != nil { @@ -82,14 +98,18 @@ func GetAllChannels(c *gin.Context) { total, _ = model.CountAllChannels() } + // calculate type counts + typeCounts, _ := model.CountChannelsGroupByType() + c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": gin.H{ - "items": channelData, - "total": total, - "page": p, - "page_size": pageSize, + "success": true, + "message": "", + "data": gin.H{ + "items": channelData, + "total": total, + "page": p, + "page_size": pageSize, + "type_counts": typeCounts, }, }) return diff --git a/controller/group.go b/controller/group.go index 632b6cd5..2565b6ea 100644 --- a/controller/group.go +++ b/controller/group.go @@ -4,13 +4,14 @@ import ( "net/http" "one-api/model" "one-api/setting" + "one-api/setting/ratio_setting" "github.com/gin-gonic/gin" ) func GetGroups(c *gin.Context) { groupNames := make([]string, 0) - for groupName, _ := range setting.GetGroupRatioCopy() { + for groupName := range ratio_setting.GetGroupRatioCopy() { groupNames = append(groupNames, groupName) } c.JSON(http.StatusOK, gin.H{ @@ -25,7 +26,7 @@ func GetUserGroups(c *gin.Context) { userGroup := "" userId := c.GetInt("id") userGroup, _ = model.GetUserGroup(userId, false) - for groupName, ratio := range setting.GetGroupRatioCopy() { + for groupName, ratio := range ratio_setting.GetGroupRatioCopy() { // UserUsableGroups contains the groups that the user can use userUsableGroups := setting.GetUserUsableGroups(userGroup) if desc, ok := userUsableGroups[groupName]; ok { diff --git a/controller/option.go b/controller/option.go index 79ba2ffe..97bb6a5a 100644 --- a/controller/option.go +++ b/controller/option.go @@ -7,6 +7,7 @@ import ( "one-api/model" "one-api/setting" "one-api/setting/console_setting" + "one-api/setting/ratio_setting" "one-api/setting/system_setting" "strings" @@ -103,7 +104,7 @@ func UpdateOption(c *gin.Context) { return } case "GroupRatio": - err = setting.CheckGroupRatio(option.Value) + err = ratio_setting.CheckGroupRatio(option.Value) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, diff --git a/controller/pricing.go b/controller/pricing.go index e6a3e57f..f27336b7 100644 --- a/controller/pricing.go +++ b/controller/pricing.go @@ -3,7 +3,7 @@ package controller import ( "one-api/model" "one-api/setting" - "one-api/setting/operation_setting" + "one-api/setting/ratio_setting" "github.com/gin-gonic/gin" ) @@ -13,7 +13,7 @@ func GetPricing(c *gin.Context) { userId, exists := c.Get("id") usableGroup := map[string]string{} groupRatio := map[string]float64{} - for s, f := range setting.GetGroupRatioCopy() { + for s, f := range ratio_setting.GetGroupRatioCopy() { groupRatio[s] = f } var group string @@ -22,7 +22,7 @@ func GetPricing(c *gin.Context) { if err == nil { group = user.Group for g := range groupRatio { - ratio, ok := setting.GetGroupGroupRatio(group, g) + ratio, ok := ratio_setting.GetGroupGroupRatio(group, g) if ok { groupRatio[g] = ratio } @@ -32,7 +32,7 @@ func GetPricing(c *gin.Context) { usableGroup = setting.GetUserUsableGroups(group) // check groupRatio contains usableGroup - for group := range setting.GetGroupRatioCopy() { + for group := range ratio_setting.GetGroupRatioCopy() { if _, ok := usableGroup[group]; !ok { delete(groupRatio, group) } @@ -47,7 +47,7 @@ func GetPricing(c *gin.Context) { } func ResetModelRatio(c *gin.Context) { - defaultStr := operation_setting.DefaultModelRatio2JSONString() + defaultStr := ratio_setting.DefaultModelRatio2JSONString() err := model.UpdateOption("ModelRatio", defaultStr) if err != nil { c.JSON(200, gin.H{ @@ -56,7 +56,7 @@ func ResetModelRatio(c *gin.Context) { }) return } - err = operation_setting.UpdateModelRatioByJSONString(defaultStr) + err = ratio_setting.UpdateModelRatioByJSONString(defaultStr) if err != nil { c.JSON(200, gin.H{ "success": false, diff --git a/main.go b/main.go index 30ba8092..cf593b57 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,7 @@ import ( "one-api/model" "one-api/router" "one-api/service" - "one-api/setting/operation_setting" + "one-api/setting/ratio_setting" "os" "strconv" @@ -74,7 +74,7 @@ func main() { } // Initialize model settings - operation_setting.InitRatioSettings() + ratio_setting.InitRatioSettings() // Initialize constants constant.InitEnv() // Initialize options diff --git a/middleware/distributor.go b/middleware/distributor.go index 5d1c3641..84eb182e 100644 --- a/middleware/distributor.go +++ b/middleware/distributor.go @@ -11,6 +11,7 @@ import ( relayconstant "one-api/relay/constant" "one-api/service" "one-api/setting" + "one-api/setting/ratio_setting" "strconv" "strings" "time" @@ -48,7 +49,7 @@ func Distribute() func(c *gin.Context) { return } // check group in common.GroupRatio - if !setting.ContainsGroupRatio(tokenGroup) { + if !ratio_setting.ContainsGroupRatio(tokenGroup) { if tokenGroup != "auto" { abortWithOpenAiMessage(c, http.StatusForbidden, fmt.Sprintf("分组 %s 已被弃用", tokenGroup)) return diff --git a/model/channel.go b/model/channel.go index b5503eee..6cbd8adc 100644 --- a/model/channel.go +++ b/model/channel.go @@ -597,3 +597,39 @@ func CountAllTags() (int64, error) { err := DB.Model(&Channel{}).Where("tag is not null AND tag != ''").Distinct("tag").Count(&total).Error return total, err } + +// Get channels of specified type with pagination +func GetChannelsByType(startIdx int, num int, idSort bool, channelType int) ([]*Channel, error) { + var channels []*Channel + order := "priority desc" + if idSort { + order = "id desc" + } + err := DB.Where("type = ?", channelType).Order(order).Limit(num).Offset(startIdx).Omit("key").Find(&channels).Error + return channels, err +} + +// Count channels of specific type +func CountChannelsByType(channelType int) (int64, error) { + var count int64 + err := DB.Model(&Channel{}).Where("type = ?", channelType).Count(&count).Error + return count, err +} + +// Return map[type]count for all channels +func CountChannelsGroupByType() (map[int64]int64, error) { + type result struct { + Type int64 `gorm:"column:type"` + Count int64 `gorm:"column:count"` + } + var results []result + err := DB.Model(&Channel{}).Select("type, count(*) as count").Group("type").Find(&results).Error + if err != nil { + return nil, err + } + counts := make(map[int64]int64) + for _, r := range results { + counts[r.Type] = r.Count + } + return counts, nil +} diff --git a/model/option.go b/model/option.go index 1391b203..43c0a644 100644 --- a/model/option.go +++ b/model/option.go @@ -5,6 +5,7 @@ import ( "one-api/setting" "one-api/setting/config" "one-api/setting/operation_setting" + "one-api/setting/ratio_setting" "strconv" "strings" "time" @@ -96,13 +97,13 @@ func InitOptionMap() { common.OptionMap["ModelRequestRateLimitDurationMinutes"] = strconv.Itoa(setting.ModelRequestRateLimitDurationMinutes) common.OptionMap["ModelRequestRateLimitSuccessCount"] = strconv.Itoa(setting.ModelRequestRateLimitSuccessCount) common.OptionMap["ModelRequestRateLimitGroup"] = setting.ModelRequestRateLimitGroup2JSONString() - common.OptionMap["ModelRatio"] = operation_setting.ModelRatio2JSONString() - common.OptionMap["ModelPrice"] = operation_setting.ModelPrice2JSONString() - common.OptionMap["CacheRatio"] = operation_setting.CacheRatio2JSONString() - common.OptionMap["GroupRatio"] = setting.GroupRatio2JSONString() - common.OptionMap["GroupGroupRatio"] = setting.GroupGroupRatio2JSONString() + common.OptionMap["ModelRatio"] = ratio_setting.ModelRatio2JSONString() + common.OptionMap["ModelPrice"] = ratio_setting.ModelPrice2JSONString() + common.OptionMap["CacheRatio"] = ratio_setting.CacheRatio2JSONString() + common.OptionMap["GroupRatio"] = ratio_setting.GroupRatio2JSONString() + common.OptionMap["GroupGroupRatio"] = ratio_setting.GroupGroupRatio2JSONString() common.OptionMap["UserUsableGroups"] = setting.UserUsableGroups2JSONString() - common.OptionMap["CompletionRatio"] = operation_setting.CompletionRatio2JSONString() + common.OptionMap["CompletionRatio"] = ratio_setting.CompletionRatio2JSONString() common.OptionMap["TopUpLink"] = common.TopUpLink //common.OptionMap["ChatLink"] = common.ChatLink //common.OptionMap["ChatLink2"] = common.ChatLink2 @@ -358,19 +359,19 @@ func updateOptionMap(key string, value string) (err error) { case "DataExportDefaultTime": common.DataExportDefaultTime = value case "ModelRatio": - err = operation_setting.UpdateModelRatioByJSONString(value) + err = ratio_setting.UpdateModelRatioByJSONString(value) case "GroupRatio": - err = setting.UpdateGroupRatioByJSONString(value) + err = ratio_setting.UpdateGroupRatioByJSONString(value) case "GroupGroupRatio": - err = setting.UpdateGroupGroupRatioByJSONString(value) + err = ratio_setting.UpdateGroupGroupRatioByJSONString(value) case "UserUsableGroups": err = setting.UpdateUserUsableGroupsByJSONString(value) case "CompletionRatio": - err = operation_setting.UpdateCompletionRatioByJSONString(value) + err = ratio_setting.UpdateCompletionRatioByJSONString(value) case "ModelPrice": - err = operation_setting.UpdateModelPriceByJSONString(value) + err = ratio_setting.UpdateModelPriceByJSONString(value) case "CacheRatio": - err = operation_setting.UpdateCacheRatioByJSONString(value) + err = ratio_setting.UpdateCacheRatioByJSONString(value) case "TopUpLink": common.TopUpLink = value //case "ChatLink": diff --git a/model/pricing.go b/model/pricing.go index ba1815e2..74a25f2d 100644 --- a/model/pricing.go +++ b/model/pricing.go @@ -2,7 +2,7 @@ package model import ( "one-api/common" - "one-api/setting/operation_setting" + "one-api/setting/ratio_setting" "sync" "time" ) @@ -65,14 +65,14 @@ func updatePricing() { ModelName: model, EnableGroup: groups, } - modelPrice, findPrice := operation_setting.GetModelPrice(model, false) + modelPrice, findPrice := ratio_setting.GetModelPrice(model, false) if findPrice { pricing.ModelPrice = modelPrice pricing.QuotaType = 1 } else { - modelRatio, _ := operation_setting.GetModelRatio(model) + modelRatio, _ := ratio_setting.GetModelRatio(model) pricing.ModelRatio = modelRatio - pricing.CompletionRatio = operation_setting.GetCompletionRatio(model) + pricing.CompletionRatio = ratio_setting.GetCompletionRatio(model) pricing.QuotaType = 0 } pricingMap = append(pricingMap, pricing) diff --git a/relay/helper/price.go b/relay/helper/price.go index 326790b4..0d32c61a 100644 --- a/relay/helper/price.go +++ b/relay/helper/price.go @@ -5,8 +5,7 @@ import ( "one-api/common" constant2 "one-api/constant" relaycommon "one-api/relay/common" - "one-api/setting" - "one-api/setting/operation_setting" + "one-api/setting/ratio_setting" "github.com/gin-gonic/gin" ) @@ -49,21 +48,21 @@ func HandleGroupRatio(ctx *gin.Context, relayInfo *relaycommon.RelayInfo) GroupR } // check user group special ratio - userGroupRatio, ok := setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group) + userGroupRatio, ok := ratio_setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group) if ok { // user group special ratio groupRatioInfo.GroupSpecialRatio = userGroupRatio groupRatioInfo.GroupRatio = userGroupRatio } else { // normal group ratio - groupRatioInfo.GroupRatio = setting.GetGroupRatio(relayInfo.Group) + groupRatioInfo.GroupRatio = ratio_setting.GetGroupRatio(relayInfo.Group) } return groupRatioInfo } func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens int, maxTokens int) (PriceData, error) { - modelPrice, usePrice := operation_setting.GetModelPrice(info.OriginModelName, false) + modelPrice, usePrice := ratio_setting.GetModelPrice(info.OriginModelName, false) groupRatioInfo := HandleGroupRatio(c, info) @@ -79,7 +78,7 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens preConsumedTokens = promptTokens + maxTokens } var success bool - modelRatio, success = operation_setting.GetModelRatio(info.OriginModelName) + modelRatio, success = ratio_setting.GetModelRatio(info.OriginModelName) if !success { acceptUnsetRatio := false if accept, ok := info.UserSetting[constant2.UserAcceptUnsetRatioModel]; ok { @@ -92,10 +91,10 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens return PriceData{}, fmt.Errorf("模型 %s 倍率或价格未配置,请联系管理员设置或开始自用模式;Model %s ratio or price not set, please set or start self-use mode", info.OriginModelName, info.OriginModelName) } } - completionRatio = operation_setting.GetCompletionRatio(info.OriginModelName) - cacheRatio, _ = operation_setting.GetCacheRatio(info.OriginModelName) - cacheCreationRatio, _ = operation_setting.GetCreateCacheRatio(info.OriginModelName) - imageRatio, _ = operation_setting.GetImageRatio(info.OriginModelName) + completionRatio = ratio_setting.GetCompletionRatio(info.OriginModelName) + cacheRatio, _ = ratio_setting.GetCacheRatio(info.OriginModelName) + cacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(info.OriginModelName) + imageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName) ratio := modelRatio * groupRatioInfo.GroupRatio preConsumedQuota = int(float64(preConsumedTokens) * ratio) } else { @@ -122,11 +121,11 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens } func ContainPriceOrRatio(modelName string) bool { - _, ok := operation_setting.GetModelPrice(modelName, false) + _, ok := ratio_setting.GetModelPrice(modelName, false) if ok { return true } - _, ok = operation_setting.GetModelRatio(modelName) + _, ok = ratio_setting.GetModelRatio(modelName) if ok { return true } diff --git a/relay/relay-mj.go b/relay/relay-mj.go index 9d0a2077..ce4346b6 100644 --- a/relay/relay-mj.go +++ b/relay/relay-mj.go @@ -15,7 +15,7 @@ import ( relayconstant "one-api/relay/constant" "one-api/service" "one-api/setting" - "one-api/setting/operation_setting" + "one-api/setting/ratio_setting" "strconv" "strings" "time" @@ -174,17 +174,17 @@ func RelaySwapFace(c *gin.Context) *dto.MidjourneyResponse { return service.MidjourneyErrorWrapper(constant.MjRequestError, "sour_base64_and_target_base64_is_required") } modelName := service.CoverActionToModelName(constant.MjActionSwapFace) - modelPrice, success := operation_setting.GetModelPrice(modelName, true) + modelPrice, success := ratio_setting.GetModelPrice(modelName, true) // 如果没有配置价格,则使用默认价格 if !success { - defaultPrice, ok := operation_setting.GetDefaultModelRatioMap()[modelName] + defaultPrice, ok := ratio_setting.GetDefaultModelRatioMap()[modelName] if !ok { modelPrice = 0.1 } else { modelPrice = defaultPrice } } - groupRatio := setting.GetGroupRatio(group) + groupRatio := ratio_setting.GetGroupRatio(group) ratio := modelPrice * groupRatio userQuota, err := model.GetUserQuota(userId, false) if err != nil { @@ -480,17 +480,17 @@ func RelayMidjourneySubmit(c *gin.Context, relayMode int) *dto.MidjourneyRespons fullRequestURL := fmt.Sprintf("%s%s", baseURL, requestURL) modelName := service.CoverActionToModelName(midjRequest.Action) - modelPrice, success := operation_setting.GetModelPrice(modelName, true) + modelPrice, success := ratio_setting.GetModelPrice(modelName, true) // 如果没有配置价格,则使用默认价格 if !success { - defaultPrice, ok := operation_setting.GetDefaultModelRatioMap()[modelName] + defaultPrice, ok := ratio_setting.GetDefaultModelRatioMap()[modelName] if !ok { modelPrice = 0.1 } else { modelPrice = defaultPrice } } - groupRatio := setting.GetGroupRatio(group) + groupRatio := ratio_setting.GetGroupRatio(group) ratio := modelPrice * groupRatio userQuota, err := model.GetUserQuota(userId, false) if err != nil { diff --git a/relay/relay_task.go b/relay/relay_task.go index 26874ba6..3da9a20f 100644 --- a/relay/relay_task.go +++ b/relay/relay_task.go @@ -15,8 +15,7 @@ import ( relaycommon "one-api/relay/common" relayconstant "one-api/relay/constant" "one-api/service" - "one-api/setting" - "one-api/setting/operation_setting" + "one-api/setting/ratio_setting" ) /* @@ -38,9 +37,9 @@ func RelayTaskSubmit(c *gin.Context, relayMode int) (taskErr *dto.TaskError) { } modelName := service.CoverTaskActionToModelName(platform, relayInfo.Action) - modelPrice, success := operation_setting.GetModelPrice(modelName, true) + modelPrice, success := ratio_setting.GetModelPrice(modelName, true) if !success { - defaultPrice, ok := operation_setting.GetDefaultModelRatioMap()[modelName] + defaultPrice, ok := ratio_setting.GetDefaultModelRatioMap()[modelName] if !ok { modelPrice = 0.1 } else { @@ -49,7 +48,7 @@ func RelayTaskSubmit(c *gin.Context, relayMode int) (taskErr *dto.TaskError) { } // 预扣 - groupRatio := setting.GetGroupRatio(relayInfo.Group) + groupRatio := ratio_setting.GetGroupRatio(relayInfo.Group) ratio := modelPrice * groupRatio userQuota, err := model.GetUserQuota(relayInfo.UserId, false) if err != nil { diff --git a/service/quota.go b/service/quota.go index 0fb9e67c..973deba7 100644 --- a/service/quota.go +++ b/service/quota.go @@ -11,7 +11,7 @@ import ( relaycommon "one-api/relay/common" "one-api/relay/helper" "one-api/setting" - "one-api/setting/operation_setting" + "one-api/setting/ratio_setting" "strings" "time" @@ -46,9 +46,9 @@ func calculateAudioQuota(info QuotaInfo) int { return int(quota.IntPart()) } - completionRatio := decimal.NewFromFloat(operation_setting.GetCompletionRatio(info.ModelName)) - audioRatio := decimal.NewFromFloat(operation_setting.GetAudioRatio(info.ModelName)) - audioCompletionRatio := decimal.NewFromFloat(operation_setting.GetAudioCompletionRatio(info.ModelName)) + completionRatio := decimal.NewFromFloat(ratio_setting.GetCompletionRatio(info.ModelName)) + audioRatio := decimal.NewFromFloat(ratio_setting.GetAudioRatio(info.ModelName)) + audioCompletionRatio := decimal.NewFromFloat(ratio_setting.GetAudioCompletionRatio(info.ModelName)) groupRatio := decimal.NewFromFloat(info.GroupRatio) modelRatio := decimal.NewFromFloat(info.ModelRatio) @@ -94,18 +94,18 @@ func PreWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usag textOutTokens := usage.OutputTokenDetails.TextTokens audioInputTokens := usage.InputTokenDetails.AudioTokens audioOutTokens := usage.OutputTokenDetails.AudioTokens - groupRatio := setting.GetGroupRatio(relayInfo.Group) - modelRatio, _ := operation_setting.GetModelRatio(modelName) + groupRatio := ratio_setting.GetGroupRatio(relayInfo.Group) + modelRatio, _ := ratio_setting.GetModelRatio(modelName) autoGroup, exists := ctx.Get("auto_group") if exists { - groupRatio = setting.GetGroupRatio(autoGroup.(string)) + groupRatio = ratio_setting.GetGroupRatio(autoGroup.(string)) log.Printf("final group ratio: %f", groupRatio) relayInfo.Group = autoGroup.(string) } actualGroupRatio := groupRatio - userGroupRatio, ok := setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group) + userGroupRatio, ok := ratio_setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group) if ok { actualGroupRatio = userGroupRatio } @@ -154,9 +154,9 @@ func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, mod audioOutTokens := usage.OutputTokenDetails.AudioTokens tokenName := ctx.GetString("token_name") - completionRatio := decimal.NewFromFloat(operation_setting.GetCompletionRatio(modelName)) - audioRatio := decimal.NewFromFloat(operation_setting.GetAudioRatio(relayInfo.OriginModelName)) - audioCompletionRatio := decimal.NewFromFloat(operation_setting.GetAudioCompletionRatio(modelName)) + completionRatio := decimal.NewFromFloat(ratio_setting.GetCompletionRatio(modelName)) + audioRatio := decimal.NewFromFloat(ratio_setting.GetAudioRatio(relayInfo.OriginModelName)) + audioCompletionRatio := decimal.NewFromFloat(ratio_setting.GetAudioCompletionRatio(modelName)) modelRatio := priceData.ModelRatio groupRatio := priceData.GroupRatioInfo.GroupRatio @@ -289,9 +289,9 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, audioOutTokens := usage.CompletionTokenDetails.AudioTokens tokenName := ctx.GetString("token_name") - completionRatio := decimal.NewFromFloat(operation_setting.GetCompletionRatio(relayInfo.OriginModelName)) - audioRatio := decimal.NewFromFloat(operation_setting.GetAudioRatio(relayInfo.OriginModelName)) - audioCompletionRatio := decimal.NewFromFloat(operation_setting.GetAudioCompletionRatio(relayInfo.OriginModelName)) + completionRatio := decimal.NewFromFloat(ratio_setting.GetCompletionRatio(relayInfo.OriginModelName)) + audioRatio := decimal.NewFromFloat(ratio_setting.GetAudioRatio(relayInfo.OriginModelName)) + audioCompletionRatio := decimal.NewFromFloat(ratio_setting.GetAudioCompletionRatio(relayInfo.OriginModelName)) modelRatio := priceData.ModelRatio groupRatio := priceData.GroupRatioInfo.GroupRatio diff --git a/setting/operation_setting/cache_ratio.go b/setting/ratio_setting/cache_ratio.go similarity index 99% rename from setting/operation_setting/cache_ratio.go rename to setting/ratio_setting/cache_ratio.go index ec0c766d..aa934b22 100644 --- a/setting/operation_setting/cache_ratio.go +++ b/setting/ratio_setting/cache_ratio.go @@ -1,4 +1,4 @@ -package operation_setting +package ratio_setting import ( "encoding/json" diff --git a/setting/group_ratio.go b/setting/ratio_setting/group_ratio.go similarity index 99% rename from setting/group_ratio.go rename to setting/ratio_setting/group_ratio.go index 28dbd167..f600a7b5 100644 --- a/setting/group_ratio.go +++ b/setting/ratio_setting/group_ratio.go @@ -1,4 +1,4 @@ -package setting +package ratio_setting import ( "encoding/json" diff --git a/setting/operation_setting/model-ratio.go b/setting/ratio_setting/model_ratio.go similarity index 99% rename from setting/operation_setting/model-ratio.go rename to setting/ratio_setting/model_ratio.go index 5155b2fc..3102dfe9 100644 --- a/setting/operation_setting/model-ratio.go +++ b/setting/ratio_setting/model_ratio.go @@ -1,8 +1,9 @@ -package operation_setting +package ratio_setting import ( "encoding/json" "one-api/common" + "one-api/setting/operation_setting" "strings" "sync" ) @@ -366,7 +367,7 @@ func GetModelRatio(name string) (float64, bool) { } ratio, ok := modelRatioMap[name] if !ok { - return 37.5, SelfUseModeEnabled + return 37.5, operation_setting.SelfUseModeEnabled } return ratio, true } diff --git a/web/src/components/settings/OperationSetting.js b/web/src/components/settings/OperationSetting.js index 7bd9bf62..f6786f95 100644 --- a/web/src/components/settings/OperationSetting.js +++ b/web/src/components/settings/OperationSetting.js @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { Card, Spin, Tabs } from '@douyinfe/semi-ui'; +import { Card, Spin } from '@douyinfe/semi-ui'; import SettingsGeneral from '../../pages/Setting/Operation/SettingsGeneral.js'; import SettingsDrawing from '../../pages/Setting/Operation/SettingsDrawing.js'; import SettingsSensitiveWords from '../../pages/Setting/Operation/SettingsSensitiveWords.js'; @@ -7,63 +7,58 @@ import SettingsLog from '../../pages/Setting/Operation/SettingsLog.js'; import SettingsDataDashboard from '../../pages/Setting/Operation/SettingsDataDashboard.js'; import SettingsMonitoring from '../../pages/Setting/Operation/SettingsMonitoring.js'; import SettingsCreditLimit from '../../pages/Setting/Operation/SettingsCreditLimit.js'; -import ModelSettingsVisualEditor from '../../pages/Setting/Operation/ModelSettingsVisualEditor.js'; -import GroupRatioSettings from '../../pages/Setting/Operation/GroupRatioSettings.js'; -import ModelRatioSettings from '../../pages/Setting/Operation/ModelRatioSettings.js'; - -import { API, showError, showSuccess } from '../../helpers'; import SettingsChats from '../../pages/Setting/Operation/SettingsChats.js'; -import { useTranslation } from 'react-i18next'; -import ModelRatioNotSetEditor from '../../pages/Setting/Operation/ModelRationNotSetEditor.js'; +import { API, showError } from '../../helpers'; const OperationSetting = () => { - const { t } = useTranslation(); let [inputs, setInputs] = useState({ + /* 额度相关 */ QuotaForNewUser: 0, + PreConsumedQuota: 0, QuotaForInviter: 0, QuotaForInvitee: 0, - QuotaRemindThreshold: 0, - PreConsumedQuota: 0, - StreamCacheQueueLength: 0, - ModelRatio: '', - CacheRatio: '', - CompletionRatio: '', - ModelPrice: '', - GroupRatio: '', - GroupGroupRatio: '', - AutoGroups: '', - DefaultUseAutoGroup: false, - UserUsableGroups: '', + + /* 通用设置 */ TopUpLink: '', 'general_setting.docs_link': '', - // ChatLink2: '', // 添加的新状态变量 QuotaPerUnit: 0, - AutomaticDisableChannelEnabled: false, - AutomaticEnableChannelEnabled: false, - ChannelDisableThreshold: 0, - LogConsumeEnabled: false, + RetryTimes: 0, DisplayInCurrencyEnabled: false, DisplayTokenStatEnabled: false, - CheckSensitiveEnabled: false, - CheckSensitiveOnPromptEnabled: false, - CheckSensitiveOnCompletionEnabled: '', - StopOnSensitiveEnabled: '', - SensitiveWords: '', + DefaultCollapseSidebar: false, + DemoSiteEnabled: false, + SelfUseModeEnabled: false, + + /* 绘图设置 */ + DrawingEnabled: false, MjNotifyEnabled: false, MjAccountFilterEnabled: false, - MjModeClearEnabled: false, MjForwardUrlEnabled: false, + MjModeClearEnabled: false, MjActionCheckSuccessEnabled: false, - DrawingEnabled: false, + + /* 敏感词设置 */ + CheckSensitiveEnabled: false, + CheckSensitiveOnPromptEnabled: false, + SensitiveWords: '', + + /* 日志设置 */ + LogConsumeEnabled: false, + + /* 数据看板 */ DataExportEnabled: false, DataExportDefaultTime: 'hour', DataExportInterval: 5, - DefaultCollapseSidebar: false, // 默认折叠侧边栏 - RetryTimes: 0, - Chats: '[]', - DemoSiteEnabled: false, - SelfUseModeEnabled: false, + + /* 监控设置 */ + ChannelDisableThreshold: 0, + QuotaRemindThreshold: 0, + AutomaticDisableChannelEnabled: false, + AutomaticEnableChannelEnabled: false, AutomaticDisableKeywords: '', + + /* 聊天设置 */ + Chats: '[]', }); let [loading, setLoading] = useState(false); @@ -74,22 +69,9 @@ const OperationSetting = () => { if (success) { let newInputs = {}; data.forEach((item) => { - if ( - item.key === 'ModelRatio' || - item.key === 'GroupRatio' || - item.key === 'GroupGroupRatio' || - item.key === 'AutoGroups' || - item.key === 'UserUsableGroups' || - item.key === 'CompletionRatio' || - item.key === 'ModelPrice' || - item.key === 'CacheRatio' - ) { - item.value = JSON.stringify(JSON.parse(item.value), null, 2); - } if ( item.key.endsWith('Enabled') || - ['DefaultCollapseSidebar'].includes(item.key) || - ['DefaultUseAutoGroup'].includes(item.key) + ['DefaultCollapseSidebar'].includes(item.key) ) { newInputs[item.key] = item.value === 'true' ? true : false; } else { @@ -153,24 +135,6 @@ const OperationSetting = () => { - {/* 分组倍率设置 */} - - - - {/* 合并模型倍率设置和可视化倍率设置 */} - - - - - - - - - - - - - ); diff --git a/web/src/components/settings/RatioSetting.js b/web/src/components/settings/RatioSetting.js new file mode 100644 index 00000000..bf97282c --- /dev/null +++ b/web/src/components/settings/RatioSetting.js @@ -0,0 +1,109 @@ +import React, { useEffect, useState } from 'react'; +import { Card, Spin, Tabs } from '@douyinfe/semi-ui'; +import { useTranslation } from 'react-i18next'; + +import GroupRatioSettings from '../../pages/Setting/Ratio/GroupRatioSettings.js'; +import ModelRatioSettings from '../../pages/Setting/Ratio/ModelRatioSettings.js'; +import ModelSettingsVisualEditor from '../../pages/Setting/Ratio/ModelSettingsVisualEditor.js'; +import ModelRatioNotSetEditor from '../../pages/Setting/Ratio/ModelRationNotSetEditor.js'; + +import { API, showError } from '../../helpers'; + +const RatioSetting = () => { + const { t } = useTranslation(); + + let [inputs, setInputs] = useState({ + ModelPrice: '', + ModelRatio: '', + CacheRatio: '', + CompletionRatio: '', + GroupRatio: '', + GroupGroupRatio: '', + AutoGroups: '', + DefaultUseAutoGroup: false, + UserUsableGroups: '', + }); + + const [loading, setLoading] = useState(false); + + const getOptions = async () => { + const res = await API.get('/api/option/'); + const { success, message, data } = res.data; + if (success) { + let newInputs = {}; + data.forEach((item) => { + if ( + item.key === 'ModelRatio' || + item.key === 'GroupRatio' || + item.key === 'GroupGroupRatio' || + item.key === 'AutoGroups' || + item.key === 'UserUsableGroups' || + item.key === 'CompletionRatio' || + item.key === 'ModelPrice' || + item.key === 'CacheRatio' + ) { + try { + item.value = JSON.stringify(JSON.parse(item.value), null, 2); + } catch (e) { + // 如果后端返回的不是合法 JSON,直接展示 + } + } + if (['DefaultUseAutoGroup'].includes(item.key)) { + newInputs[item.key] = item.value === 'true' ? true : false; + } else { + newInputs[item.key] = item.value; + } + }); + setInputs(newInputs); + } else { + showError(message); + } + }; + + const onRefresh = async () => { + try { + setLoading(true); + await getOptions(); + } catch (error) { + showError('刷新失败'); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + onRefresh(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + {/* 分组倍率设置 */} + + + + {/* 模型倍率设置以及可视化编辑器 */} + + + + + + + + + + + + + + + ); +}; + +export default RatioSetting; \ No newline at end of file diff --git a/web/src/components/table/ChannelsTable.js b/web/src/components/table/ChannelsTable.js index f5a78490..90921460 100644 --- a/web/src/components/table/ChannelsTable.js +++ b/web/src/components/table/ChannelsTable.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useMemo, useRef } from 'react'; import { API, showError, @@ -16,11 +16,6 @@ import { XCircle, AlertCircle, HelpCircle, - TestTube, - Zap, - Timer, - Clock, - AlertTriangle, Coins, Tags } from 'lucide-react'; @@ -43,7 +38,9 @@ import { Typography, Checkbox, Card, - Form + Form, + Tabs, + TabPane } from '@douyinfe/semi-ui'; import { IllustrationNoResult, @@ -141,31 +138,31 @@ const ChannelsTable = () => { time = time.toFixed(2) + t(' 秒'); if (responseTime === 0) { return ( - }> + {t('未测试')} ); } else if (responseTime <= 1000) { return ( - }> + {time} ); } else if (responseTime <= 3000) { return ( - }> + {time} ); } else if (responseTime <= 5000) { return ( - }> + {time} ); } else { return ( - }> + {time} ); @@ -682,11 +679,10 @@ const ChannelsTable = () => { const [isBatchTesting, setIsBatchTesting] = useState(false); const [testQueue, setTestQueue] = useState([]); const [isProcessingQueue, setIsProcessingQueue] = useState(false); - - // Form API 引用 + const [activeTypeKey, setActiveTypeKey] = useState('all'); + const [typeCounts, setTypeCounts] = useState({}); + const requestCounter = useRef(0); const [formApi, setFormApi] = useState(null); - - // Form 初始值 const formInitValues = { searchKeyword: '', searchGroup: '', @@ -868,17 +864,23 @@ const ChannelsTable = () => { setChannels(channelDates); }; - const loadChannels = async (page, pageSize, idSort, enableTagMode) => { + const loadChannels = async (page, pageSize, idSort, enableTagMode, typeKey = activeTypeKey) => { + const reqId = ++requestCounter.current; // 记录当前请求序号 setLoading(true); + const typeParam = (!enableTagMode && typeKey !== 'all') ? `&type=${typeKey}` : ''; const res = await API.get( - `/api/channel/?p=${page}&page_size=${pageSize}&id_sort=${idSort}&tag_mode=${enableTagMode}`, + `/api/channel/?p=${page}&page_size=${pageSize}&id_sort=${idSort}&tag_mode=${enableTagMode}${typeParam}`, ); - if (res === undefined) { + if (res === undefined || reqId !== requestCounter.current) { return; } const { success, message, data } = res.data; if (success) { - const { items, total } = data; + const { items, total, type_counts } = data; + if (type_counts) { + const sumAll = Object.values(type_counts).reduce((acc, v) => acc + v, 0); + setTypeCounts({ ...type_counts, all: sumAll }); + } setChannelFormat(items, enableTagMode); setChannelCount(total); } else { @@ -1044,12 +1046,16 @@ const ChannelsTable = () => { return; } + const typeParam = (!enableTagMode && activeTypeKey !== 'all') ? `&type=${activeTypeKey}` : ''; const res = await API.get( - `/api/channel/search?keyword=${searchKeyword}&group=${searchGroup}&model=${searchModel}&id_sort=${idSort}&tag_mode=${enableTagMode}`, + `/api/channel/search?keyword=${searchKeyword}&group=${searchGroup}&model=${searchModel}&id_sort=${idSort}&tag_mode=${enableTagMode}${typeParam}`, ); const { success, message, data } = res.data; if (success) { - setChannelFormat(data, enableTagMode); + const { items = [], type_counts = {} } = data; + const sumAll = Object.values(type_counts).reduce((acc, v) => acc + v, 0); + setTypeCounts({ ...type_counts, all: sumAll }); + setChannelFormat(items, enableTagMode); setActivePage(1); } else { showError(message); @@ -1179,7 +1185,94 @@ const ChannelsTable = () => { } }; + const channelTypeCounts = useMemo(() => { + if (Object.keys(typeCounts).length > 0) return typeCounts; + // fallback 本地计算 + const counts = { all: channels.length }; + channels.forEach((channel) => { + const collect = (ch) => { + const type = ch.type; + counts[type] = (counts[type] || 0) + 1; + }; + if (channel.children !== undefined) { + channel.children.forEach(collect); + } else { + collect(channel); + } + }); + return counts; + }, [typeCounts, channels]); + + const availableTypeKeys = useMemo(() => { + const keys = ['all']; + Object.entries(channelTypeCounts).forEach(([k, v]) => { + if (k !== 'all' && v > 0) keys.push(String(k)); + }); + return keys; + }, [channelTypeCounts]); + + const renderTypeTabs = () => { + if (enableTagMode) return null; + + return ( + { + setActiveTypeKey(key); + setActivePage(1); + loadChannels(1, pageSize, idSort, enableTagMode, key); + }} + className="mb-4" + > + + {t('全部')} + + {channelTypeCounts['all'] || 0} + + + } + /> + + {CHANNEL_OPTIONS.filter((opt) => availableTypeKeys.includes(String(opt.value))).map((option) => { + const key = String(option.value); + const count = channelTypeCounts[option.value] || 0; + return ( + + {getChannelIcon(option.value)} + {option.label} + + {count} + + + } + /> + ); + })} + + ); + }; + let pageData = channels; + if (activeTypeKey !== 'all') { + const typeVal = parseInt(activeTypeKey); + if (!isNaN(typeVal)) { + pageData = pageData.filter((ch) => { + if (ch.children !== undefined) { + return ch.children.some((c) => c.type === typeVal); + } + return ch.type === typeVal; + }); + } + } const handlePageChange = (page) => { setActivePage(page); @@ -1371,6 +1464,7 @@ const ChannelsTable = () => { const renderHeader = () => (
+ {renderTypeTabs()}
@@ -1388,6 +1389,7 @@ const Detail = (props) => { ) : ( typeof item === 'string'); } catch (error) { diff --git a/web/src/pages/Setting/Operation/ModelRatioSettings.js b/web/src/pages/Setting/Ratio/ModelRatioSettings.js similarity index 100% rename from web/src/pages/Setting/Operation/ModelRatioSettings.js rename to web/src/pages/Setting/Ratio/ModelRatioSettings.js diff --git a/web/src/pages/Setting/Operation/ModelRationNotSetEditor.js b/web/src/pages/Setting/Ratio/ModelRationNotSetEditor.js similarity index 100% rename from web/src/pages/Setting/Operation/ModelRationNotSetEditor.js rename to web/src/pages/Setting/Ratio/ModelRationNotSetEditor.js diff --git a/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js b/web/src/pages/Setting/Ratio/ModelSettingsVisualEditor.js similarity index 100% rename from web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js rename to web/src/pages/Setting/Ratio/ModelSettingsVisualEditor.js diff --git a/web/src/pages/Setting/index.js b/web/src/pages/Setting/index.js index dc48c8dc..5572e540 100644 --- a/web/src/pages/Setting/index.js +++ b/web/src/pages/Setting/index.js @@ -10,6 +10,7 @@ import OperationSetting from '../../components/settings/OperationSetting.js'; import RateLimitSetting from '../../components/settings/RateLimitSetting.js'; import ModelSetting from '../../components/settings/ModelSetting.js'; import DashboardSetting from '../../components/settings/DashboardSetting.js'; +import RatioSetting from '../../components/settings/RatioSetting.js'; const Setting = () => { const { t } = useTranslation(); @@ -24,6 +25,11 @@ const Setting = () => { content: , itemKey: 'operation', }); + panes.push({ + tab: t('倍率设置'), + content: , + itemKey: 'ratio', + }); panes.push({ tab: t('速率限制设置'), content: , @@ -45,7 +51,7 @@ const Setting = () => { itemKey: 'other', }); panes.push({ - tab: t('仪表盘配置'), + tab: t('仪表盘设置'), content: , itemKey: 'dashboard', });