Skip to content

Commit 32059af

Browse files
committed
feat: refine account and live session surfaces
1 parent ded74c7 commit 32059af

13 files changed

Lines changed: 156 additions & 97 deletions

File tree

docs-linhay/memory/2026-05-26.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828

2929
## 账号页头动作菜单收口
3030
- 决策:`#frame=accounts` 的独立 `+ ADD ACCOUNT` 按钮不再单独占据 header 动作区,改为收进右侧 actions menu 的第一项;同一个菜单继续承载 `ChatGPT 登录``导入 Auth File``粘贴账号内容``添加 Codex API Key``轮动设置`,避免 header 再出现一层独立入口。
31-
- 实现:`AccountsHeader` 新增扁平菜单模型与图标映射,菜单项使用纯行式 hover / active 状态,不再套用 card shell;`+ ADD ACCOUNT` 保持原文案但只在菜单中出现
32-
- 验证:本地预览确认 header 外只剩刷新与菜单按钮,菜单展开后首项为 `+ ADD ACCOUNT`,并且菜单项没有 border/shadow 卡片壳。新增单测 `frontend/src/features/accounts/tests/accountHeaderMenu.test.mjs` 已通过。
31+
- 实现:`AccountsHeader` 新增扁平菜单模型与图标映射,菜单项使用纯行式 hover / active 状态,不再套用 card shell;首项图标保留加号,文案去掉前缀 `+`,避免视觉上叠出两个加号
32+
- 验证:本地预览确认 header 外只剩刷新与菜单按钮,菜单展开后首项为 `ADD ACCOUNT`,并且菜单项没有 border/shadow 卡片壳。新增单测 `frontend/src/features/accounts/tests/accountHeaderMenu.test.mjs` 已通过。
3333
- 补充:用户反馈菜单项字号过小后,将 actions menu 行字号提升到 `--font-size-ui-md`,图标同步提升到 20px;Playwright 复核 5 个菜单项实际字号为 12px、行高 44px,且无横向溢出。
3434
- 归档:验收截图已保存到 `docs-linhay/spaces/20260526-account-provider-picker-icons/screenshots/20260526/accounts/20260526-accounts-header-actions-menu-after-v01.png`
3535
- 归档:字号修正截图已保存到 `docs-linhay/spaces/20260526-account-provider-picker-icons/screenshots/20260526/accounts/20260526-accounts-header-menu-font-after-v01.png`
@@ -158,6 +158,12 @@
158158
- 沉淀:`gettokens-domain-engineering` 的 Codex live sessions 小节新增了时间线单行化、核心指标前置和去“卡中卡”边界;`docs-linhay/dev/20260523-session-distillation-codex-live-sessions-ui.md` 也补了对应段落。
159159
- 不纳入:这仍然是 runtime observability 的 UI 密度规则,不升级为 repo-wide 的 `AGENTS.md` 规范。
160160

