From bed3d3685561a5822112cce65cb6dca5166ca40f Mon Sep 17 00:00:00 2001 From: July-X Date: Mon, 9 Nov 2026 22:47:25 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(webui):=20=E8=B4=A6=E5=8F=B7=E8=BA=AB?= =?UTF-8?q?=E4=BB=BD=E8=A1=8C=E5=B1=95=E7=A4=BA=E5=A5=97=E9=A4=90=E5=90=8D?= =?UTF-8?q?=E5=B9=B6=E9=80=82=E9=85=8D=E6=96=B0=E8=80=81=E5=A5=97=E9=A4=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除身份行的“本地 · host:port”,未登录显示“请先登录账号!”,已登录显示账号的套餐名(来自配额接口)。 - 套餐名以账户返回为准:新 M Plan 档位(Go / Explore / Build)原样透传;旧账号标识 starter / plus / max / ultra / plus极速版 映射为显示名;订阅态解析不出档位时优先用 planName;数字 combo id 按 subscription_type 兜底为 Token Plan / M Plan。 - 套餐措辞模块发布为 usage-plan.public.ts 供账号业务复用,身份行与“用量与模型”页面共用同一套措辞。 - 同步更新设计文档、冻结路径清单和单元测试。 --- packages/webui/docs/design/design-language.md | 8 +- .../account/projection/usage.public.ts | 16 +++ .../client/features/account/ui/UserMenu.tsx | 12 +- .../settings/projection/usage-plan.public.ts | 110 ++++++++++++++++++ .../settings/projection/usage-plan.ts | 65 ----------- .../settings/ui/AccountUsageSettings.tsx | 2 +- .../settings/ui/UsageModelSettings.tsx | 2 +- packages/webui/src/client/main.tsx | 1 - .../client/shell/WebuiClientFoundationApp.tsx | 9 +- .../gates/fixtures/path-metadata-f00.json | 9 ++ .../test/unit/webui-account-billing.test.ts | 53 ++++++++- .../webui/test/unit/webui-usage-quota.test.ts | 38 +++++- release/public-source.json | 2 +- 13 files changed, 239 insertions(+), 88 deletions(-) create mode 100644 packages/webui/src/client/features/settings/projection/usage-plan.public.ts delete mode 100644 packages/webui/src/client/features/settings/projection/usage-plan.ts diff --git a/packages/webui/docs/design/design-language.md b/packages/webui/docs/design/design-language.md index 712315656..a9df1b0a8 100644 --- a/packages/webui/docs/design/design-language.md +++ b/packages/webui/docs/design/design-language.md @@ -108,9 +108,13 @@ styled with the same names the token file declares. a copy of it. A settings page brings its own content, never its own frame. - The 用量与模型 page names the account's plan, it does not name a product: the plan name, the model-source label and the 限额重置 section all follow the account's own answer - (`features/settings/projection/usage-plan.ts` and `features/settings/ui/UsageResetCards.tsx`). + (`features/settings/projection/usage-plan.public.ts` and `features/settings/ui/UsageResetCards.tsx`). A hard-coded plan name is a bug, not a default — the legacy "Token Plan" label belongs to the - legacy `token_plan` subscription kind alone. + legacy `token_plan` subscription kind alone. The account's tier is shown as the membership answer + reports it: the M Plan levels (Go / Explore / Build) read as reported, and the pre-upgrade + identifiers (starter / plus / max / ultra / plus极速版) read as their product display names. Any + other reported tier name passes through, and only a numeric combo id falls back to the kind's + generic plan name. - The settings pages share their controls too, not only their frame: `features/settings/ui/settings-atoms.tsx` owns the page's button (`SettingsButton`, with a `compact` size for a row action) and its dialog (`SettingsDialog` — a scrim, a surface, a header and a scrolling body). A control that each page diff --git a/packages/webui/src/client/features/account/projection/usage.public.ts b/packages/webui/src/client/features/account/projection/usage.public.ts index 07fd89261..c72edd729 100644 --- a/packages/webui/src/client/features/account/projection/usage.public.ts +++ b/packages/webui/src/client/features/account/projection/usage.public.ts @@ -8,6 +8,22 @@ // The text below is the declaration as it stood in the source module — no // behaviour change. +import type { WebuiUsageQuotaResult } from "../../../../shared/contracts/usage-quota.js"; +import { formatPlanName } from "../../settings/projection/usage-plan.public.js"; + +/** + * The line under the rail identity: the plan a signed-in account reports, the + * sign-in prompt for a signed-out one, and nothing until the quota answer + * arrives (or when it cannot be read). + */ +export function formatRailPlanLabel( + result: WebuiUsageQuotaResult | undefined, +): string | undefined { + if (!result) return undefined; + if (result.signedIn === false) return "请先登录账号!"; + return formatPlanName(result); +} + /** * Formats a quota reset timestamp: * `3小时13分后重置` below a day, `5天7小时后重置` once days are involved, diff --git a/packages/webui/src/client/features/account/ui/UserMenu.tsx b/packages/webui/src/client/features/account/ui/UserMenu.tsx index 3c55614a1..4a0d30a59 100644 --- a/packages/webui/src/client/features/account/ui/UserMenu.tsx +++ b/packages/webui/src/client/features/account/ui/UserMenu.tsx @@ -52,12 +52,12 @@ import { type SigninState, } from "./UserMenuSignin.js"; import { UsagePanel, type UsageState } from "./UserMenuUsage.js"; +import { formatRailPlanLabel } from "../projection/usage.public.js"; type AccountStatus = Record; interface UserMenuProps { readonly collapsed: boolean; - readonly hostLabel?: string; readonly dataDir?: string; readonly version?: WebuiVersionInfo; readonly sessionId?: string; @@ -151,7 +151,6 @@ function Chevron(): ReactElement { export function UserMenu({ collapsed, - hostLabel, dataDir, sessionId, version, @@ -285,10 +284,9 @@ export function UserMenu({ "MiniMax Code"; const avatarUrl = profile?.avatarUrl?.trim() || profile?.defaultAvatarUrl?.trim(); - const plan = - accountString(account, "plan") ?? - accountString(account, "planName") ?? - (hostLabel ? `本地 · ${hostLabel}` : "本地"); + const planLabel = formatRailPlanLabel( + usage.status === "ready" ? usage.result : undefined, + ); const identity = objectValue(account?.identity); const auth = objectValue(account?.auth); const uid = @@ -374,7 +372,7 @@ export function UserMenu({ {nickname} - {plan} + {planLabel ? {planLabel} : null}