diff --git a/controller/option.go b/controller/option.go index decdb0d40..fb54d20a0 100644 --- a/controller/option.go +++ b/controller/option.go @@ -112,6 +112,33 @@ func UpdateOption(c *gin.Context) { }) return } + case "ImageRatio": + err = ratio_setting.UpdateImageRatioByJSONString(option.Value) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "图片倍率设置失败: " + err.Error(), + }) + return + } + case "AudioRatio": + err = ratio_setting.UpdateAudioRatioByJSONString(option.Value) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "音频倍率设置失败: " + err.Error(), + }) + return + } + case "AudioCompletionRatio": + err = ratio_setting.UpdateAudioCompletionRatioByJSONString(option.Value) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "音频补全倍率设置失败: " + err.Error(), + }) + return + } case "ModelRequestRateLimitGroup": err = setting.CheckModelRequestRateLimitGroup(option.Value) if err != nil { diff --git a/model/option.go b/model/option.go index 2121710ce..e589b46ed 100644 --- a/model/option.go +++ b/model/option.go @@ -111,6 +111,9 @@ func InitOptionMap() { common.OptionMap["GroupGroupRatio"] = ratio_setting.GroupGroupRatio2JSONString() common.OptionMap["UserUsableGroups"] = setting.UserUsableGroups2JSONString() common.OptionMap["CompletionRatio"] = ratio_setting.CompletionRatio2JSONString() + common.OptionMap["ImageRatio"] = ratio_setting.ImageRatio2JSONString() + common.OptionMap["AudioRatio"] = ratio_setting.AudioRatio2JSONString() + common.OptionMap["AudioCompletionRatio"] = ratio_setting.AudioCompletionRatio2JSONString() common.OptionMap["TopUpLink"] = common.TopUpLink //common.OptionMap["ChatLink"] = common.ChatLink //common.OptionMap["ChatLink2"] = common.ChatLink2 @@ -396,6 +399,12 @@ func updateOptionMap(key string, value string) (err error) { err = ratio_setting.UpdateModelPriceByJSONString(value) case "CacheRatio": err = ratio_setting.UpdateCacheRatioByJSONString(value) + case "ImageRatio": + err = ratio_setting.UpdateImageRatioByJSONString(value) + case "AudioRatio": + err = ratio_setting.UpdateAudioRatioByJSONString(value) + case "AudioCompletionRatio": + err = ratio_setting.UpdateAudioCompletionRatioByJSONString(value) case "TopUpLink": common.TopUpLink = value //case "ChatLink": diff --git a/relay/helper/price.go b/relay/helper/price.go index fdc5b66d8..c23c068b3 100644 --- a/relay/helper/price.go +++ b/relay/helper/price.go @@ -52,6 +52,8 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens var cacheRatio float64 var imageRatio float64 var cacheCreationRatio float64 + var audioRatio float64 + var audioCompletionRatio float64 if !usePrice { preConsumedTokens := common.Max(promptTokens, common.PreConsumedQuota) if meta.MaxTokens != 0 { @@ -73,6 +75,8 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens cacheRatio, _ = ratio_setting.GetCacheRatio(info.OriginModelName) cacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(info.OriginModelName) imageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName) + audioRatio = ratio_setting.GetAudioRatio(info.OriginModelName) + audioCompletionRatio = ratio_setting.GetAudioCompletionRatio(info.OriginModelName) ratio := modelRatio * groupRatioInfo.GroupRatio preConsumedQuota = int(float64(preConsumedTokens) * ratio) } else { @@ -90,6 +94,8 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens UsePrice: usePrice, CacheRatio: cacheRatio, ImageRatio: imageRatio, + AudioRatio: audioRatio, + AudioCompletionRatio: audioCompletionRatio, CacheCreationRatio: cacheCreationRatio, ShouldPreConsumedQuota: preConsumedQuota, } diff --git a/setting/ratio_setting/model_ratio.go b/setting/ratio_setting/model_ratio.go index f06cd71ef..99952daf1 100644 --- a/setting/ratio_setting/model_ratio.go +++ b/setting/ratio_setting/model_ratio.go @@ -278,6 +278,18 @@ var defaultModelPrice = map[string]float64{ "mj_upload": 0.05, } +var defaultAudioRatio = map[string]float64{ + "gpt-4o-audio-preview": 16, + "gpt-4o-mini-audio-preview": 66.67, + "gpt-4o-realtime-preview": 8, + "gpt-4o-mini-realtime-preview": 16.67, +} + +var defaultAudioCompletionRatio = map[string]float64{ + "gpt-4o-realtime": 2, + "gpt-4o-mini-realtime": 2, +} + var ( modelPriceMap map[string]float64 = nil modelPriceMapMutex = sync.RWMutex{} @@ -326,6 +338,15 @@ func InitRatioSettings() { imageRatioMap = defaultImageRatio imageRatioMapMutex.Unlock() + // initialize audioRatioMap + audioRatioMapMutex.Lock() + audioRatioMap = defaultAudioRatio + audioRatioMapMutex.Unlock() + + // initialize audioCompletionRatioMap + audioCompletionRatioMapMutex.Lock() + audioCompletionRatioMap = defaultAudioCompletionRatio + audioCompletionRatioMapMutex.Unlock() } func GetModelPriceMap() map[string]float64 { @@ -417,6 +438,18 @@ func GetDefaultModelRatioMap() map[string]float64 { return defaultModelRatio } +func GetDefaultImageRatioMap() map[string]float64 { + return defaultImageRatio +} + +func GetDefaultAudioRatioMap() map[string]float64 { + return defaultAudioRatio +} + +func GetDefaultAudioCompletionRatioMap() map[string]float64 { + return defaultAudioCompletionRatio +} + func GetCompletionRatioMap() map[string]float64 { CompletionRatioMutex.RLock() defer CompletionRatioMutex.RUnlock() @@ -584,32 +617,20 @@ func getHardcodedCompletionModelRatio(name string) (float64, bool) { } func GetAudioRatio(name string) float64 { - if strings.Contains(name, "-realtime") { - if strings.HasSuffix(name, "gpt-4o-realtime-preview") { - return 8 - } else if strings.Contains(name, "gpt-4o-mini-realtime-preview") { - return 10 / 0.6 - } else { - return 20 - } - } - if strings.Contains(name, "-audio") { - if strings.HasPrefix(name, "gpt-4o-audio-preview") { - return 40 / 2.5 - } else if strings.HasPrefix(name, "gpt-4o-mini-audio-preview") { - return 10 / 0.15 - } else { - return 40 - } + audioRatioMapMutex.RLock() + defer audioRatioMapMutex.RUnlock() + if ratio, ok := audioRatioMap[name]; ok { + return ratio } return 20 } func GetAudioCompletionRatio(name string) float64 { - if strings.HasPrefix(name, "gpt-4o-realtime") { - return 2 - } else if strings.HasPrefix(name, "gpt-4o-mini-realtime") { - return 2 + audioCompletionRatioMapMutex.RLock() + defer audioCompletionRatioMapMutex.RUnlock() + if ratio, ok := audioCompletionRatioMap[name]; ok { + + return ratio } return 2 } @@ -630,6 +651,14 @@ var defaultImageRatio = map[string]float64{ } var imageRatioMap map[string]float64 var imageRatioMapMutex sync.RWMutex +var ( + audioRatioMap map[string]float64 = nil + audioRatioMapMutex = sync.RWMutex{} +) +var ( + audioCompletionRatioMap map[string]float64 = nil + audioCompletionRatioMapMutex = sync.RWMutex{} +) func ImageRatio2JSONString() string { imageRatioMapMutex.RLock() @@ -658,6 +687,68 @@ func GetImageRatio(name string) (float64, bool) { return ratio, true } +func AudioRatio2JSONString() string { + audioRatioMapMutex.RLock() + defer audioRatioMapMutex.RUnlock() + jsonBytes, err := common.Marshal(audioRatioMap) + if err != nil { + common.SysError("error marshalling audio ratio: " + err.Error()) + } + return string(jsonBytes) +} + +func UpdateAudioRatioByJSONString(jsonStr string) error { + audioRatioMapMutex.Lock() + defer audioRatioMapMutex.Unlock() + audioRatioMap = make(map[string]float64) + err := common.Unmarshal([]byte(jsonStr), &audioRatioMap) + if err == nil { + InvalidateExposedDataCache() + } + return err +} + +func GetAudioRatioCopy() map[string]float64 { + audioRatioMapMutex.RLock() + defer audioRatioMapMutex.RUnlock() + copyMap := make(map[string]float64, len(audioRatioMap)) + for k, v := range audioRatioMap { + copyMap[k] = v + } + return copyMap +} + +func AudioCompletionRatio2JSONString() string { + audioCompletionRatioMapMutex.RLock() + defer audioCompletionRatioMapMutex.RUnlock() + jsonBytes, err := common.Marshal(audioCompletionRatioMap) + if err != nil { + common.SysError("error marshalling audio completion ratio: " + err.Error()) + } + return string(jsonBytes) +} + +func UpdateAudioCompletionRatioByJSONString(jsonStr string) error { + audioCompletionRatioMapMutex.Lock() + defer audioCompletionRatioMapMutex.Unlock() + audioCompletionRatioMap = make(map[string]float64) + err := common.Unmarshal([]byte(jsonStr), &audioCompletionRatioMap) + if err == nil { + InvalidateExposedDataCache() + } + return err +} + +func GetAudioCompletionRatioCopy() map[string]float64 { + audioCompletionRatioMapMutex.RLock() + defer audioCompletionRatioMapMutex.RUnlock() + copyMap := make(map[string]float64, len(audioCompletionRatioMap)) + for k, v := range audioCompletionRatioMap { + copyMap[k] = v + } + return copyMap +} + func GetModelRatioCopy() map[string]float64 { modelRatioMapMutex.RLock() defer modelRatioMapMutex.RUnlock() diff --git a/types/price_data.go b/types/price_data.go index f6a92d7e3..ec7fcdfe9 100644 --- a/types/price_data.go +++ b/types/price_data.go @@ -15,6 +15,8 @@ type PriceData struct { CacheRatio float64 CacheCreationRatio float64 ImageRatio float64 + AudioRatio float64 + AudioCompletionRatio float64 UsePrice bool ShouldPreConsumedQuota int GroupRatioInfo GroupRatioInfo @@ -27,5 +29,5 @@ type PerCallPriceData struct { } func (p PriceData) ToSetting() string { - return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, ShouldPreConsumedQuota: %d, ImageRatio: %f", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.ShouldPreConsumedQuota, p.ImageRatio) + return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, ShouldPreConsumedQuota: %d, ImageRatio: %f, AudioRatio: %f, AudioCompletionRatio: %f", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.ShouldPreConsumedQuota, p.ImageRatio, p.AudioRatio, p.AudioCompletionRatio) } diff --git a/web/src/components/settings/RatioSetting.jsx b/web/src/components/settings/RatioSetting.jsx index 096722bba..f5d8ef99d 100644 --- a/web/src/components/settings/RatioSetting.jsx +++ b/web/src/components/settings/RatioSetting.jsx @@ -39,6 +39,9 @@ const RatioSetting = () => { CompletionRatio: '', GroupRatio: '', GroupGroupRatio: '', + ImageRatio: '', + AudioRatio: '', + AudioCompletionRatio: '', AutoGroups: '', DefaultUseAutoGroup: false, ExposeRatioEnabled: false, @@ -61,7 +64,10 @@ const RatioSetting = () => { item.key === 'UserUsableGroups' || item.key === 'CompletionRatio' || item.key === 'ModelPrice' || - item.key === 'CacheRatio' + item.key === 'CacheRatio' || + item.key === 'ImageRatio' || + item.key === 'AudioRatio' || + item.key === 'AudioCompletionRatio' ) { try { item.value = JSON.stringify(JSON.parse(item.value), null, 2); diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 877fa44fe..62c8ebb79 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -2017,5 +2017,15 @@ "查看密钥": "View key", "查看渠道密钥": "View channel key", "渠道密钥信息": "Channel key information", - "密钥获取成功": "Key acquisition successful" + "密钥获取成功": "Key acquisition successful", + "模型补全倍率(仅对自定义模型有效)": "Model completion ratio (only effective for custom models)", + "图片倍率": "Image ratio", + "音频倍率": "Audio ratio", + "音频补全倍率": "Audio completion ratio", + "图片输入相关的倍率设置,键为模型名称,值为倍率": "Image input related ratio settings, key is model name, value is ratio", + "音频输入相关的倍率设置,键为模型名称,值为倍率": "Audio input related ratio settings, key is model name, value is ratio", + "音频输出补全相关的倍率设置,键为模型名称,值为倍率": "Audio output completion related ratio settings, key is model name, value is ratio", + "为一个 JSON 文本,键为模型名称,值为倍率,例如:{\"gpt-image-1\": 2}": "A JSON text with model name as key and ratio as value, e.g.: {\"gpt-image-1\": 2}", + "为一个 JSON 文本,键为模型名称,值为倍率,例如:{\"gpt-4o-audio-preview\": 16}": "A JSON text with model name as key and ratio as value, e.g.: {\"gpt-4o-audio-preview\": 16}", + "为一个 JSON 文本,键为模型名称,值为倍率,例如:{\"gpt-4o-realtime\": 2}": "A JSON text with model name as key and ratio as value, e.g.: {\"gpt-4o-realtime\": 2}" } diff --git a/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx b/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx index 2462a35ad..b40951261 100644 --- a/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx +++ b/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx @@ -44,6 +44,9 @@ export default function ModelRatioSettings(props) { ModelRatio: '', CacheRatio: '', CompletionRatio: '', + ImageRatio: '', + AudioRatio: '', + AudioCompletionRatio: '', ExposeRatioEnabled: false, }); const refForm = useRef(); @@ -219,6 +222,72 @@ export default function ModelRatioSettings(props) { /> + + + verifyJSON(value), + message: '不是合法的 JSON 字符串', + }, + ]} + onChange={(value) => + setInputs({ ...inputs, ImageRatio: value }) + } + /> + + + + + verifyJSON(value), + message: '不是合法的 JSON 字符串', + }, + ]} + onChange={(value) => + setInputs({ ...inputs, AudioRatio: value }) + } + /> + + + + + verifyJSON(value), + message: '不是合法的 JSON 字符串', + }, + ]} + onChange={(value) => + setInputs({ ...inputs, AudioCompletionRatio: value }) + } + /> + +