Skip to content

feat: tiered pricing based on prompt-side tokens - #3157

Closed
zuoliangyu wants to merge 3 commits into
QuantumNous:mainfrom
zuoliangyu:feat/tiered-pricing
Closed

feat: tiered pricing based on prompt-side tokens#3157
zuoliangyu wants to merge 3 commits into
QuantumNous:mainfrom
zuoliangyu:feat/tiered-pricing

Conversation

@zuoliangyu

@zuoliangyu zuoliangyu commented Mar 6, 2026

Copy link
Copy Markdown

Summary

Implement context-based tiered pricing (like OpenAI's GPT-4.1 / GPT-5 pricing tiers). When prompt-side tokens exceed a configurable threshold, the entire request is billed at the higher tier's ratios.

For example, GPT-5.4:

  • 0 - 272K tokens: Input $2.50/1M, Output $15.00/1M
  • 272,001+ tokens: Input $5.00/1M, Output $22.50/1M

Changes

Backend (Go):

  • setting/ratio_setting/tiered_ratio.go — New file: TieredPricingTier struct, default configs for GPT-4.1 (200K) and GPT-5 (272K) families, tier resolution logic
  • types/price_data.go — PriceData gains HasTieredPricing, TieredModelRatio, TieredCompletionRatio, TieredCacheRatio fields
  • relay/helper/price.go — ModelPriceHelper uses highest tier for conservative pre-consumption estimation
  • service/quota.go — PostClaudeConsumeQuota resolves tier based on actual prompt-side tokens; adds log notice "分段计费已触发(prompt tokens: Xk,阈值: Yk)"
  • model/option.go — TieredPricing option persistence via settings API
  • model/pricing.go — Pricing struct exposes tiered_pricing per model to frontend
  • setting/ratio_setting/model_ratio.go — InitRatioSettings loads tiered pricing defaults
  • setting/ratio_setting/exposed_cache.go — Expose tiered_pricing in ratio data cache

Frontend (React):

  • Admin ratio settings page: new TieredPricing JSON editor
  • Model card view: green "分段计费" tag badge for models with tiered pricing
  • Model detail side sheet: tiered pricing panel showing per-tier input/output prices
  • RatioSetting.jsx: add TieredPricing to initial state for proper loading

Design

  • Tier resolution: Based on prompt-side tokens (promptTokens + cacheTokens + cacheCreationTokens)
  • Whole-tier switch: When tokens exceed threshold, ALL tokens in the request use the higher tier's ratios (matching upstream provider behavior)
  • Pre-consumption: Uses highest tier's modelRatio for conservative pre-consumption, settled with actual tier after response
  • Configuration: JSON map per model, e.g. {"gpt-5.4": [{"threshold": 272000, "model_ratio": 2.5, "completion_ratio": 4.5, "cache_ratio": 0.1}]}
  • Backward compatible: Models without tiered pricing config use existing ratios unchanged

Test plan

  • Configure tiered pricing for a model via admin settings
  • Request with prompt tokens < threshold → base tier ratios in logs
  • Request with prompt tokens > threshold → higher tier ratios + log notice
  • Model card shows "分段计费" tag
  • Model detail shows tiered pricing breakdown panel
  • Admin settings page loads and persists TieredPricing correctly

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Introduced tiered pricing system enabling variable pricing rates based on token consumption thresholds.
    • Pricing calculations now dynamically apply tier-specific rates when applicable.
  • UI Updates

    • Added tiered pricing display in model pricing tables with per-tier cost breakdowns.
    • Extended settings interface to support tiered pricing configuration and management.

zuoliangyu and others added 3 commits March 6, 2026 17:46
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 <noreply@anthropic.com>
When tiered pricing is triggered, the log content now shows:
"分段计费已触发(prompt tokens: Xk,阈值: Yk)"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

The pull request introduces a comprehensive tiered pricing system that enables pricing ratios to vary based on prompt token thresholds. Changes span backend pricing logic, quota calculations, configuration management, and frontend UI components for setting and displaying tiered pricing tiers per model.

Changes

Cohort / File(s) Summary
Tiered Pricing Core
setting/ratio_setting/tiered_ratio.go
Introduces TieredPricingTier struct with threshold and ratio fields, manages tiered pricing data via RWMap, provides serialization/deserialization, and implements tier resolution logic based on prompt token counts.
Model & Option Integration
model/pricing.go, model/option.go
Adds TieredPricing field to Pricing struct; registers TieredPricing in options initialization and update logic to expose tier data.
Price Calculation
relay/helper/price.go, types/price_data.go
Detects highest applicable tier and applies its ratios; extends PriceData with HasTieredPricing and TieredModelRatio/CompletionRatio/CacheRatio fields to communicate tiered pricing state to callers.
Quota Management
service/quota.go
Applies tiered pricing resolution to Claude consume quota calculations when enabled; computes prompt-side tokens and updates ratios based on resolved tier threshold.
Settings Infrastructure
setting/ratio_setting/model_ratio.go, setting/ratio_setting/exposed_cache.go, web/src/components/settings/RatioSetting.jsx
Initializes tiered pricing settings; exposes tiered pricing data in metrics; adds TieredPricing field to component state.
Tiered Pricing Configuration UI
web/src/pages/Setting/Ratio/ModelRatioSettings.jsx
Adds TextArea form field for JSON-based tiered pricing configuration with validation and onChange handlers.
Tiered Pricing Display UI
web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx, web/src/components/table/model-pricing/view/card/PricingCardView.jsx
Renders tiered pricing tier cards with threshold labels, per-token calculations for each tier; adds visual indicator tag for tiered pricing presence.

Sequence Diagram(s)

sequenceDiagram
    actor Admin as Admin/User
    participant Frontend as Frontend UI
    participant Backend as Backend API
    participant PricingEngine as Pricing Engine
    participant QuotaService as Quota Service

    Admin->>Frontend: Configure tiered pricing tiers (JSON)
    Frontend->>Backend: POST tiered pricing config
    Backend->>PricingEngine: UpdateTieredPricingByJSONString(jsonStr)
    PricingEngine->>PricingEngine: Load tiers into tieredPricingMap
    PricingEngine->>Backend: Cache invalidated
    
    Note over Backend: Later: User makes API call
    Backend->>PricingEngine: Get ModelPriceHelper for model
    PricingEngine->>PricingEngine: GetHighestTier(model name)
    alt Tiered pricing exists
        PricingEngine->>PricingEngine: ResolveTieredPricing(name, promptTokens)
        PricingEngine->>PricingEngine: Select highest tier where Threshold ≤ promptTokens
        PricingEngine->>Backend: Return tier's ModelRatio/CompletionRatio/CacheRatio
        Backend->>QuotaService: Apply tiered ratios to quota calculation
        QuotaService->>QuotaService: Use tiered ratios instead of base ratios
    else No tiered pricing
        Backend->>QuotaService: Use standard pricing ratios
    end
    QuotaService->>Backend: Return calculated quota with tier info
    Backend->>Frontend: Return PriceData with HasTieredPricing flag
    Frontend->>Frontend: Display pricing with tiered breakdown
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Suggested reviewers

  • seefs001

Poem

🐰 A tiered tale unfolds with care,
Where pricing dances through the air,
Each threshold crossed, new ratios bloom,
From backend deep to frontend's room,
Tiers resolved with token's might! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary feature: implementing tiered pricing triggered by prompt-side token counts.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Calcium-Ion

Copy link
Copy Markdown
Member

非常感谢你的PR!但是这个功能我们已经在内部开发推进中,考虑到核心模块的长期维护、架构一致性以及后续迭代节奏,当前阶段暂不接受核心功能相关的外部 PR,因此这次无法合并。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
types/price_data.go (1)

28-31: Surface the new tiered fields in ToSetting().

PriceData now carries tiered-pricing state, but ToSetting() still omits it, so the debug log in relay/helper/price.go cannot show whether tiered pricing was attached or which override ratios were chosen.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@types/price_data.go` around lines 28 - 31, The ToSetting() method on
PriceData needs to include the new tiered-pricing fields so logs can surface
them: update PriceData.ToSetting() to copy HasTieredPricing, TieredModelRatio,
TieredCompletionRatio, and TieredCacheRatio into the returned settings structure
(or map) alongside the existing ModelRatio/CompletionRatio/CacheRatio values;
ensure the keys/names used match what relay/helper/price.go expects for logging
so the debug output will show whether tiered pricing is enabled and the override
ratios selected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@relay/helper/price.go`:
- Around line 93-105: The code uses highestTier.ModelRatio to compute
preConsumedQuota but later still checks "free model" using the base modelRatio,
causing tiered paid requests to be misclassified; fix by introducing an
effectiveRatio (e.g., effectiveRatio := if highestTier != nil then
highestTier.ModelRatio * groupRatioInfo.GroupRatio else modelRatio *
groupRatioInfo.GroupRatio), use effectiveRatio when computing preConsumedQuota
(with preConsumedTokens) and reuse that same effectiveRatio in the subsequent
free-model check instead of modelRatio so the tiered decision is consistent;
update any related flags (hasTieredPricing / tieredModelRatio) only as before
but base free-model logic on effectiveRatio.

In `@setting/ratio_setting/tiered_ratio.go`:
- Around line 45-46: UpdateTieredPricingByJSONString must normalize and validate
tiers before publishing: parse the incoming JSON into the same struct used by
tieredPricingMap, sort the tier slice by Threshold ascending, verify thresholds
are strictly increasing (no duplicates) and that required fields (e.g., Name/ID
and Threshold) are present; if validation fails return an error. After
normalization call types.LoadFromJsonStringWithCallback using the normalized
JSON/structure (or replace tieredPricingMap with the normalized value) so
ResolveTieredPricing and GetHighestTier always see a canonical,
strictly-ascending tier list; keep references to tieredPricingMap and
InvalidateExposedDataCache when wiring the call.

In
`@web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx`:
- Around line 219-227: The code currently picks a multiplier using the first
enumerable key from availableGroups (availableGroups[0]), which makes tier
prices dependent on object key iteration order; change this to select a
deterministic, explicit group instead: compute the intersection between
modelEnableGroups (preserving its order) and Object.keys(usableGroup) (filtered)
and use the first match from modelEnableGroups, or fall back to a clearly named
default group (e.g., 'default' or 'standard') before falling back to 1; update
the logic around usedGroupRatio, availableGroups, usableGroup,
modelEnableGroups, and groupRatio so the group choice is predictable and
documented in the UI/state rather than relying on object key order.
- Around line 199-217: The UI labels are off-by-one vs backend (backend applies
a tier when promptSideTokens >= threshold), so update the range formatting to
use inclusive upper bounds: for the base band use 0 - (firstThreshold - 1)
(guard with Math.max(0, firstThreshold - 1)), and in the tiers loop treat the
current tier as starting at tier.threshold and ending at nextThreshold - 1 (when
nextThreshold exists) or as `${formatTokenCount(tier.threshold)}+` when it's the
last tier; compute upperEnd = nextThreshold ? nextThreshold - 1 : null and build
labels with formatTokenCount(currentStart) and formatTokenCount(upperEnd)
accordingly, leaving modelRatio/completionRatio assignment
(baseModelRatio/baseCompletionRatio and tier.model_ratio/tier.completion_ratio)
unchanged.
- Around line 264-266: The caption "per {unitLabel} tokens" is hardcoded and
bypasses i18n; update ModelPricingTable.jsx to use the useTranslation() hook and
translate the unit caption by calling t('your.translation.key', { unit:
unitLabel }) (or similar) where the Text showing per {unitLabel} tokens is
rendered; import and invoke useTranslation() at the top of the ModelPricingTable
component and replace the literal string with t('...') referencing a new
translation key so the label renders through the i18n pipeline.

In `@web/src/pages/Setting/Ratio/ModelRatioSettings.jsx`:
- Around line 337-340: The validation error message for the JSON textarea is a
raw string; update the component to use i18n by calling useTranslation() and
replacing the literal message with a call to t('...') (e.g.,
t('modelRatio.invalidJson') or an existing key) in the rules array where
validator uses verifyJSON; ensure useTranslation is imported and invoked in the
ModelRatioSettings component so the message flows through t().

---

Nitpick comments:
In `@types/price_data.go`:
- Around line 28-31: The ToSetting() method on PriceData needs to include the
new tiered-pricing fields so logs can surface them: update PriceData.ToSetting()
to copy HasTieredPricing, TieredModelRatio, TieredCompletionRatio, and
TieredCacheRatio into the returned settings structure (or map) alongside the
existing ModelRatio/CompletionRatio/CacheRatio values; ensure the keys/names
used match what relay/helper/price.go expects for logging so the debug output
will show whether tiered pricing is enabled and the override ratios selected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dbc9a55c-f1a5-4cbd-80f1-19d0782802b7

📥 Commits

Reviewing files that changed from the base of the PR and between f9b5ecc and 1a083a4.

📒 Files selected for processing (12)
  • model/option.go
  • model/pricing.go
  • relay/helper/price.go
  • service/quota.go
  • setting/ratio_setting/exposed_cache.go
  • setting/ratio_setting/model_ratio.go
  • setting/ratio_setting/tiered_ratio.go
  • types/price_data.go
  • web/src/components/settings/RatioSetting.jsx
  • web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx
  • web/src/components/table/model-pricing/view/card/PricingCardView.jsx
  • web/src/pages/Setting/Ratio/ModelRatioSettings.jsx

Comment thread relay/helper/price.go
Comment on lines +93 to +105
// 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)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't base free-model detection on the non-tiered ratio.

This block can pre-consume using highestTier.ModelRatio, but Line 125 still decides "free model" from the base modelRatio. If a model is free below the threshold and paid above it, paid tiered requests will have their pre-consumption zeroed back out.

[suggested fix: track the effective ratio used for pre-consumption and reuse that in the later free-model check.]

💡 Proposed fix
 	var freeModel bool
 	var hasTieredPricing bool
 	var tieredModelRatio, tieredCompletionRatio, tieredCacheRatio float64
+	var effectivePreconsumeModelRatio float64
 	if !usePrice {
 		preConsumedTokens := common.Max(promptTokens, common.PreConsumedQuota)
 		if meta.MaxTokens != 0 {
 			preConsumedTokens += meta.MaxTokens
@@
 		modelRatio, success, matchName = ratio_setting.GetModelRatio(info.OriginModelName)
+		effectivePreconsumeModelRatio = modelRatio
 		if !success {
@@
 		// 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
+			effectivePreconsumeModelRatio = highestTier.ModelRatio
 			// Use highest tier's model ratio for pre-consumption to be conservative
-			ratio := highestTier.ModelRatio * groupRatioInfo.GroupRatio
+			ratio := effectivePreconsumeModelRatio * groupRatioInfo.GroupRatio
 			preConsumedQuota = int(float64(preConsumedTokens) * ratio)
 		} else {
-			ratio := modelRatio * groupRatioInfo.GroupRatio
+			ratio := effectivePreconsumeModelRatio * groupRatioInfo.GroupRatio
 			preConsumedQuota = int(float64(preConsumedTokens) * ratio)
 		}
 	} else {
@@
 		} else {
-			if modelRatio == 0 {
+			if effectivePreconsumeModelRatio == 0 {
 				preConsumedQuota = 0
 				freeModel = true
 			}
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/helper/price.go` around lines 93 - 105, The code uses
highestTier.ModelRatio to compute preConsumedQuota but later still checks "free
model" using the base modelRatio, causing tiered paid requests to be
misclassified; fix by introducing an effectiveRatio (e.g., effectiveRatio := if
highestTier != nil then highestTier.ModelRatio * groupRatioInfo.GroupRatio else
modelRatio * groupRatioInfo.GroupRatio), use effectiveRatio when computing
preConsumedQuota (with preConsumedTokens) and reuse that same effectiveRatio in
the subsequent free-model check instead of modelRatio so the tiered decision is
consistent; update any related flags (hasTieredPricing / tieredModelRatio) only
as before but base free-model logic on effectiveRatio.

Comment on lines +45 to +46
func UpdateTieredPricingByJSONString(jsonStr string) error {
return types.LoadFromJsonStringWithCallback(tieredPricingMap, jsonStr, InvalidateExposedDataCache)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Normalize and validate tiers before publishing them.

ResolveTieredPricing() and GetHighestTier() both rely on thresholds being strictly ascending, but this accepts arbitrary admin JSON as-is. A misordered or duplicate tier list will select the wrong active tier and can misprice both final settlement and conservative pre-consumption.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@setting/ratio_setting/tiered_ratio.go` around lines 45 - 46,
UpdateTieredPricingByJSONString must normalize and validate tiers before
publishing: parse the incoming JSON into the same struct used by
tieredPricingMap, sort the tier slice by Threshold ascending, verify thresholds
are strictly increasing (no duplicates) and that required fields (e.g., Name/ID
and Threshold) are present; if validation fails return an error. After
normalization call types.LoadFromJsonStringWithCallback using the normalized
JSON/structure (or replace tieredPricingMap with the normalized value) so
ResolveTieredPricing and GetHighestTier always see a canonical,
strictly-ascending tier list; keep references to tieredPricingMap and
InvalidateExposedDataCache when wiring the call.

Comment on lines +199 to +217
// 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,
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Align the displayed ranges with the backend's inclusive threshold rule.

The backend switches tiers on promptSideTokens >= threshold, but this card shows the base band as ending at threshold and the next band as starting at threshold + 1. Requests exactly on the threshold will therefore be billed in the higher tier while the UI says they still belong to the lower one.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx`
around lines 199 - 217, The UI labels are off-by-one vs backend (backend applies
a tier when promptSideTokens >= threshold), so update the range formatting to
use inclusive upper bounds: for the base band use 0 - (firstThreshold - 1)
(guard with Math.max(0, firstThreshold - 1)), and in the tiers loop treat the
current tier as starting at tier.threshold and ending at nextThreshold - 1 (when
nextThreshold exists) or as `${formatTokenCount(tier.threshold)}+` when it's the
last tier; compute upperEnd = nextThreshold ? nextThreshold - 1 : null and build
labels with formatTokenCount(currentStart) and formatTokenCount(upperEnd)
accordingly, leaving modelRatio/completionRatio assignment
(baseModelRatio/baseCompletionRatio and tier.model_ratio/tier.completion_ratio)
unchanged.

Comment on lines +219 to +227
// 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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't derive tier prices from the first enumerable group.

This card uses availableGroups[0] as its multiplier, so the numbers depend on object key order rather than an explicit group choice. That can show the wrong tier prices with no indication of which group the card is using.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx`
around lines 219 - 227, The code currently picks a multiplier using the first
enumerable key from availableGroups (availableGroups[0]), which makes tier
prices dependent on object key iteration order; change this to select a
deterministic, explicit group instead: compute the intersection between
modelEnableGroups (preserving its order) and Object.keys(usableGroup) (filtered)
and use the first match from modelEnableGroups, or fall back to a clearly named
default group (e.g., 'default' or 'standard') before falling back to 1; update
the logic around usedGroupRatio, availableGroups, usableGroup,
modelEnableGroups, and groupRatio so the group choice is predictable and
documented in the UI/state rather than relying on object key order.

Comment on lines +264 to +266
<div className='flex items-center justify-between mb-2'>
<Text strong className='text-sm'>{tier.label}</Text>
<Text type='tertiary' className='text-xs'>per {unitLabel} tokens</Text>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Translate the new unit caption.

per {unitLabel} tokens is hardcoded English, so it skips the i18n pipeline unlike the surrounding labels. As per coding guidelines, "Use useTranslation() hook and call t('中文key') in components."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx`
around lines 264 - 266, The caption "per {unitLabel} tokens" is hardcoded and
bypasses i18n; update ModelPricingTable.jsx to use the useTranslation() hook and
translate the unit caption by calling t('your.translation.key', { unit:
unitLabel }) (or similar) where the Text showing per {unitLabel} tokens is
rendered; import and invoke useTranslation() at the top of the ModelPricingTable
component and replace the literal string with t('...') referencing a new
translation key so the label renders through the i18n pipeline.

Comment on lines +337 to +340
rules={[
{
validator: (rule, value) => verifyJSON(value),
message: '不是合法的 JSON 字符串',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Localize the new validation message.

The new textarea uses t() for its labels and helper copy, but the validation error on Line 340 is still a raw string, so it won't go through the i18n pipeline.

               rules={[
                 {
                   validator: (rule, value) => verifyJSON(value),
-                  message: '不是合法的 JSON 字符串',
+                  message: t('不是合法的 JSON 字符串'),
                 },
               ]}

As per coding guidelines, web/src/**/*.{ts,tsx,js,jsx} must "Use useTranslation() hook and call t('中文key') in components."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rules={[
{
validator: (rule, value) => verifyJSON(value),
message: '不是合法的 JSON 字符串',
rules={[
{
validator: (rule, value) => verifyJSON(value),
message: t('不是合法的 JSON 字符串'),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/Setting/Ratio/ModelRatioSettings.jsx` around lines 337 - 340,
The validation error message for the JSON textarea is a raw string; update the
component to use i18n by calling useTranslation() and replacing the literal
message with a call to t('...') (e.g., t('modelRatio.invalidJson') or an
existing key) in the rules array where validator uses verifyJSON; ensure
useTranslation is imported and invoked in the ModelRatioSettings component so
the message flows through t().

@Calcium-Ion Calcium-Ion closed this Mar 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants