Files
hermes-ui/docs/phases/phase_006.md
T
toor effc29085c Phase 006: migrate the chat page from naive-ui to Arco Design Vue
Swaps the rendering layer only. Chat store logic, keyboard handling, and the
/chat-run Socket.IO contract are untouched, so a mixed UI cannot hide a chat
regression behind a library swap.

Arco's dropdowns take their items from a #content slot of a-doption children
rather than a droplist prop, and Arco Triggers cannot be nested, so the two
toolbar menus lost their a-tooltip in favour of a native title. Both rules are
recorded in docs/arco-mapping.md, and tests/e2e/chat-input-dropdown.spec.ts plus
tests/e2e/chat-rendering-smoke.spec.ts keep the menus, the streaming markdown
rendering, and the no-Arco-inside-the-bubble red line from regressing silently.
2026-10-17 22:17:32 +08:00

260 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 视图批量迁移)。