Skip to content

Show upstream balances in the quota / billing column - #316

Closed
fylorn wants to merge 1 commit into
devfrom
feat/upstream-balance-display
Closed

fylorn wants to merge 1 commit into
devfrom
feat/upstream-balance-display

Conversation

@fylorn

@fylorn fylorn commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Depends on the core balances PR (ThinkWatch-Core, branch feat/upstream-balances, contract E, protocol 46). Until core is released and the pin is bumped, the Rust side of this branch does not build: tw_api::ep::RefreshBalance is not in the pinned tw-api, and ts_bindings will report src/generated/tw-api.ts as out of date. TypeScript checks and tests pass.

What it does

The 额度 / 计费 column of the upstream table shows what an upstream reports about the money or allowance left on its key (ProviderView.balance). Upstreams without a balance are unchanged; disabled upstreams show the billing line, the same as for account quotas.

Balance Cell
total quota 「剩余 $42.18 / $100」, the account-quota bar, and「Sub2API 额度 · 1 分钟前读取」
time windows the tightest window, drawn like an account quota window (「已用 86%」, 「5 小时 · 2 小时后」, bar amber from 80 %, red at 100 %), then 「剩余 $1.40 / $10」; a window with scope: user adds 「账号额度」 in front (key adds nothing)
quota and windows whichever is used more is drawn
wallet 「余额 $18.40」 ($ USD, ¥ CNY, otherwise the code) over the billing line 「按量计费 · 默认价目表」
quota (or window) + wallet the wallet joins the same line: 「剩余 $42.18 / $100 · 余额 $5」
only spent 「本月已用 $12.40」 / 「今日已用」 / 「累计已用」, no bar, over the billing line
error, nothing read before amber tag 「余额读取失败」, the reason on hover, the billing line
error with an older reading the reading; the failure is only in the hover

Token and request amounts are counts (「1.2M / 5M token」, 「320 / 1,000 次」). The hover lists the source and read time, the wallet, what was spent, the quota and every current window with its scope and exact amounts, the expiry date and the last failure. Windows whose reset time has passed are left out, the same rule as account quotas. daily, monthly and <n>w windows get names (每天, 每月, 1 周).

Also:

  • The row menu has 「刷新余额」 for an upstream with a balance. It calls POST /providers/{name}/balance/refresh and shows a toast that turns into what was read, or into the failure with its reason; the overview is re-read afterwards.
  • Check connection shows the balance it read on a second line under the OK line (TestLine), amber when the read failed.
  • balance_updated re-reads the overview the way auth_changed / proxy_changed do (same coalescing in useCoreLink).

Formatting lives in src/upstreams/balance.ts (pure, with balance.test.ts covering quota, windows incl. the amber threshold and scopes, token / request units, wallet USD / CNY / other, mixed, spent, error with and without a reading, and null) and balance.i18n.ts (both languages).

Protocol types added by hand

To src/generated/tw-api.ts, matching the core branch's names: Balance, BalanceQuota, BalanceWindow, BalanceScope, BalanceSource, Money, Spent, SpentPeriod; ProviderView.balance: Balance | null; ProviderTestResult.balance (written optional here so the dialog's own failure result in UpstreamDialog.tsx, which is being rewritten in a parallel PR, still type-checks; core sends it as Balance | null); the event { kind: "balance_updated", id, provider, at_ms }; the endpoint RefreshBalance in ENDPOINTS / Endpoints. RefreshBalance is added to the webview call table in both src/control.ts and src-tauri/src/call.rs. ProviderView.balance_setting and ProviderInput.balance are left to the wizard PR. Regenerating the file after the pin bump replaces all of this.

The screenshot pipeline's stored overviews (scripts/shots/core/*/overview.json) get "balance": null per upstream so the mock type-checks; oracle.sh regenerates them after the pin bump. Its mock refuses RefreshBalance like every other write.

Verification

  • pnpm typecheck: passes.
  • pnpm test: 105 files, 1118 tests pass.
  • cargo fmt --manifest-path src-tauri/Cargo.toml --all --check: passes. Clippy and cargo test need the core bump (see above).
  • Visual check in the local full-app preview harness (not part of the repo) on ?page=upstreams, light, dark and English. The harness mock gave its upstreams one balance per shape: openrouter a USD wallet with a failed later read, deepseek a CNY wallet, relay-hk a Sub2API quota, relay-tw quota + wallet, relay-sg 5h / daily windows (86 %, amber), oneapi-lab a New API read failure, corp-gw ThinkWatch windows in USD, tokens and requests with key and account scopes (30-day account window at 82 % drawn), corp-dev only spent this month. Its RefreshBalance re-reads and emits balance_updated; TestProvider returns the balance of the upstream at the same address. Checked: the hovers, the row menu, refresh success and failure toasts, and Check connection for a window, a mixed and a failed balance.

Not covered

  • Chinese text for core's new balance messages (read failures) goes into src/i18n/core.zh.json once the core PR lists its message codes; until then the reason shows in English.
  • With quota and wallet together, the line is cut short in the 160 px column (「剩余 $18.50 / $50 · 余…」); the full text is in the hover. The column does not widen, so upstream names keep their room in narrow windows.

🤖 Generated with Claude Code

Relays and pay-as-you-go services report what is left on the key: a
wallet, a total quota, time windows, or only what was spent. Core now
reads that (contract E, protocol 46) and this shows it where the column
already answers "how much is left": the tightest of quota and windows is
drawn with the account-quota bar (amber from 80 %), a wallet alone reads
"Balance $18.40" over the billing line, a key without limits reads what
it spent this month, and a failed read with nothing to show is an amber
tag with the reason on hover. An older reading stays on screen when the
latest read fails; the failure is in the hover, which lists every window
(with its key / account scope), the exact amounts and the expiry.

The row menu gets "Refresh balance", Check connection shows the balance
it read under the OK line, and balance_updated re-reads the overview
like the other upstream-state events.

The protocol types are added to src/generated/tw-api.ts by hand until
the pinned core is bumped; the Rust side does not build against the
current pin because tw_api::ep::RefreshBalance does not exist there yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fylorn

fylorn commented Oct 10, 2026

Copy link
Copy Markdown
Contributor Author

Merged as part of #318 (2026.10.14).

@fylorn fylorn closed this Oct 10, 2026
@fylorn
fylorn deleted the feat/upstream-balance-display branch October 10, 2026 17:00
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.

1 participant