From 429d5a8880355a5cc399b3bf468d0654e809a5d9 Mon Sep 17 00:00:00 2001 From: zuolan Date: Fri, 6 Mar 2026 17:46:19 +0800 Subject: [PATCH 1/3] feat: add tiered pricing support based on prompt-side tokens Implement context-based tiered pricing (e.g., GPT-4.1 200K threshold, GPT-5 272K threshold). When prompt-side tokens exceed a threshold, the entire request is billed at the higher tier's ratios. Backend: - New tiered_ratio.go with TieredPricingTier struct and resolution logic - PostClaudeConsumeQuota resolves tier based on actual prompt-side tokens - ModelPriceHelper uses highest tier for conservative pre-consumption - TieredPricing option persisted via settings API - Pricing API exposes tiered_pricing per model Frontend: - Admin ratio settings page: new TieredPricing JSON editor - Model card view: green "tiered pricing" tag for applicable models - Model detail side sheet: tiered pricing panel showing per-tier prices Co-Authored-By: Claude Opus 4.6 --- model/option.go | 3 + model/pricing.go | 31 ++-- relay/helper/price.go | 50 ++++-- service/quota.go | 11 ++ setting/ratio_setting/exposed_cache.go | 1 + setting/ratio_setting/model_ratio.go | 1 + setting/ratio_setting/tiered_ratio.go | 92 ++++++++++ types/price_data.go | 4 + .../modal/components/ModelPricingTable.jsx | 163 +++++++++++++++--- .../view/card/PricingCardView.jsx | 10 +- .../Setting/Ratio/ModelRatioSettings.jsx | 27 +++ 11 files changed, 336 insertions(+), 57 deletions(-) create mode 100644 setting/ratio_setting/tiered_ratio.go diff --git a/model/option.go b/model/option.go index 697e77dfe7a5..95f359b33bf6 100644 --- a/model/option.go +++ b/model/option.go @@ -123,6 +123,7 @@ func InitOptionMap() { common.OptionMap["ImageRatio"] = ratio_setting.ImageRatio2JSONString() common.OptionMap["AudioRatio"] = ratio_setting.AudioRatio2JSONString() common.OptionMap["AudioCompletionRatio"] = ratio_setting.AudioCompletionRatio2JSONString() + common.OptionMap["TieredPricing"] = ratio_setting.TieredPricing2JSONString() common.OptionMap["TopUpLink"] = common.TopUpLink //common.OptionMap["ChatLink"] = common.ChatLink //common.OptionMap["ChatLink2"] = common.ChatLink2 @@ -436,6 +437,8 @@ func updateOptionMap(key string, value string) (err error) { err = ratio_setting.UpdateAudioRatioByJSONString(value) case "AudioCompletionRatio": err = ratio_setting.UpdateAudioCompletionRatioByJSONString(value) + case "TieredPricing": + err = ratio_setting.UpdateTieredPricingByJSONString(value) case "TopUpLink": common.TopUpLink = value //case "ChatLink": diff --git a/model/pricing.go b/model/pricing.go index cb687d04ad1f..c644a731dc24 100644 --- a/model/pricing.go +++ b/model/pricing.go @@ -15,19 +15,20 @@ import ( ) type Pricing struct { - ModelName string `json:"model_name"` - Description string `json:"description,omitempty"` - Icon string `json:"icon,omitempty"` - Tags string `json:"tags,omitempty"` - VendorID int `json:"vendor_id,omitempty"` - QuotaType int `json:"quota_type"` - ModelRatio float64 `json:"model_ratio"` - ModelPrice float64 `json:"model_price"` - OwnerBy string `json:"owner_by"` - CompletionRatio float64 `json:"completion_ratio"` - EnableGroup []string `json:"enable_groups"` - SupportedEndpointTypes []constant.EndpointType `json:"supported_endpoint_types"` - PricingVersion string `json:"pricing_version,omitempty"` + ModelName string `json:"model_name"` + Description string `json:"description,omitempty"` + Icon string `json:"icon,omitempty"` + Tags string `json:"tags,omitempty"` + VendorID int `json:"vendor_id,omitempty"` + QuotaType int `json:"quota_type"` + ModelRatio float64 `json:"model_ratio"` + ModelPrice float64 `json:"model_price"` + OwnerBy string `json:"owner_by"` + CompletionRatio float64 `json:"completion_ratio"` + EnableGroup []string `json:"enable_groups"` + SupportedEndpointTypes []constant.EndpointType `json:"supported_endpoint_types"` + PricingVersion string `json:"pricing_version,omitempty"` + TieredPricing []ratio_setting.TieredPricingTier `json:"tiered_pricing,omitempty"` } type PricingVendor struct { @@ -296,6 +297,10 @@ func updatePricing() { pricing.ModelRatio = modelRatio pricing.CompletionRatio = ratio_setting.GetCompletionRatio(model) pricing.QuotaType = 0 + // Add tiered pricing if available + if tiers := ratio_setting.GetTieredPricing(model); tiers != nil { + pricing.TieredPricing = tiers + } } pricingMap = append(pricingMap, pricing) } diff --git a/relay/helper/price.go b/relay/helper/price.go index f109040da0ed..7913239e2188 100644 --- a/relay/helper/price.go +++ b/relay/helper/price.go @@ -61,6 +61,8 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens var audioRatio float64 var audioCompletionRatio float64 var freeModel bool + var hasTieredPricing bool + var tieredModelRatio, tieredCompletionRatio, tieredCacheRatio float64 if !usePrice { preConsumedTokens := common.Max(promptTokens, common.PreConsumedQuota) if meta.MaxTokens != 0 { @@ -87,8 +89,20 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens 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) + + // Check tiered pricing: use highest tier for conservative pre-consumption + if highestTier := ratio_setting.GetHighestTier(info.OriginModelName); highestTier != nil { + hasTieredPricing = true + tieredModelRatio = highestTier.ModelRatio + tieredCompletionRatio = highestTier.CompletionRatio + tieredCacheRatio = highestTier.CacheRatio + // Use highest tier's model ratio for pre-consumption to be conservative + ratio := highestTier.ModelRatio * groupRatioInfo.GroupRatio + preConsumedQuota = int(float64(preConsumedTokens) * ratio) + } else { + ratio := modelRatio * groupRatioInfo.GroupRatio + preConsumedQuota = int(float64(preConsumedTokens) * ratio) + } } else { if meta.ImagePriceRatio != 0 { modelPrice = modelPrice * meta.ImagePriceRatio @@ -116,20 +130,24 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens } priceData := types.PriceData{ - FreeModel: freeModel, - ModelPrice: modelPrice, - ModelRatio: modelRatio, - CompletionRatio: completionRatio, - GroupRatioInfo: groupRatioInfo, - UsePrice: usePrice, - CacheRatio: cacheRatio, - ImageRatio: imageRatio, - AudioRatio: audioRatio, - AudioCompletionRatio: audioCompletionRatio, - CacheCreationRatio: cacheCreationRatio, - CacheCreation5mRatio: cacheCreationRatio5m, - CacheCreation1hRatio: cacheCreationRatio1h, - QuotaToPreConsume: preConsumedQuota, + FreeModel: freeModel, + ModelPrice: modelPrice, + ModelRatio: modelRatio, + CompletionRatio: completionRatio, + GroupRatioInfo: groupRatioInfo, + UsePrice: usePrice, + CacheRatio: cacheRatio, + ImageRatio: imageRatio, + AudioRatio: audioRatio, + AudioCompletionRatio: audioCompletionRatio, + CacheCreationRatio: cacheCreationRatio, + CacheCreation5mRatio: cacheCreationRatio5m, + CacheCreation1hRatio: cacheCreationRatio1h, + QuotaToPreConsume: preConsumedQuota, + HasTieredPricing: hasTieredPricing, + TieredModelRatio: tieredModelRatio, + TieredCompletionRatio: tieredCompletionRatio, + TieredCacheRatio: tieredCacheRatio, } if common.DebugEnabled { diff --git a/service/quota.go b/service/quota.go index 7ee70edd50c1..babe2be90ed4 100644 --- a/service/quota.go +++ b/service/quota.go @@ -272,6 +272,17 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, promptTokens -= cacheCreationTokens } + // Tiered pricing: resolve based on prompt-side tokens + promptSideTokens := promptTokens + cacheTokens + cacheCreationTokens + if relayInfo.PriceData.HasTieredPricing && !relayInfo.PriceData.UsePrice { + tier := ratio_setting.ResolveTieredPricing(relayInfo.OriginModelName, promptSideTokens) + if tier != nil { + modelRatio = tier.ModelRatio + completionRatio = tier.CompletionRatio + cacheRatio = tier.CacheRatio + } + } + calculateQuota := 0.0 if !relayInfo.PriceData.UsePrice { calculateQuota = float64(promptTokens) diff --git a/setting/ratio_setting/exposed_cache.go b/setting/ratio_setting/exposed_cache.go index c88216fcb015..cb62211d7552 100644 --- a/setting/ratio_setting/exposed_cache.go +++ b/setting/ratio_setting/exposed_cache.go @@ -47,6 +47,7 @@ func GetExposedData() gin.H { "cache_ratio": GetCacheRatioCopy(), "create_cache_ratio": GetCreateCacheRatioCopy(), "model_price": GetModelPriceCopy(), + "tiered_pricing": GetTieredPricingCopy(), } exposedData.Store(&exposedCache{ data: newData, diff --git a/setting/ratio_setting/model_ratio.go b/setting/ratio_setting/model_ratio.go index a40fb35f0064..bbf48590681e 100644 --- a/setting/ratio_setting/model_ratio.go +++ b/setting/ratio_setting/model_ratio.go @@ -343,6 +343,7 @@ func InitRatioSettings() { imageRatioMap.AddAll(defaultImageRatio) audioRatioMap.AddAll(defaultAudioRatio) audioCompletionRatioMap.AddAll(defaultAudioCompletionRatio) + tieredPricingMap.AddAll(defaultTieredPricing) } func GetModelPriceMap() map[string]float64 { diff --git a/setting/ratio_setting/tiered_ratio.go b/setting/ratio_setting/tiered_ratio.go new file mode 100644 index 000000000000..3a42fca15f71 --- /dev/null +++ b/setting/ratio_setting/tiered_ratio.go @@ -0,0 +1,92 @@ +package ratio_setting + +import ( + "github.com/QuantumNous/new-api/types" +) + +// TieredPricingTier defines a pricing tier for a model. +// When prompt-side tokens >= Threshold, the ratios in this tier override the base ratios. +// Tiers should be sorted by Threshold in ascending order. +type TieredPricingTier struct { + Threshold int `json:"threshold"` + ModelRatio float64 `json:"model_ratio"` + CompletionRatio float64 `json:"completion_ratio"` + CacheRatio float64 `json:"cache_ratio"` +} + +// Default tiered pricing for models that have context-based pricing tiers. +// Key is model name, value is list of tiers (sorted by threshold ascending). +// The first tier (threshold=0) is not needed here — it uses the base ratios from modelRatioMap etc. +// Only tiers above the base need to be listed. +var defaultTieredPricing = map[string][]TieredPricingTier{ + // gpt-4.1: base $2/1M input, $8/1M output; 200K+: $4/1M input, $8/1M output (doubled input, same output) + "gpt-4.1": {{Threshold: 200000, ModelRatio: 2.0, CompletionRatio: 2.0, CacheRatio: 0.5}}, + "gpt-4.1-2025-04-14": {{Threshold: 200000, ModelRatio: 2.0, CompletionRatio: 2.0, CacheRatio: 0.5}}, + "gpt-4.1-mini": {{Threshold: 200000, ModelRatio: 0.4, CompletionRatio: 4.0, CacheRatio: 0.5}}, + "gpt-4.1-mini-2025-04-14": {{Threshold: 200000, ModelRatio: 0.4, CompletionRatio: 4.0, CacheRatio: 0.5}}, + "gpt-4.1-nano": {{Threshold: 200000, ModelRatio: 0.1, CompletionRatio: 4.0, CacheRatio: 0.5}}, + "gpt-4.1-nano-2025-04-14": {{Threshold: 200000, ModelRatio: 0.1, CompletionRatio: 4.0, CacheRatio: 0.5}}, + // gpt-5: base $1.25/1M input, $10/1M output; 272K+: $2.5/1M input, $15/1M output + "gpt-5": {{Threshold: 272000, ModelRatio: 1.25, CompletionRatio: 6.0, CacheRatio: 0.2}}, + "gpt-5-2025-08-07": {{Threshold: 272000, ModelRatio: 1.25, CompletionRatio: 6.0, CacheRatio: 0.2}}, + "gpt-5-chat-latest": {{Threshold: 272000, ModelRatio: 1.25, CompletionRatio: 6.0, CacheRatio: 0.2}}, + "gpt-5-mini": {{Threshold: 272000, ModelRatio: 0.25, CompletionRatio: 6.0, CacheRatio: 0.2}}, + "gpt-5-mini-2025-08-07": {{Threshold: 272000, ModelRatio: 0.25, CompletionRatio: 6.0, CacheRatio: 0.2}}, + "gpt-5-nano": {{Threshold: 272000, ModelRatio: 0.05, CompletionRatio: 6.0, CacheRatio: 0.2}}, + "gpt-5-nano-2025-08-07": {{Threshold: 272000, ModelRatio: 0.05, CompletionRatio: 6.0, CacheRatio: 0.2}}, +} + +var tieredPricingMap = types.NewRWMap[string, []TieredPricingTier]() + +func TieredPricing2JSONString() string { + return tieredPricingMap.MarshalJSONString() +} + +func UpdateTieredPricingByJSONString(jsonStr string) error { + return types.LoadFromJsonStringWithCallback(tieredPricingMap, jsonStr, InvalidateExposedDataCache) +} + +func GetTieredPricingCopy() map[string][]TieredPricingTier { + return tieredPricingMap.ReadAll() +} + +// GetTieredPricing returns the tiered pricing tiers for a model. +// Returns nil if the model has no tiered pricing. +func GetTieredPricing(name string) []TieredPricingTier { + name = FormatMatchingModelName(name) + tiers, ok := tieredPricingMap.Get(name) + if !ok || len(tiers) == 0 { + return nil + } + return tiers +} + +// ResolveTieredPricing determines the active tier based on prompt-side token count. +// Returns the tier if prompt tokens exceed a threshold, nil otherwise (use base ratios). +func ResolveTieredPricing(name string, promptSideTokens int) *TieredPricingTier { + tiers := GetTieredPricing(name) + if tiers == nil { + return nil + } + + // Find the highest tier whose threshold is <= promptSideTokens + var activeTier *TieredPricingTier + for i := range tiers { + if promptSideTokens >= tiers[i].Threshold { + activeTier = &tiers[i] + } else { + break + } + } + return activeTier +} + +// GetHighestTier returns the highest (most expensive) tier for a model. +// Used for conservative pre-consumption estimation. +func GetHighestTier(name string) *TieredPricingTier { + tiers := GetTieredPricing(name) + if tiers == nil { + return nil + } + return &tiers[len(tiers)-1] +} diff --git a/types/price_data.go b/types/price_data.go index 93bc6ae8d168..98a4015056f2 100644 --- a/types/price_data.go +++ b/types/price_data.go @@ -25,6 +25,10 @@ type PriceData struct { Quota int // 按次计费的最终额度(MJ / Task) QuotaToPreConsume int // 按量计费的预消耗额度 GroupRatioInfo GroupRatioInfo + HasTieredPricing bool // 是否启用了分段计费 + TieredModelRatio float64 // 分段计费时的模型倍率(覆盖 ModelRatio) + TieredCompletionRatio float64 // 分段计费时的补全倍率(覆盖 CompletionRatio) + TieredCacheRatio float64 // 分段计费时的缓存倍率(覆盖 CacheRatio) } func (p *PriceData) AddOtherRatio(key string, ratio float64) { diff --git a/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx b/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx index 266bbf9478c2..38eb2973850c 100644 --- a/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx +++ b/web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx @@ -181,36 +181,145 @@ const ModelPricingTable = ({ ); }; - return ( - -
- - - -
- {t('分组价格')} -
- {t('不同用户分组的价格信息')} + const formatTokenCount = (count) => { + if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`; + if (count >= 1000) return `${(count / 1000).toFixed(0)}K`; + return String(count); + }; + + const renderTieredPricing = () => { + const tiers = modelData?.tiered_pricing; + if (!tiers || tiers.length === 0 || modelData?.quota_type !== 0) return null; + + // Build tier ranges: base tier + defined tiers + const baseModelRatio = modelData.model_ratio || 0; + const baseCompletionRatio = modelData.completion_ratio || 1; + const allTiers = []; + + // Determine the first tier's upper bound + const firstThreshold = tiers[0]?.threshold || 0; + allTiers.push({ + label: `0 - ${formatTokenCount(firstThreshold)}`, + modelRatio: baseModelRatio, + completionRatio: baseCompletionRatio, + }); + + tiers.forEach((tier, idx) => { + const nextThreshold = idx < tiers.length - 1 ? tiers[idx + 1].threshold : null; + const label = nextThreshold + ? `${formatTokenCount(tier.threshold + 1)} - ${formatTokenCount(nextThreshold)}` + : `${formatTokenCount(tier.threshold + 1)}+`; + allTiers.push({ + label, + modelRatio: tier.model_ratio, + completionRatio: tier.completion_ratio, + }); + }); + + // Use the first available group ratio + let usedGroupRatio = 1; + const availableGroups = Object.keys(usableGroup || {}) + .filter((g) => g !== '' && g !== 'auto') + .filter((g) => modelEnableGroups.includes(g)); + if (availableGroups.length > 0) { + const firstGroup = availableGroups[0]; + usedGroupRatio = groupRatio?.[firstGroup] ?? 1; + } + + let symbol = '$'; + if (currency === 'CNY') symbol = '¥'; + + const unitDivisor = tokenUnit === 'K' ? 1000 : 1; + const unitLabel = tokenUnit === 'K' ? '1K' : '1M'; + + return ( + +
+ + + +
+ {t('分段计费')} +
+ {t('基于 prompt 侧 token 数量')} +
-
- {autoChain.length > 0 && ( -
- {t('auto分组调用链路')} - - {autoChain.map((g, idx) => ( - - - {g} - {t('分组')} - - {idx < autoChain.length - 1 && } - - ))} +
+ {allTiers.map((tier, idx) => { + const inputPriceUSD = tier.modelRatio * 2 * usedGroupRatio; + const outputPriceUSD = tier.modelRatio * tier.completionRatio * 2 * usedGroupRatio; + const numInput = (currency === 'CNY' ? inputPriceUSD * 7.3 : inputPriceUSD) / unitDivisor; + const numOutput = (currency === 'CNY' ? outputPriceUSD * 7.3 : outputPriceUSD) / unitDivisor; + + return ( +
0 ? 'var(--semi-color-warning-light-default)' : 'var(--semi-color-fill-0)', + }} + > +
+ {tier.label} + per {unitLabel} tokens +
+
+
+
{t('输入')}
+
+ {symbol}{numInput.toFixed(4)} +
+
+
+
{t('输出')}
+
+ {symbol}{numOutput.toFixed(4)} +
+
+
+
+ ); + })}
- )} - {renderGroupPriceTable()} - + + ); + }; + + return ( + <> + +
+ + + +
+ {t('分组价格')} +
+ {t('不同用户分组的价格信息')} +
+
+
+ {autoChain.length > 0 && ( +
+ {t('auto分组调用链路')} + + {autoChain.map((g, idx) => ( + + + {g} + {t('分组')} + + {idx < autoChain.length - 1 && } + + ))} +
+ )} + {renderGroupPriceTable()} +
+ {renderTieredPricing()} + ); }; diff --git a/web/src/components/table/model-pricing/view/card/PricingCardView.jsx b/web/src/components/table/model-pricing/view/card/PricingCardView.jsx index 6d532869f652..b876b068b6ed 100644 --- a/web/src/components/table/model-pricing/view/card/PricingCardView.jsx +++ b/web/src/components/table/model-pricing/view/card/PricingCardView.jsx @@ -172,6 +172,14 @@ const PricingCardView = ({ ); } + // 分段计费标签 + const hasTiered = record.tiered_pricing && record.tiered_pricing.length > 0; + const tieredTag = hasTiered ? ( + + {t('分段计费')} + + ) : null; + // 自定义标签(右边) const customTags = []; if (record.tags) { @@ -192,7 +200,7 @@ const PricingCardView = ({ return (
-
{billingTag}
+
{billingTag}{tieredTag}
{customTags.length > 0 && renderLimitedItems({ diff --git a/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx b/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx index e9be19785547..5a4ef44fd370 100644 --- a/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx +++ b/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx @@ -48,6 +48,7 @@ export default function ModelRatioSettings(props) { ImageRatio: '', AudioRatio: '', AudioCompletionRatio: '', + TieredPricing: '', ExposeRatioEnabled: false, }); const refForm = useRef(); @@ -319,6 +320,32 @@ export default function ModelRatioSettings(props) { /> + + + verifyJSON(value), + message: '不是合法的 JSON 字符串', + }, + ]} + onChange={(value) => + setInputs({ ...inputs, TieredPricing: value }) + } + /> + + Date: Fri, 6 Mar 2026 17:51:09 +0800 Subject: [PATCH 2/3] feat: add tiered pricing trigger notice in consume logs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When tiered pricing is triggered, the log content now shows: "分段计费已触发(prompt tokens: Xk,阈值: Yk)" Co-Authored-By: Claude Opus 4.6 --- service/quota.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/service/quota.go b/service/quota.go index babe2be90ed4..0dde4dbdf30d 100644 --- a/service/quota.go +++ b/service/quota.go @@ -274,12 +274,16 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, // Tiered pricing: resolve based on prompt-side tokens promptSideTokens := promptTokens + cacheTokens + cacheCreationTokens + var tieredTriggered bool + var tieredThreshold int if relayInfo.PriceData.HasTieredPricing && !relayInfo.PriceData.UsePrice { tier := ratio_setting.ResolveTieredPricing(relayInfo.OriginModelName, promptSideTokens) if tier != nil { modelRatio = tier.ModelRatio completionRatio = tier.CompletionRatio cacheRatio = tier.CacheRatio + tieredTriggered = true + tieredThreshold = tier.Threshold } } @@ -308,6 +312,9 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, totalTokens := promptTokens + completionTokens var logContent string + if tieredTriggered { + logContent += fmt.Sprintf("分段计费已触发(prompt tokens: %dk,阈值: %dk)", promptSideTokens/1000, tieredThreshold/1000) + } // record all the consume log even if quota is 0 if totalTokens == 0 { // in this case, must be some error happened From 1a083a49640a379302911c6a46707d00e2f16116 Mon Sep 17 00:00:00 2001 From: zuolan Date: Sat, 7 Mar 2026 00:01:38 +0800 Subject: [PATCH 3/3] fix: add TieredPricing to RatioSetting initial state The parent component's initial state was missing the TieredPricing key, causing the child ModelRatioSettings to filter it out when loading options from the API. Co-Authored-By: Claude Opus 4.6 --- web/src/components/settings/RatioSetting.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/components/settings/RatioSetting.jsx b/web/src/components/settings/RatioSetting.jsx index 170413058c1e..0ae9b6fac56f 100644 --- a/web/src/components/settings/RatioSetting.jsx +++ b/web/src/components/settings/RatioSetting.jsx @@ -43,6 +43,7 @@ const RatioSetting = () => { ImageRatio: '', AudioRatio: '', AudioCompletionRatio: '', + TieredPricing: '', AutoGroups: '', DefaultUseAutoGroup: false, ExposeRatioEnabled: false,