Skip to content

Commit 5c0ce9a

Browse files
committed
feat: refine account import preview layout
1 parent f22688e commit 5c0ce9a

5 files changed

Lines changed: 166 additions & 34 deletions

File tree

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@
4747
- CPA 自动转换继续复用 app 内 `UploadAuthFiles` 后端归一化路径,前端不复制 ChatGPT Session / 9router OAuth 字段映射。
4848
- 候选队列项现在会展示解析后的内容预览,帮助用户在提交前核对每个导入对象的实际内容。
4949
- 预览证据已归档:`docs-linhay/screenshots/20260528/accounts/20260528-accounts-import-modal-preview-after-v01.png`
50+
- 交接说明已补充:`docs-linhay/spaces/20260528-account-import-unified-modal/handoff-20260528-account-import-preview.md`
5051
- 追补验证中账号导入 targeted tests、typecheck、完整 `test:unit`、build、build-storybook 均通过;此前 `SessionPluginConsolePanel.tsx` 设计系统 manifest 不一致已消除。
5152
- 此前整体验证通过:账户相关 targeted node tests、`npm --prefix frontend run typecheck``npm --prefix frontend run test:unit``npm --prefix frontend run build``npm --prefix frontend run build-storybook`
53+
- 追加要求已处理:`AccountImportModal` 候选队列项改为账号卡片样式,主体改为左侧合并输入面板、右侧账号预览队列;`resolveAccountImportPayloadPreview` 的脱敏行为已由测试覆盖。验证通过:账号导入 targeted tests `30 pass / 0 fail``npm --prefix frontend run typecheck`、完整 `npm --prefix frontend run test:unit` `609 pass / 0 fail`。浏览器预览功能冒烟确认导入 modal 可打开、粘贴 auth JSON 可加入候选、预览脱敏、移除候选可恢复空队列。
5254

5355
## CLIProxyAPI 日常上游合并
5456
-`20260527-cliproxyapi-upstream-merge` 方案完成 2026-05-28 日常同步:`upstream/main` 合并到 fork `gettokens/sidecar`,新增 upstream tag `v7.1.24`
@@ -149,3 +151,7 @@
149151
- 已将 Codex live sessions 的 `projectName` 加入大小写不敏感搜索索引,并把 placeholder 补充为可搜索项目。
150152
- 会话管理页新增纯过滤 helper:项目名命中时左侧项目列表匹配,右侧保留该项目当前状态筛选下的会话;会话 ID、文件名、Provider 和角色摘要也进入大小写不敏感搜索索引。
151153
- 验证通过:`node --test frontend/src/features/codex-live-sessions/model.test.mjs``node --test frontend/src/features/session-management/model.test.mjs``npm --prefix frontend run typecheck`
154+
155+
## 账号导入统一 modal 交接复核
156+
- 复核接手 `docs-linhay/spaces/20260528-account-import-unified-modal/handoff-20260528-account-import-preview.md`:账号导入聚焦 tests `30 pass / 0 fail`,当前工作区 `typecheck` 与完整 `npm --prefix frontend run test:unit` 均通过。
157+
- 浏览器预览确认统一导入入口可打开 modal,粘贴 auth JSON 可加入候选队列,预览会脱敏敏感字段;移除候选后队列恢复为空且提交按钮重新禁用。

docs-linhay/spaces/20260528-account-import-unified-modal/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ And 已有账户列表不受影响
8383

8484
- 前序 space:[多格式自动检测转 CPA 上传](../20260523-cpa-auto-detect-upload/README.md)
8585
- 实施计划:[Plan v01](plans/20260528-account-import-unified-modal-plan-v01.md)
86+
- 交接说明:[账号导入统一 modal 交接说明](handoff-20260528-account-import-preview.md)
8687
- 账户页实现:`frontend/src/features/accounts/`
8788
- 账户导入解析:`frontend/src/features/accounts/model/accountTransfer.ts`
8889
- 账户导入弹窗:`frontend/src/features/accounts/components/AccountImportModal.tsx`
@@ -99,6 +100,8 @@ And 已有账户列表不受影响
99100
4. 文件候选和粘贴 auth-file 候选继续走 `UploadAuthFiles`,CPA 自动转换仍复用现有 app 内后端归一化路径。
100101
5. 设计系统侧已用 `AccountModalComponents.stories.tsx``componentManifest.ts` 收编统一导入 modal。
101102
6. 队列项现在直接展示解析后的内容预览,便于提交前核对导入对象。
103+
7. 2026-05-28 追加:候选队列项从普通分隔列表行改为复用账号卡片视觉骨架,外层使用 `data-account-card``card-swiss`,顶部展示标题、来源和类型 badge,底部保留解析内容预览。
104+
8. 2026-05-28 追加:导入 modal 主体改为左右布局,左侧合并“选择文件”和“粘贴输入”,右侧固定为账号预览候选队列。
102105