161+
## 账号厂商选择弹窗本地化与品牌图标收口
162+
- 决策:账号页新增弹窗继续沿用统一 `UnifiedComposeModal`,但把 provider 选择卡片收窄为“本地图标 / 字母徽记 + 短名称 + 短格式标签”,并把所有剩余静态文案收口到 `accounts.unified_compose_*` locale key。
163+
- 实现:`vendorIcons.ts` 本地嵌入 Simple Icons 路径数据,`VendorLogoMark` 统一渲染 logo / initials fallback;`unifiedComposeCopy.ts` 把标题、搜索、分类、端点、label、advanced、billing 等文案统一收口到 locale。
164+
- 验证:`node --test frontend/src/features/accounts/tests/accountPresentation.test.mjs``npm --prefix frontend run typecheck` 通过;Storybook 预览确认 provider list 里官方与常见厂商显示品牌图形,兜底厂商显示字母徽记,配置步骤里不再泄漏 locale key。
165+
- 归档:provider picker 截图已保存到 `docs-linhay/spaces/20260526-account-provider-picker-icons/screenshots/20260526/accounts/20260526-accounts-provider-picker-icons-after-v01.png`
166+
161167
## Codex live sessions 未知项目根因
162168
- 结论:线上最新 App 仍显示“未知项目”的直接原因是 live snapshot 里没有 `projectName`;前端和 Wails 只是透传 sidecar 字段并在缺失时显示 fallback。
163169
- 根因:本机 8317 端口上残留了 2026-05-24 启动的 `/Applications/GetTokens.app/Contents/MacOS/cli-proxy-api` 孤儿进程。新 App 的端口检测只尝试 `127.0.0.1:<port>`,未覆盖旧进程的 wildcard IPv6 监听,导致误判 8317 可用并把健康检查/管理接口连到旧 sidecar。
@@ -247,3 +253,15 @@
247253
- 追加:列表模式的 badge 区继续收敛,只显示套餐信息,不再展示 `ANTH` / `OAI RESP` 这类格式标签;新截图归档为 `docs-linhay/screenshots/20260526/accounts/20260526-accounts-list-row-redesign-after-v04.png`
248254
- 追加:如果某行没有套餐 badge,也要保留 badge 列的占位宽度,避免右侧指标 / 动作整体左移;最新验收截图归档为 `docs-linhay/screenshots/20260526/accounts/20260526-accounts-list-row-redesign-after-v05.png`
249255
- 追加:最终收敛为“状态文本 + 套餐 + 近期请求 + 累计 Token + 剩余/额度”一条 `·` 串联文本,右侧只保留动作按钮;最新验收截图归档为 `docs-linhay/screenshots/20260526/accounts/20260526-accounts-list-row-redesign-after-v07.png`
256+
257+
## 账号池启动只显示 API Key 问题
258+
- 现象:启动后账号池首屏只显示 3 个 API Key / 兼容服务账号,但本地 `~/.config/gettokens` 实际存在 16 个 auth-file,sidecar ready 后 `/v0/management/auth-files` 可正常返回 16 条。
259+
- 根因:`shouldLoadAccountsData` 在 Wails 模式下把 `sidecarStatus.code === "stopped"` 也当成可加载,页面可能在 sidecar 尚未 ready 时先加载到只有本地 Codex API Key 的局部快照,并把 `accountsLoaded` 置为 true,导致后续 ready 状态不一定重新拉全量 auth-files。
260+
- 修复:Wails 模式下仅 `sidecarStatus.code === "ready"` 才触发账号加载;浏览器预览仍允许无 Wails bindings 时加载 preview 数据。
261+
- 验证:`npm --prefix frontend run test:unit -- src/features/accounts/tests/accountPresentation.test.mjs` 通过,实际执行脚本会附带全量 unit 列表并全部通过。
262+
263+
## routing.strategy 完整绕过收口
264+
- 决策:Codex / Claude 请求侧的账号选择以 `~/.config/gettokens-data/channel-routing/config.json` 为唯一主路径,旧 `routing.strategy` 只保留为 legacy relay / 配置兼容边界,不再参与 channel routing 决策。
265+
- 实现:CLIProxyAPI fork 新增 `channelRoutingRoutePolicy` 并在 `InstallRoutePolicyHook` 中优先注册;该 policy 将 sidecar 候选账号转换为 `gettokensrouting` 快照,调用 `DecideChannelRoute` 输出候选顺序,从而覆盖旧 `FillFirstSelector` 主路径。
266+
- 补充:balanced 模式的活跃会话计数直接读取 live session tracker 的轻量计数,不再通过完整 snapshot 汇总,避免路由热路径被展示快照牵连。
267+
- 验证:`go test ./internal/gettokenshooks -count=1``go test ./internal/cmd -count=1``go test ./internal/gettokensrouting ./internal/gettokenshooks ./internal/cmd ./sdk/cliproxy/auth -count=1` 全部通过。

docs-linhay/references/CLIProxyAPI

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Subproject commit 5db7768ae54962f185f9fe4080b4b137a8b4a098
1+
Subproject commit 1c0f003145c3c93fcabe193c7020d71bf2d5ac62

docs-linhay/spaces/20260524-account-routing-engine/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ sidecar 现有账号轮动能力已经包含 `round-robin`、`fill-first`、prio
134134
## 当前状态
135135
- 状态:implementation-ready
136136
- 最近更新:2026-05-26
137+
- 2026-05-26 补充:sidecar 请求侧已完成 `routing.strategy` 主路径绕过,Codex / Claude 请求优先通过 `channel-routing` 快照生成候选顺序;`routing.strategy` 仅作为 legacy relay / 配置兼容边界保留。
137138
- 2026-05-26 补充:Codex / Claude 的 channel routing 进入“完整绕过 `routing.strategy`”收口,后续以 `channel-routing` 快照作为唯一决策源;旧 `config.yaml` 只保留 legacy relay 边界,不再参与渠道路由主路径。
138139
- 2026-05-26 补充:Codex 账号列表的旧 `session-affinity` / `websocket-pin` / `route-order-header` 现在只作为 `兼容层提示` 的总数与说明呈现,不写入新的 `ChannelRoutingConfig`,也不展开三条明细,以便后续继续和上游代码保持最小合并面。
139140
- 2026-05-26 补充:Codex / Claude 路由工作台主界面进一步降噪,只保留当前请求模式和参与账号列表;诊断、预演、Shadow、候选/过滤和最近路由默认隐藏。验收截图:`screenshots/20260526/codex/20260526-channel-routing-workbench-desktop-after-v05.png``screenshots/20260526/codex/20260526-channel-routing-workbench-mobile-collapsed-after-v05.png`

