# 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 建的适配层: ```bash cd packages/client/src grep -rln "useMessage\|useDialog\|useNotification" components/hermes/chat/ stores/hermes/chat.ts ``` 改成: ```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 命令 ```bash 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 视图批量迁移)。