103106
## 验证
104107

@@ -109,3 +112,7 @@ And 已有账户列表不受影响
109112
- `npm --prefix frontend run build-storybook`
110113
- 队列预览截图:`docs-linhay/screenshots/20260528/accounts/20260528-accounts-import-modal-preview-after-v01.png`
111114
- 2026-05-28 追补验证:账号导入 targeted tests、`npm --prefix frontend run typecheck``npm --prefix frontend run test:unit``npm --prefix frontend run build``npm --prefix frontend run build-storybook` 通过。
115+
- 交接前复核:`npm --prefix frontend run test:unit` 通过,完整单测 `602 pass / 0 fail`
116+
- 2026-05-28 账号卡片与左右布局追加验证:`node --test frontend/src/features/accounts/tests/accountTransfer.test.mjs frontend/src/features/accounts/tests/accountHeaderMenu.test.mjs frontend/src/features/accounts/tests/accountCardInteractions.test.mjs` 通过,`30 pass / 0 fail``npm --prefix frontend run typecheck` 通过;`npm --prefix frontend run test:unit` 通过,完整单测 `609 pass / 0 fail`
117+
- 2026-05-28 浏览器预览 DOM 验收:`http://127.0.0.1:4173/?preview=accounts#frame=accounts` 中打开导入 modal,粘贴 auth JSON 后候选项渲染为 `data-account-card` / `card-swiss ... flex ... p-0`,并显示 `AUTH FILE` 与脱敏内容预览。
118+
- 2026-05-28 功能冒烟:浏览器预览中“账号操作菜单 -> 导入账号”可打开 modal;粘贴 auth JSON 可加入候选队列,预览敏感字段脱敏;点击“移除候选”后队列恢复为空且提交按钮重新禁用。
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# 账号导入统一 modal 交接说明
2+
3+
## 交接范围
4+
5+
本交接覆盖 GetTokens 账号导入 / 粘贴统一 modal 的收尾状态,重点是用户补充要求:候选队列中需要显示解析后的内容。
6+
7+
## 当前状态
8+
9+
- 状态:已实现,可交接。
10+
- 主入口:`frontend/src/features/accounts/components/AccountImportModal.tsx`
11+
- 解析与预览:`frontend/src/features/accounts/model/accountTransfer.ts`
12+
- 相关测试:`frontend/src/features/accounts/tests/accountTransfer.test.mjs`
13+
- 设计系统入口:`frontend/src/features/accounts/components/AccountModalComponents.stories.tsx`
14+
- 截图证据:`docs-linhay/screenshots/20260528/accounts/20260528-accounts-import-modal-preview-after-v01.png`
15+
16+
## 已完成内容
17+
18+
1. 账户页上传文件和粘贴内容已合并为一个 `AccountImportModal`
19+
2. 同一个 modal 内支持多文件添加、粘贴 JSON 对象、粘贴 JSON 数组。
20+
3. 粘贴数组会拆成多个候选项,候选队列支持逐项移除。
21+
4. 复制的账号卡 payload 保持原语义:API Key、OpenAI-compatible provider、auth-file 分别按原类型进入导入流程。
22+
5. 普通 JSON 对象作为 auth-file 候选进入后端上传归一化链路,CPA 自动转换继续复用 app 内现有 `UploadAuthFiles` 后端路径。
23+
6. 候选队列行现在显示解析后的内容预览:
24+
- `upload-file`:解码 base64 后展示文件内容预览,预览解码只读取前段内容,避免大文件卡顿。
25+
- `auth-file`:展示解析后的 auth JSON 内容。
26+
- `codex-api-key` / `openai-compatible`:展示结构化 payload。
27+
7. 追加收口:候选队列项已改为账号卡片样式,外层使用 `data-account-card``card-swiss`,保留解析预览入口但不再渲染为普通三列分隔列表行。
28+
29+
## 验收覆盖
30+
31+
- 单一入口:账户页只保留统一“导入账号”动作。
32+
- 多文件:文件选择支持 multiple,并加入候选队列。
33+
- 粘贴 JSON 数组:`parseAccountImportPayloads` 支持递归拆分数组。
34+
- 导入前核对:候选队列行显示 title、source、kind 和解析内容预览。
35+
- 取消导入:modal 内队列状态是本地草稿,关闭后不会影响既有账号。
36+
37+
## 最新验证
38+
39+
以下命令在 2026-05-28 已重新执行:
40+
41+
```bash
42+
node --test frontend/src/features/accounts/tests/accountTransfer.test.mjs
43+
npm --prefix frontend run typecheck
44+
npm --prefix frontend run build
45+
npm --prefix frontend run build-storybook
46+
npm --prefix frontend run test:unit
47+
```
48+
49+
结果:全部通过。完整单测当前为 `602 pass / 0 fail`
50+
51+
## 接手复核
52+
53+
2026-05-28 接手复核结论:
54+
55+
1. 账号导入聚焦测试通过:`node --test frontend/src/features/accounts/tests/accountTransfer.test.mjs frontend/src/features/accounts/tests/accountHeaderMenu.test.mjs frontend/src/features/accounts/tests/accountCardInteractions.test.mjs`,结果 `28 pass / 0 fail`
56+
2. 当前工作区下 `npm --prefix frontend run typecheck` 通过。
57+
3. 当前工作区下完整 `npm --prefix frontend run test:unit` 未通过,但失败点是并行的 session-management / design-system 改动:`session-plugin-console-panel must provide a story path`,不在账号导入 modal 范围内。
58+
4. 浏览器预览在 `http://127.0.0.1:4173/?preview=accounts#frame=accounts` 打开账号页后,可以通过“账号操作菜单 -> 导入账号”进入统一 modal;粘贴 auth JSON + OpenAI-compatible 账号卡 JSON 数组后,候选队列拆成 2 项,并显示 `team-codex-auth.json` / `deepseek` 的解析内容预览。
59+
5. 当前浏览器预览在重复交互后会触发 `useAccountsPageStateContext must be used within AccountsPageStateProvider` 崩溃,属于当前并行账号页/设计系统工作区状态风险;本交接未修改这部分实现。
60+
61+
## 追加复核
62+
63+
2026-05-28 追加处理候选队列账号卡片样式和左右布局后:
64+
65+
1. `AccountImportModal` 的候选项外层已使用账号卡片视觉骨架:`data-account-card``card-swiss`、零内边距卡片容器、顶部身份区和底部预览区。
66+
2. `AccountImportModal` 主体已改成左右两栏:左侧单个 `data-account-import-input-panel` 合并文件选择与粘贴输入,右侧为账号预览候选队列。
67+
3. 新增源码守护测试,防止候选项回退到旧的 `grid-cols-[2.25rem_minmax(0,1fr)_auto]` 分隔列表行,并防止输入区重新拆成两个并列输入卡。
68+
4. 验证通过:`node --test frontend/src/features/accounts/tests/accountTransfer.test.mjs frontend/src/features/accounts/tests/accountHeaderMenu.test.mjs frontend/src/features/accounts/tests/accountCardInteractions.test.mjs`,结果 `30 pass / 0 fail``npm --prefix frontend run typecheck` 通过;`npm --prefix frontend run test:unit` 通过,完整单测 `609 pass / 0 fail`
69+
5. 浏览器预览 DOM / 功能冒烟通过:`http://127.0.0.1:4173/?preview=accounts#frame=accounts` 中“账号操作菜单 -> 导入账号”可打开 modal;粘贴 auth JSON 可加入候选队列,预览为账号卡片并将 `access_token` 显示为 `[REDACTED]`;移除候选后队列恢复为空且提交按钮重新禁用。
70+
71+
## 交接注意
72+
73+
1. 当前仓库中账号导入相关文件已经是干净状态;最近实现已进入提交 `f107f30 feat: consolidate account and session workbenches`
74+
2. `git status --short` 仍显示以下无关条目:
75+
- `20260528-session-plugin-console-design-desktop-after-v01.png`
76+
- `frontend/wailsjs/go/models.ts`
77+
- `frontend/debug-storybook.log`
78+
3. 不要在账号导入交接中处理上述文件,除非另一个 session-management/storybook 或 Wails 生成物任务明确要求。
79+
4. 若继续改导入预览 UI,优先保持 `resolveAccountImportPayloadPreview` 作为单一格式化入口,避免在 React 组件里复制类型分支。
80+
81+
## 建议下一步
82+
83+
1. 人工在真实 app 中打开账户页“导入账号”,用一个 auth JSON 和一个 OpenAI-compatible 账号卡 JSON 数组做最终手测。
84+
2. 若要继续优化体验,建议只做两类小改动:敏感字段脱敏显示、预览区折叠/展开。不要改变现有导入提交语义。