docs-linhay/spaces/20260524-account-routing-engine/plans/routing-strategy-bypass-v01.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,8 @@
5959
2. 再拆读写链路,断开 `routing.strategy` 对 channel routing 的影响。
6060
3. 最后补文档、记忆和 qmd 索引。
6161

62+
## 进展记录
63+
64+
- 2026-05-26:完成 channel routing 对 `routing.strategy` 的请求侧绕过收口,`InstallRoutePolicyHook` 现在优先注册 `channelRoutingRoutePolicy`,并且活跃会话计数直接读取 live tracker,不再走 snapshot 汇总。
65+
- 2026-05-26:新增回归测试,覆盖 balanced 模式在 `routing.strategy: fill-first` 存在时仍按 channel routing 选择账号,以及 `InstallGetTokensHooks` 不再退回 fill-first 选择器。
66+
- 2026-05-26:焦点验证通过:`go test ./internal/gettokenshooks -count=1``go test ./internal/cmd -count=1``go test ./internal/gettokensrouting ./internal/gettokenshooks ./internal/cmd ./sdk/cliproxy/auth -count=1`

docs-linhay/spaces/20260526-account-provider-picker-icons/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Account Provider Picker Icons
22

33
## 背景
4-
账号页的 `+ ADD ACCOUNT` 入口已经收敛到头部 actions menu 中的第一项。弹窗一次性展示大量厂商和格式标签,当前卡片主要依赖大写文本,扫读压力偏高,也没有把 `vendorPresets` 中已有的 `icon / iconColor` 信息真正呈现出来。
4+
账号页的添加账号入口已经收敛到头部 actions menu 中的第一项,由加号图标承担动作提示,文案不再额外带 `+`。弹窗一次性展示大量厂商和格式标签,当前卡片主要依赖大写文本,扫读压力偏高,也没有把 `vendorPresets` 中已有的 `icon / iconColor` 信息真正呈现出来。
55

66
## 目标
77
- 让厂商选择卡片优先通过品牌图形和短名称识别,降低首屏文字密度。
@@ -19,7 +19,7 @@
1919
- 不引入新的运行时远端图片依赖。
2020

2121
## 验收标准
22-
1. Given 用户打开 `#frame=accounts` 并展开头部 actions menu,再点击 `+ ADD ACCOUNT`,When 厂商选择弹窗出现,Then 每个厂商卡片都有品牌图形或字母徽记。
22+
1. Given 用户打开 `#frame=accounts` 并展开头部 actions menu,再点击添加账号入口,When 厂商选择弹窗出现,Then 每个厂商卡片都有品牌图形或字母徽记。
2323
2. Given 选择弹窗展示官方、国内厂商和聚合商列表,When 用户扫读卡片,Then 卡片主名称使用短名称,格式标签使用短格式,不再堆叠长格式文案。
2424
3. Given 某厂商没有可用官方 SVG,When 该厂商出现在列表,Then 使用品牌色或默认色的字母徽记兜底,并保留完整厂商名作为可访问标签。
2525
4. Given 用户选择某个 preset 进入配置步骤,When 查看顶部厂商摘要和 endpoint 列表,Then 摘要继续展示图标与短格式标签,账号创建流程不变。
@@ -39,8 +39,9 @@
3939
- 页面:`http://localhost:5173/#frame=accounts`
4040

4141
## 当前状态
42-
- 状态:implementing
42+
- 状态:completed
4343
- 最近更新:2026-05-26
44-
- 备注:`+ ADD ACCOUNT` 已从 header 独立按钮收口到 actions menu 的第一项,后续图标与布局优化继续围绕同一入口推进
44+
- 备注:添加账号入口已从 header 独立按钮收口到 actions menu 的第一项;首项保留加号图标,文案不再额外带 `+`;统一新增弹窗已完成 provider 图标 / 字母徽记、短名称、短格式标签和中英本地化收口
4545
- 验收截图:`docs-linhay/spaces/20260526-account-provider-picker-icons/screenshots/20260526/accounts/20260526-accounts-header-actions-menu-after-v01.png`
4646
- 字号修正截图:`docs-linhay/spaces/20260526-account-provider-picker-icons/screenshots/20260526/accounts/20260526-accounts-header-menu-font-after-v01.png`
47+
- provider picker 截图:`docs-linhay/spaces/20260526-account-provider-picker-icons/screenshots/20260526/accounts/20260526-accounts-provider-picker-icons-after-v01.png`

