You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit c96e7a2
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: docs/webui.md
+62-6Lines changed: 62 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1375,7 +1375,7 @@ snapshot is broadcast to every SSE subscriber.
1375
1375
| Row | Field | Missing-value sentence |
1376
1376
| --- | --- | --- |
1377
1377
| 账户名 | `identity.name`, trimmed | the engine answered and reported no name |
1378
-
| 当前套餐 | `tokenPlan.tier`, through the Token Plan card's own `planNameOf` | 「未订阅套餐」 when the engine reported no plan; the unread sentence when the surface itself failed |
1378
+
| 当前套餐 | `tokenPlan.tier`, through the Token Plan card's own `planNameOf` | 「未订阅套餐」 when the engine reported no plan; 「正在读取当前套餐…」 while a read is in flight; the unread sentence when the surface itself failed |
1379
1379
| 配额概况 | `tokenPlanQuotaState`, plus `quota.fiveHour` / `quota.weekly` `remainingPercent` | 「引擎未返回读数」 per window; 「不限量」 when the engine reports the window unmetered |
1380
1380
| 账户状态 | `status` | the unread sentence; a status token with no dictionary entry resolves to no sentence rather than leaking a raw enum |
1381
1381
@@ -1683,11 +1683,23 @@ return a mask while the engine rejects a mask submitted as a key.
1683
1683
the `minimax_api` provider and the response says so (`tested:
1684
1684
"stored_key"`). Two limits are the engine's contract, not this UI's:
1685
1685
`testUserModel` takes no key override, so an unsaved value cannot be
1686
-
probed — the button is disabled with a title saying so while the field
1687
-
holds one — and the managed Token Plan credential is not a model-service
1688
-
key, so the Token Plan source has nothing to probe here. A probe that
1689
-
ran and failed is a completed probe, not an error: it renders the
1690
-
engine's status.
1686
+
probed — the button is disabled while the field holds one, and a visible
1687
+
line under the field says why, because the reason used to live only in a
1688
+
`title`attribute that keyboard and touch users never see — and the
1689
+
managed Token Plan credential is not a model-service key, so the Token
1690
+
Plan source has nothing to probe here. A probe that ran and failed is a
1691
+
completed probe, not an error: it renders the engine's status.
1692
+
1693
+
**The key row is four states, not two.** The badge reads the engine's
1694
+
masked projection, but it is describing a field the user can be editing
1695
+
right now, so a typed-but-unsaved key is its own state (「已输入,未保存」)
1696
+
that outranks both 「已保存密钥」 and 「未启用」 — the user is replacing the
1697
+
stored key, or has plainly typed one, and neither badge is true. Symmetric
1698
+
to that, the engine's `NO_API_KEY` refusal is a verdict on an EMPTY field:
1699
+
typing one falsifies it, so the pinned 「请先填写 API Key」 is dropped on
1700
+
the next keystroke. A refusal that is not about the missing key — a
1701
+
transport failure, a rejected write — is still true afterwards and stays
1702
+
on screen.
1691
1703
1692
1704
**Cost.** One extra read per settings-tab open. The read boots the
1693
1705
engine runtime if none is up, which is the write-side contract and is
@@ -1702,6 +1714,50 @@ engine method behind it. The Token Plan cards stay on decision A1
1702
1714
(本地版不适用) — wiring them to `/api/usage` and `/api/account` is a
1703
1715
separate, undecided item, not a side effect of this one.
1704
1716
1717
+
### Usage & models: a source switch re-reads the account (P20)
1718
+
1719
+
**The defect this fixes.** A UAT round trip on 2026-10-03 (板块 4) switched
1720
+
Token Plan → MiniMax API → Token Plan with a key stored in between. The
1721
+
Token Plan card came back reading 「未订阅套餐」 while `GET /api/account`
1722
+
answered `tier: "Ultra"` throughout, and only F5 recovered it. The
1723
+
endpoint was never wrong.
1724
+
1725
+
**Root cause.** The plan name is read in `UsageModelsSection`
1726
+
(`webapp/components/panels.tsx`), mounted only while the port's view is on
1727
+
the token-plan tab, so a switch away and back remounts it. The read was
1728
+
`useEffect(..., [])` — once per mount — and that mount raced the engine:
1729
+
the section rendered in the same tick as `PUT /api/model-source`, and
1730
+
`GET /api/account`answers an engine that is still rebinding with HTTP
1731
+
**200** and `{ok: false, reason: "no_client"}`. The card read that as "no
1732
+
plan", nothing re-read it, and the section's state outlived the failure.
1733
+
1734
+
**The re-read.** The port owns an `accountRevision` counter and increments
1735
+
it after every *successful* source write — the dropdown's `PUT
1736
+
/api/model-source` and 保存并使用's `PUT /api/model-source/api-key`, which
1737
+
switches the source inside the same engine transaction. The section's
1738
+
`/api/account`effect lists that counter as a dependency, so a confirmed
1739
+
write re-runs the read against an engine that has finished rebinding. A
1740
+
refused write bumps nothing: nothing changed, and a re-read would only
1741
+
spend a request to re-render the same answer.
1742
+
1743
+
**A failed read may not un-know a name.** Revalidation alone is not enough,
1744
+
because the re-read can also lose the race. `reconciledAccount`
1745
+
(`webapp/components/usage-models-cards.tsx`, pure and unit-tested) keeps
1746
+
the last `ok: true` answer standing: only an `ok: true` payload is new
1747
+
information, so an unreachable account surface cannot knock a known plan
1748
+
off the card. An `ok: true` answer that reports no plan *does* replace it —
1749
+
the engine saying "no plan" is an answer, saying "unreachable" is not.
1750
+
1751
+
**A read in flight is its own sentence.** With the name held, the only
1752
+
remaining nameless state is "not read yet", and it renders 「正在读取当前
1753
+
套餐…」 rather than 「未订阅套餐」. An account surface that has not
1754
+
answered has not said the user has no plan — that conflation was the
1755
+
visible half of UAT4-1.
1756
+
1757
+
**Cost.** One `GET /api/account` per confirmed source write, on a surface
1758
+
the user has just acted on. The read is not debounced: a source switch is
1759
+
a deliberate act, not a stream of them.
1760
+
1705
1761
### Follow-up messages: the switch is a behaviour (SB-4)
1706
1762
1707
1763
**What the user sees.** 跟进消息行为 has three positions. While a task is
0 commit comments