8.1 KiB
Phase 006 — Arco 迁移:聊天页
| 项 | 值 |
|---|---|
| 目标 | 聊天页全量换 Arco,功能与旧版完全对等 |
| 前置 | Phase 005 通过(docs/ui-baseline.md 已定稿) |
| 产出 | 聊天全流程手工验收清单全绿 |
| 参考 | docs/03-施工蓝图.md §8.1 |
0. 为什么聊天页优先
它是全站唯一的 Socket.IO 流式实时页,含审批弹窗、工具轨迹、思考块、Markdown 渲染、虚拟滚动、附件上传。最难的坑在这里踩完,后面 20 个 CRUD 页就是纯体力活。
1. 硬性能规则:消息气泡内禁用 Arco 组件
流式增量渲染时,MessageItem.vue 每次 token 到达都 re-render。Arco 组件被包进消息气泡会引入不必要的 diff 开销。
✅ 允许:a-button(输入区工具条、审批按钮)
✅ 允许:a-modal(审批弹窗,在气泡外层)
❌ 禁止:a-tag / a-avatar / a-progress 等包在气泡内的装饰性组件
❌ 禁止:气泡内任何有内部状态的组件
气泡内用原生 HTML + 项目自己的 SCSS。这条已写进 docs/ui-baseline.md。
2. 改动落点清单
packages/client/src/views/hermes/ChatView.vue
packages/client/src/components/hermes/chat/ChatPanel.vue
packages/client/src/components/hermes/chat/ChatInput.vue
packages/client/src/components/hermes/chat/MessageList.vue
packages/client/src/components/hermes/chat/MessageItem.vue
packages/client/src/components/hermes/chat/VirtualMessageList.vue
packages/client/src/components/hermes/chat/SessionSearchModal.vue
packages/client/src/components/hermes/chat/HistoryMessageList.vue
packages/client/src/components/hermes/chat/FilesPanel.vue
packages/client/src/components/hermes/chat/DrawerPanel.vue
packages/client/src/components/hermes/chat/MarkdownRenderer.vue
packages/client/src/components/hermes/chat/OutlinePanel.vue
packages/client/src/components/hermes/chat/TerminalPanel.vue
packages/client/src/components/hermes/chat/ConversationMonitorPane.vue
packages/client/src/stores/hermes/chat.ts
packages/client/src/stores/hermes/files.ts
packages/client/src/api/hermes/chat.ts
3. 功能任务 1 — 布局与容器
| 原组件 | 新组件 | 备注 |
|---|---|---|
NLayout / NLayoutSider |
a-layout / a-layout-sider |
三栏:会话列表 / 聊天区 / 侧面板 |
NCard |
a-card |
会话项卡片 |
NScrollbar |
a-scrollbar |
注意:虚拟滚动与 a-scrollbar 冲突,见 §5 |
NEmpty |
a-empty |
空态 |
NPopover |
a-popover |
会话项操作菜单 |
NTooltip |
a-tooltip |
|
NDrawer |
a-drawer |
侧面板 |
NCollapse |
a-collapse |
工具轨迹折叠 |
4. 功能任务 2 — 输入区
| 原组件 | 新组件 |
|---|---|
NInput |
a-textarea |
NButton |
a-button |
NSelect(模型) |
a-select |
NDropdown |
a-dropdown |
NPopconfirm |
a-popconfirm |
NProgress(录音) |
a-progress |
NIcon |
Arco Icon |
ChatInput.vue 里有 useKeyboard / 高度自适应(chat-input-height.test.ts 覆盖),不要重构这块逻辑,只换渲染层。
5. 功能任务 3 — 消息列表(最高风险区)
5.1 虚拟滚动
VirtualMessageList.vue 用 vue-virtual-scroller。
a-scrollbar 与 vue-virtual-scroller 不能同时管滚动容器。二选一:
- 保留
vue-virtual-scroller的滚动容器,去掉a-scrollbar包裹 - 或放弃虚拟滚动改
a-table-virtual(不推荐,会破坏消息气泡的自适应高度)
结论:保留 vue-virtual-scroller,去掉 a-scrollbar。在 docs/arco-mapping.md 记下这条例外。
5.2 流式渲染
MessageItem.vue 的 re-render 频率高。确保:
- 气泡内无 Arco 组件(§1)
- token 拼接走局部 ref,不触发整列表 diff
- Markdown 渲染有防抖(保留旧版逻辑)
5.3 审批与澄清弹窗
时序敏感:必须打断流式。这是 a-modal 与流式状态的竞争点。
- 审批请求到达 → 流式暂停 → 弹窗
- 用户选择 → 恢复流式
- 断线重连时若有未处理的审批,要能恢复
MessageItem.vue 里的审批 UI 改成 a-modal 或内联 a-card + a-button(内联更好:不打断视觉连续性)。内联是首选。
5.4 工具轨迹
a-collapse + a-timeline。折叠状态要记住(重渲染不丢)。
5.5 Markdown / 代码 / 图表
MarkdownRenderer.vue 保持不变(不涉及 naive)。相关测试:
markdown-rendering.test.ts
markdown-rendering-mermaid-import-timeout.test.ts
markdown-special-mentions.test.ts
highlight-safety.test.ts
highlight-helper.test.ts
这些不改,跑绿即可。
6. 功能任务 4 — 会话列表
SessionListItem.vue / SessionSearchModal.vue:
| 原组件 | 新组件 |
|---|---|
NCard |
a-card |
NInput(搜索) |
a-input |
NEmpty |
a-empty |
NTag(标签) |
a-tag |
NDropdown |
a-dropdown |
NBadge |
a-badge |
NPopconfirm(删除确认) |
a-popconfirm |
7. 功能任务 5 — 全局 hook 替换
全量 grep 替换,只用 Phase 005 §5 建的适配层:
cd packages/client/src
grep -rln "useMessage\|useDialog\|useNotification" components/hermes/chat/ stores/hermes/chat.ts
改成:
// 旧
const message = useMessage() // naive hook
// 新
import { useMessage } from '@/composables/useMessage'
const message = useMessage() // 我们的 composable,签名一致
签名一致,所以正文代码几乎不用改,只改 import 来源。这是 Phase 005 §5 的设计目的。
8. 功能任务 6 — 补测试
新建 tests/client/message-item-arko.test.ts:
- 消息气泡内不含 Arco 组件标签(防止 §1 规则被破坏)
- 审批弹窗状态机正确
- 流式追加消息后 DOM 结构不抖动
9. 验收(手工逐条走,不许跳)
9.1 命令
cd /Users/jack/source/MyProject/js/hermes-ui
grep -cE "from ['\"]naive-ui['\"]" packages/client/src/components/hermes/chat/*.vue \
packages/client/src/views/hermes/ChatView.vue \
packages/client/src/stores/hermes/chat.ts
# 期望:全部 0
npm run build
npm run test
npm run test:e2e
9.2 手工清单
流式与渲染
- 发消息 → 流式逐字输出 → 完成态
- 工具调用轨迹展开 / 收起
- 思考块(thinking)正常显示 / 隐藏
- 长消息滚动流畅(虚拟滚动没坏)
- 滚动到底部自动跟随
交互
- 审批请求弹出 → 选择 → 流式继续
- 澄清请求 → 回答 → 继续
- 中断(stop)按钮生效
- 重新生成 / 分支切换
- 会话切换时状态正确重置
内容
- Markdown 标题 / 列表 / 表格
- 代码块高亮 + 复制按钮
- KaTeX 公式
- Mermaid 图(注意导入超时)
- @提及高亮
多标签与断线
- 两个标签页开同一会话(
chat-session-multitab.spec.ts) - 断网 → 重连 → 流式恢复(
chat-run-reconnect.test.ts) - 移动端窄屏布局(
chat-message-mobile-layout.test.ts)
其他
- 附件上传 + 引用
- 文件面板(FilesPanel)正常
- 大纲面板(OutlinePanel)正常
- 终端面板(TerminalPanel)正常
- 会话搜索正常
- 明暗双主题各过一遍
10. 风险
| # | 风险 | 缓解 |
|---|---|---|
| P6-1 | a-scrollbar 与虚拟滚动打架 → 白屏或列表不动 |
§5.1 已定方案,去掉 a-scrollbar |
| P6-2 | 流式性能退化(气泡内 Arco 组件) | §1 硬规则 + message-item-arko.test.ts 卡住 |
| P6-3 | 审批弹窗与流式状态竞争 → 卡死 | §5.3 优先内联方案,减少 modal 竞争 |
| P6-4 | useMessage 等 hook 改 import 时漏改某个文件 |
§7 的 grep 逐个确认,编译会兜底 |
| P6-5 | 顺手重构了 chat store 逻辑 | 硬规则:只换渲染层,不动逻辑。逻辑改动属 Phase 007+ |
| P6-6 | 测试大量 fail 干扰判断 | 先确认 npm run test 的 fail 是否本来就存在(Phase 004 基线),只处理新增 |
11. 完成标志
进入 Phase 007(其余 20 视图批量迁移)。