feat(ratio-sync): port upstream pricing-endpoint sync UI to HeroUI#19
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 }, |
There was a problem hiding this comment.
Preserve full ratio maps when confirming conflict sync
performSync now always writes CreateCacheRatio, ImageRatio, AudioRatio, AudioCompletionRatio, and both billing_setting.* option blobs, but the conflict-confirm path still calls it with only the legacy four maps (ModelRatio, CompletionRatio, CacheRatio, ModelPrice). When a conflict modal is confirmed, these newly added maps are initialized from missing input and get saved as empty objects, which can wipe existing per-model values for those options during an otherwise unrelated sync.
Useful? React with 👍 / 👎.
| if (category === 'tiered') { | ||
| if (rtCategory !== 'tiered') delete newModelRes[rt]; | ||
| } else { | ||
| if (rtCategory !== category) delete newModelRes[rt]; | ||
| } |
There was a problem hiding this comment.
Keep tiered selections when adding price/ratio selections
This exclusivity branch deletes any previously selected fields from other billing categories, so selecting billing_expr and then selecting model_price/ratio (or vice versa) for the same model drops the earlier selection. That prevents applying tiered + ratio/price together in one sync for a model, even though later logic in this file treats tiered as an overlay and intentionally avoids price-vs-ratio conflict handling for tiered selections.
Useful? React with 👍 / 👎.
Summary
Re-implements the frontend half of upstream commits
f424f906d(sync upstream pricing from pricing endpoint) andcc4ad6c39(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 in #17.
Only the
web/classic/src/pages/Setting/Ratio/UpstreamRatioSync.jsxUIwas left on the old per-row-per-ratio-type table that didn't surface
the new sync fields.
Why a separate PR
PR #17 was already +171k/-2.4k from the upstream merge — adding 250+
more lines of feature work would push it past reviewable. Splitting
out the UI port gives a focused diff (+278/-53) where each hunk maps
1:1 to a documented behaviour change.
Base
Built on top of `feature/sync-upstream-v1.0` (PR #17). Will
re-target `main` once #17 merges; rebase is trivial because no
files outside the ones I touched in #17 are affected.
What ships
`audio_ratio`, `audio_completion_ratio`, plus the existing
`cache_ratio`. Module-level `RATIO_FIELDS` mirrors backend
`controller/ratio_sync.go pricingSyncFields`.
`billing_expr`). Coexists with ratio/price billing on the same
model — backend chooses at billing time. Mutually-exclusive
rules:
upgrades a non-expr selection to `billing_expr` whenever the
same source exposes a non-empty expression — one click captures
the full tiered config.
`billing_mode` (= `tiered_expr` if source didn't ship a mode);
selecting `billing_mode` auto-fills `billing_expr` if present.
warning runs against `RATIO_FIELDS` (not 3 hard-coded fields)
and skips tiered-only selections.
so explicit `0` (legal ratio for free models) survives round-trip;
tiered string fields never get NaN-ified. `applySync` best-effort-parses
each option to `{}` instead of throwing on a malformed JSON.
— `handleBulkSelect` / `handleRowSelect` / in-cell CompactCheckbox
thread the upstream name through so expression-priority resolution
can pick the right source.
Option-store keys upstream introduced (`billing_setting.billing_mode`,
`billing_setting.billing_expr`).
What deliberately doesn't ship
4 files (main / table / columns / helpers) AND moved to a
per-model-row layout where each row holds a vector of ratio types.
That's a divergent UI choice this fork would re-conflict on every
future sync. Kept the existing per-(model, ratioType) row layout —
same data, denser table.
i18n
4 new keys across all 8 locale files:
`音频补全倍率` and `计费模式` were already present from the v1.0
sync merge.
Verification
Backend dependencies
All shipped via PR #17:
(`GetPricingSyncData` exposes `billing_mode` + `billing_expr`)
This PR was generated automatically by Cursor
Made with Cursor