feat(ratio-sync): port upstream pricing-endpoint sync UI to HeroUI#20
Conversation
Re-implements the frontend half of upstream commits f424f90 ("feat: sync upstream pricing from pricing endpoint") and cc4ad6c ("fix(channel): load model mapping during upstream model checks") on top of this fork's HeroUI-native UpstreamRatioSync. The backend half was already absorbed by the v1.0 sync merge; only the `web/classic/src/pages/Setting/Ratio/UpstreamRatioSync.jsx` UI was left on the old per-row-per-ratio-type table that didn't surface the new fields. What ships: - **Five new ratio columns**: `create_cache_ratio`, `image_ratio`, `audio_ratio`, `audio_completion_ratio` join the existing `cache_ratio`. `RATIO_FIELDS` / `SYNC_FIELD_ORDER` are now a module-level source of truth that mirrors backend `controller/ratio_sync.go pricingSyncFields`. - **Tiered billing as a third category**. `getBillingCategory` now returns `'tiered'` for `billing_mode` / `billing_expr`. Tiered selections coexist with ratio/price billing on the same model (backend chooses based on `billing_mode` at billing time) so picking tiered does NOT clear an existing price/ratio resolution and vice-versa. - **Expression-priority resolution**. New `getPreferredSyncField` upgrades a non-expr selection to `billing_expr` whenever the same source exposes a non-empty expression for the model — clicking any cell from that source captures the full tiered config in one go. Mirrors upstream's `web/default` reference implementation. - **Auto-pair tiered fields**. Selecting a tiered field also populates the partner field from the same source: picking `billing_expr` auto-fills `billing_mode` (= `tiered_expr` if source didn't ship a mode), and picking `billing_mode` auto-fills `billing_expr` if available. - **Apply-time conflict check tightened**. The price-vs-ratio swap warning now runs against `RATIO_FIELDS` (not just three of them) and skips tiered-only selections — tiered is a billing overlay, not a category swap, so it shouldn't trigger the modal. - **Numeric normalisation hardened**. `parseFloat` swapped for `Number(...)` in `performSync` so explicit `0` (a legal ratio for free models) survives the round-trip and tiered string fields never get NaN-ified. `applySync` now best-effort-parses every option to `{}` instead of throwing on a single malformed JSON. - **Caller plumbing**: `selectValue(model, ratioType, value, sourceName)` — `handleBulkSelect`, `handleRowSelect`, and the in-cell CompactCheckbox onChange now thread the upstream name through so expression-priority resolution can ask the right source. Backend `optionKeyFor` mapping handles the dotted Option-store keys upstream introduced (`billing_setting.billing_mode`, `billing_setting.billing_expr`) without leaking the convention into the rest of the file. i18n: adds 4 new keys (`创建缓存倍率`, `图像倍率`, `音频倍率`, `阶梯计费表达式`) across all 8 locale files. `音频补全倍率` and `计费模式` were already present from the v1.0 sync merge. Build clean. UI fits the existing per-(model, ratioType) row table shape so no architectural restructure was needed; the upstream default version's column-collapsed layout is intentionally not mirrored — it's a divergent design choice that would re-conflict on every future sync. Made-with: Cursor
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a3a49d41de
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| CreateCacheRatio: { ...currentRatios.CreateCacheRatio }, | ||
| ImageRatio: { ...currentRatios.ImageRatio }, | ||
| AudioRatio: { ...currentRatios.AudioRatio }, | ||
| AudioCompletionRatio: { ...currentRatios.AudioCompletionRatio }, | ||
| ModelPrice: { ...currentRatios.ModelPrice }, |
There was a problem hiding this comment.
Preserve extended ratio maps when building sync payload
performSync now always writes CreateCacheRatio/ImageRatio/AudioRatio/AudioCompletionRatio (and tiered maps) from currentRatios, but the conflict-confirmation path still calls it with only the legacy 4 fields. In that flow, these spreads resolve to empty objects and the subsequent PUT /api/option/ calls overwrite existing option JSON with {}, so confirming a price-vs-ratio conflict can silently erase previously configured ratio/tiered data.
Useful? React with 👍 / 👎.
| if (rtCategory !== category) delete newModelRes[rt]; | ||
| } | ||
| }); | ||
| newModelRes[preferredType] = preferredValue; |
There was a problem hiding this comment.
Keep checkbox state keyed to the rendered ratio type
This stores selections under preferredType (e.g. auto-upgrading a model_ratio click to billing_expr), but table rendering and bulk stats still read by record.ratioType. As a result, the clicked row can remain visually unchecked and bulk unselect can miss the stored entry, so users can unintentionally apply hidden tiered selections they cannot clear from the same row.
Useful? React with 👍 / 👎.
Summary
Re-target of #19 onto
main. The original PR landed onfeature/sync-upstream-v1.0, but #17 was merged before #19 made itinto v1.0, so when v1.0 was merged into
mainit carried only #17'scontent — leaving the single commit
a3a49d41d feat(ratio-sync): port upstream pricing-endpoint sync UI to HeroUIstranded on the now-defunct sync branch.
This PR brings exactly that one commit (already reviewed in #19)
onto
main. No additional changes — pure rebase-by-targeting.Diff
Single commit, +278/-53 across:
web/classic/src/pages/Setting/Ratio/UpstreamRatioSync.jsx(the actual UI port)
web/classic/src/i18n/locales/*.json(4 new keys: 创建缓存倍率,图像倍率, 音频倍率, 阶梯计费表达式)
TODO.md(markf424f906dfollow-up as done)Why it landed this way
When I opened #19 the v1.0 sync PR (#17) was still open, so #19
based on
feature/sync-upstream-v1.0made sense — it kept #19'sdiff scoped to just the UI changes (instead of also re-displaying
the entire +171k merge). The plan was to retarget after #17 merged.
GitHub's PR retargeting + auto-merge ordering didn't catch the
sequencing, so #19 quietly merged into v1.0 milliseconds before v1.0
itself merged into main, missing the train.
Net effect on review surface area: zero — this is the same content
already reviewed in #19, presented against
mainso it can actuallyland.
Verification
git log origin/main..HEADshows only
a3a49d41d)Related
main)reached
main)This PR was generated automatically by Cursor
Made with Cursor