frontend/src/features/accounts/components/accountHeaderMenu.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function buildAccountsHeaderMenuItems({
4040
items.push({
4141
id: 'unified-compose',
4242
labelKey: 'accounts.add_account',
43-
label: '+ ADD ACCOUNT',
43+
label: 'ADD ACCOUNT',
4444
icon: 'plus',
4545
disabled: false,
4646
emphasis: true,

frontend/src/features/accounts/model/accountRuntime.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ import type { SidecarStatus } from '../../../types';
22

33
export function shouldLoadAccountsData(sidecarStatus: SidecarStatus, hasWailsBindings: boolean) {
44
if (!hasWailsBindings) return true;
5-
return sidecarStatus?.code === 'ready' || sidecarStatus?.code === 'stopped';
5+
return sidecarStatus?.code === 'ready';
66
}

frontend/src/features/accounts/tests/accountHeaderMenu.test.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ test('buildAccountsHeaderMenuItems keeps all add-account actions in one flat men
2828
],
2929
);
3030
assert.equal(items[0].labelKey, 'accounts.add_account');
31-
assert.equal(items[0].label, '+ ADD ACCOUNT');
31+
assert.equal(items[0].label, 'ADD ACCOUNT');
32+
assert.equal(items[0].icon, 'plus');
3233
assert.equal(items[0].emphasis, true);
3334
assert.equal(items[1].dividerBefore, true);
3435
assert.equal(items[5].dividerBefore, true);

frontend/src/features/accounts/tests/accountPresentation.test.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ test('shouldLoadAccountsData allows browser preview data without Wails bindings'
4444

4545
test('shouldLoadAccountsData waits for ready sidecar when Wails bindings exist', () => {
4646
assert.equal(shouldLoadAccountsData({ code: 'running' }, true), false);
47+
assert.equal(shouldLoadAccountsData({ code: 'stopped' }, true), false);
4748
assert.equal(shouldLoadAccountsData({ code: 'ready' }, true), true);
4849
});
4950

frontend/src/features/channel-routing/components/ChannelRoutingWorkbench.tsx

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
Shuffle,
1010
Split,
1111
X,
12-
Zap,
1312
} from 'lucide-react';
1413
import { useEffect, useState } from 'react';
1514
import ModalFrame from '../../../components/ui/ModalFrame';
@@ -81,6 +80,7 @@ export default function ChannelRoutingWorkbench({
8180
const legacyMask = buildLegacyRoutingMaskPanel();
8281
const eventSummaries = routeEvents.slice(0, 5).map((event) => buildChannelRouteAuditEventSummary(event));
8382
const hasExplain = explainView.hasExplain;
83+
const activeRouteMode = routeModes.find((item) => item.mode === config.routeMode) ?? routeModes[0];
8484
const shadowPanelLabel = config.shadowEnabled ? (hasExplain ? explainView.shadowLabel : '开启') : '关闭';
8585
const shadowPanelMeta = config.shadowEnabled && hasExplain ? explainView.shadowMeta : '';
8686
const participantRows = buildChannelRoutingParticipantRows(config, accounts);
@@ -111,30 +111,41 @@ export default function ChannelRoutingWorkbench({
111111
>
112112
<header className="p-4">
113113
<div className="flex min-w-0 flex-wrap items-center justify-between gap-3">
114-
<div className="flex min-w-0 items-center gap-2">
115-
<Zap className="h-4 w-4 shrink-0 text-[var(--text-secondary)]" strokeWidth={3} />
116-
<h2 className="min-w-0 text-[length:var(--font-size-ui-lg)] font-black leading-5 tracking-[0] text-[var(--text-primary)] sm:text-[length:var(--font-size-heading-sm)] sm:leading-normal">
117-
请求模式
118-
</h2>
119-
<button
120-
type="button"
121-
onClick={() => setHelpOpen(true)}
122-
aria-label="查看请求模式说明"
123-
title="查看请求模式说明"
124-
aria-pressed={helpOpen}
125-
className={`flex h-8 w-8 shrink-0 items-center justify-center border-2 transition-colors active:scale-95 ${
126-
helpOpen
127-
? 'border-[var(--text-primary)] bg-[var(--text-primary)] text-[var(--bg-main)]'
128-
: 'border-[var(--border-color)] bg-[var(--bg-main)] text-[var(--text-primary)] [@media(hover:hover)]:hover:border-[var(--text-primary)]'
129-
}`}
130-
>
131-
<CircleHelp className="h-4 w-4" strokeWidth={4} />
132-
</button>
133-
{preview ? (
134-
<span className="font-mono text-[length:var(--font-size-ui-2xs)] font-black uppercase tracking-wide text-[var(--text-muted)]">
135-
预览
136-
</span>
137-
) : null}
114+
<div className="flex min-w-0 items-start gap-3">
115+
<div className="flex h-11 w-11 shrink-0 items-center justify-center border-2 border-[var(--border-color)] bg-[var(--bg-main)] text-[var(--text-primary)]">
116+
<Split className="h-4 w-4" strokeWidth={4} />
117+
</div>
118+
<div className="min-w-0">
119+
<div className="flex min-w-0 flex-wrap items-center gap-2">
120+
<h2 className="min-w-0 text-[length:var(--font-size-ui-lg)] font-black leading-5 tracking-[0] text-[var(--text-primary)] sm:text-[length:var(--font-size-heading-sm)] sm:leading-normal">
121+
请求模式
122+
</h2>
123+
<button
124+
type="button"
125+
onClick={() => setHelpOpen(true)}
126+
aria-label="查看请求模式说明"
127+
title="查看请求模式说明"
128+
aria-pressed={helpOpen}
129+
className={`flex h-8 w-8 shrink-0 items-center justify-center border-2 transition-colors active:scale-95 ${
130+
helpOpen
131+
? 'border-[var(--text-primary)] bg-[var(--text-primary)] text-[var(--bg-main)]'
132+
: 'border-[var(--border-color)] bg-[var(--bg-main)] text-[var(--text-primary)] [@media(hover:hover)]:hover:border-[var(--text-primary)]'
133+
}`}
134+
>
135+
<CircleHelp className="h-4 w-4" strokeWidth={4} />
136+
</button>
137+
</div>
138+
<div className="mt-1 flex min-w-0 flex-wrap items-center gap-2 text-[length:var(--font-size-ui-sm)] font-black leading-5 text-[var(--text-secondary)]">
139+
<span className="text-[var(--text-primary)]">{channel === 'codex' ? 'Codex' : 'Claude Code'}</span>
140+
<span aria-hidden="true">/</span>
141+
<span>{activeRouteMode.label}模式</span>
142+
{preview ? (
143+
<span className="border-2 border-[var(--border-color)] bg-[var(--bg-main)] px-2 py-0.5 text-[var(--text-primary)]">
144+
预览
145+
</span>
146+
) : null}
147+
</div>
148+
</div>
138149
</div>
139150
<div className="grid min-w-0 flex-1 gap-2 sm:max-w-[28rem] sm:flex-none sm:grid-cols-2">
140151
{routeModes.map((item) => (
@@ -359,21 +370,14 @@ function StrategyButton({
359370
onClick={() => onModeChange(mode)}
360371
disabled={disabled}
361372
aria-pressed={active}
362-
className={`grid min-h-10 grid-cols-[1.5rem_minmax(0,1fr)_auto] items-center gap-2 border-2 px-3 py-2 text-left transition-colors active:scale-[0.98] ${
373+
className={`grid min-h-10 grid-cols-[1.5rem_minmax(0,1fr)] items-center gap-2 border-2 px-3 py-2 text-left transition-colors active:scale-[0.98] ${
363374
active
364375
? 'border-[var(--text-primary)] bg-[var(--text-primary)] text-[var(--bg-main)]'
365376
: 'border-[var(--border-color)] bg-[var(--bg-main)] text-[var(--text-primary)] [@media(hover:hover)]:hover:border-[var(--text-primary)]'
366377
}`}
367378
>
368379
<Icon className="h-3.5 w-3.5 shrink-0" strokeWidth={4} />
369380
<span className="min-w-0 truncate text-[length:var(--font-size-ui-sm)] font-black">{label}</span>
370-
<span
371-
className={`min-w-0 truncate font-mono text-[length:var(--font-size-ui-2xs)] font-black uppercase tracking-wide ${
372-
active ? 'text-[var(--bg-main)]/75' : 'text-[var(--text-muted)]'
373-
}`}
374-
>
375-
{active ? '当前' : cue}
376-
</span>
377381
</button>
378382
);
379383
}

0 commit comments

Comments
 (0)