frontend/src/features/accounts/components/AccountImportModal.tsx

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,12 @@ export default function AccountImportModal({
188188
</>
189189
}
190190
>
191-
<div className="grid gap-5 p-6">
192-
<div className="grid gap-4 lg:grid-cols-[minmax(0,0.92fr)_minmax(0,1.08fr)]">
193-
<section className="grid min-w-0 gap-3 border-2 border-[var(--border-color)] bg-[var(--bg-main)] p-4 shadow-[4px_4px_0_var(--shadow-color)]">
191+
<div className="grid gap-5 p-6 lg:grid-cols-[minmax(0,0.92fr)_minmax(0,1.08fr)] lg:items-start">
192+
<section
193+
data-account-import-input-panel
194+
className="grid min-w-0 gap-4 border-2 border-[var(--border-color)] bg-[var(--bg-main)] p-4 shadow-[4px_4px_0_var(--shadow-color)]"
195+
>
196+
<div className="grid gap-3 border-b-2 border-dashed border-[var(--border-color)] pb-4">
194197
<div className="flex items-center justify-between gap-3">
195198
<div className="flex min-w-0 items-center gap-2">
196199
<FilePlus className="h-4 w-4 shrink-0 text-[var(--text-muted)]" strokeWidth={3} />
@@ -229,9 +232,9 @@ export default function AccountImportModal({
229232
</span>
230233
</span>
231234
</button>
232-
</section>
235+
</div>
233236

234-
<section className="grid min-w-0 gap-3 border-2 border-[var(--border-color)] bg-[var(--bg-main)] p-4 shadow-[4px_4px_0_var(--shadow-color)]">
237+
<div className="grid gap-3">
235238
<div className="flex items-center justify-between gap-3">
236239
<div className="flex min-w-0 items-center gap-2">
237240
<ClipboardPaste className="h-4 w-4 shrink-0 text-[var(--text-muted)]" strokeWidth={3} />
@@ -269,8 +272,8 @@ export default function AccountImportModal({
269272
{t('accounts.import_account_clear_paste')}
270273
</button>
271274
</div>
272-
</section>
273-
</div>
275+
</div>
276+
</section>
274277

275278
<section className="grid min-w-0 border-2 border-[var(--border-color)] bg-[var(--bg-main)]">
276279
<header className="border-b-2 border-[var(--border-color)] bg-[var(--bg-surface)] px-4 py-3">
@@ -286,39 +289,46 @@ export default function AccountImportModal({
286289
{t('accounts.import_account_queue_empty')}
287290
</div>
288291
) : (
289-
<div className="grid">
292+
<div className="grid gap-3 bg-[var(--bg-surface)] p-4">
290293
{queueItems.map((item, index) => (
291294
<div
292295
key={item.id}
293-
className="grid min-h-16 grid-cols-[2.25rem_minmax(0,1fr)_auto] items-center gap-3 border-b border-dashed border-[var(--border-color)] px-4 py-3 last:border-b-0 sm:grid-cols-[2.25rem_minmax(0,1fr)_9rem_auto]"
296+
data-account-card
297+
className="card-swiss relative flex min-w-0 max-w-full flex-col overflow-visible bg-[var(--bg-main)] p-0"
294298
>
295-
<span className="grid h-8 w-8 place-items-center border-2 border-[var(--border-color)] bg-[var(--bg-surface)] font-mono text-[length:var(--font-size-ui-xs)] font-black">
296-
{index + 1}
297-
</span>
298-
<div className="min-w-0">
299-
<div className="truncate text-[length:var(--font-size-ui-sm)] font-black text-[var(--text-primary)]">
300-
{resolveQueueItemTitle(item.payload)}
301-
</div>
302-
<div className="mt-1 truncate font-mono text-[length:var(--font-size-ui-2xs)] font-black uppercase tracking-[0.12em] text-[var(--text-muted)]">
303-
{item.source === 'file' ? t('accounts.import_account_source_file') : t('accounts.import_account_source_paste')}
299+
<div className="grid gap-3 border-b-2 border-[var(--border-color)] bg-[var(--bg-main)] p-4 sm:grid-cols-[minmax(0,1fr)_auto] sm:items-start">
300+
<div className="flex min-w-0 items-start gap-3">
301+
<span className="grid h-9 w-9 shrink-0 place-items-center border-2 border-[var(--border-color)] bg-[var(--bg-surface)] font-mono text-[length:var(--font-size-ui-xs)] font-black">
302+
{index + 1}
303+
</span>
304+
<div className="min-w-0">
305+
<div className="truncate text-[length:var(--font-size-ui-sm)] font-black uppercase italic tracking-normal text-[var(--text-primary)]">
306+
{resolveQueueItemTitle(item.payload)}
307+
</div>
308+
<div className="mt-1 flex min-w-0 flex-wrap gap-2">
309+
<span className="border border-[var(--border-color)] bg-[var(--bg-surface)] px-2 py-0.5 font-mono text-[length:var(--font-size-ui-2xs)] font-black uppercase tracking-[0.12em] text-[var(--text-muted)]">
310+
{item.source === 'file' ? t('accounts.import_account_source_file') : t('accounts.import_account_source_paste')}
311+
</span>
312+
<span className="border border-[var(--border-color)] bg-[var(--bg-surface)] px-2 py-0.5 font-mono text-[length:var(--font-size-ui-2xs)] font-black uppercase tracking-[0.12em] text-[var(--text-primary)]">
313+
{resolveQueueItemKind(item.payload)}
314+
</span>
315+
</div>
316+
</div>
304317
</div>
305-
<pre className="mt-2 max-h-24 overflow-auto whitespace-pre-wrap break-words font-mono text-[length:var(--font-size-ui-2xs)] leading-relaxed text-[var(--text-secondary)]">
306-
{resolveAccountImportPayloadPreview(item.payload)}
307-
</pre>
318+
<button
319+
type="button"
320+
onClick={() => setQueueItems((prev) => prev.filter((candidate) => candidate.id !== item.id))}
321+
disabled={submitting}
322+
className="justify-self-end border-0 bg-transparent p-1 text-[var(--color-status-danger)] transition-transform active:scale-95 disabled:opacity-45"
323+
aria-label={t('accounts.import_account_remove_item')}
324+
title={t('accounts.import_account_remove_item')}
325+
>
326+
<Trash2 className="h-4 w-4" strokeWidth={3} />
327+
</button>
308328
</div>
309-
<span className="hidden justify-self-start border-2 border-[var(--border-color)] bg-[var(--bg-surface)] px-2 py-1 font-mono text-[length:var(--font-size-ui-2xs)] font-black uppercase tracking-[0.12em] text-[var(--text-primary)] sm:block">
310-
{resolveQueueItemKind(item.payload)}
311-
</span>
312-
<button
313-
type="button"
314-
onClick={() => setQueueItems((prev) => prev.filter((candidate) => candidate.id !== item.id))}
315-
disabled={submitting}
316-
className="justify-self-end border-0 bg-transparent p-1 text-[var(--color-status-danger)] transition-transform active:scale-95 disabled:opacity-45"
317-
aria-label={t('accounts.import_account_remove_item')}
318-
title={t('accounts.import_account_remove_item')}
319-
>
320-
<Trash2 className="h-4 w-4" strokeWidth={3} />
321-
</button>
329+
<pre className="max-h-28 overflow-auto whitespace-pre-wrap break-words border-0 bg-[var(--bg-surface)] px-4 py-3 font-mono text-[length:var(--font-size-ui-2xs)] leading-relaxed text-[var(--text-secondary)]">
330+
{resolveAccountImportPayloadPreview(item.payload)}
331+
</pre>
322332
</div>
323333
))}
324334
</div>

0 commit comments

Comments
 (0)