# Chat 对话功能迁移重构计划 ## 参考来源 - **源文件**: `ClawX/src/pages/Chat/index.tsx` 及其子组件 - **目标文件**: `zn-ai/src/pages/home/index.vue`、`zn-ai/src/pages/home/ChatBox.vue` ## 源实现思路分析 `ClawX/src/pages/Chat/index.tsx` 的核心架构: 1. **状态层集中化**:所有聊天状态(messages、sending、loading、error、streamingMessage 等)托管在 `useChatStore`(Zustand)中,页面组件只负责订阅与渲染。 2. **组件原子化**: - `ChatMessage`:单条消息渲染(markdown、thinking、tool_use、附件图片)。 - `ChatInput`:输入框 + 发送/停止按钮。 - `ChatToolbar`:会话选择器、thinking 显隐切换、刷新按钮。 - `ExecutionGraphCard`:用户消息后的任务执行可视化卡片。 3. **流式消息处理**:通过 `streamingMessage` 和 `streamingTools` 实现未落库前的增量渲染;`pendingFinal` 标识等待最终响应的状态。 4. **生命周期管理**: - 切走页面时调用 `cleanupEmptySession()` 清理空会话。 - 加载历史消息时保留乐观用户消息,避免闪烁。 - 轮询 + 安全超时机制防止消息卡死。 5. **错误与加载态**: - 底部 `error` 条显示全局错误并可一键清除。 - `minLoading` 在 history 加载时显示透明遮罩 + LoadingSpinner。 - 发送中显示 `TypingIndicator` / `ActivityIndicator`。 6. **WelcomeScreen**:当 `messages.length === 0 && !sending` 时展示欢迎页 + 快捷操作按钮。 --- ## 迁移目标 将 `ChatBox.vue` 中沉淀的 880+ 行“上帝组件”逻辑解耦,引入 **Pinia Store + 原子组件** 的 ClawX 式架构,同时保留 zn-ai 现有的视觉风格(蓝色主题、头像布局、输入框样式)。 --- ## 实现步骤 ### 1. 提取 Pinia Chat Store(状态层迁移) 新建 `zn-ai/src/store/chat.ts`,将 `ChatBox.vue` 中的以下逻辑迁入 Store: | ChatBox.vue 现有逻辑 | Store 对应设计 | |---|---| | `chatMsgList` | `state.messages` | | `isSendingMessage` | `state.sending` | | `isSessionActive` | `state.pendingFinal` / `state.loading` | | WebSocket 管理(`initWebSocket`、`sendWebSocketMessage`) | Store Actions:`initConnection`、`sendMessage`、`stopRun` | | `pendingMap` / `pendingTimeouts` 超时回退 | Store 内部 Map + 定时器(不暴露给 UI) | | `handleWebSocketMessage` | Store Action:`handleStreamEvent` | | `loadConversationMessages` | Store Action:`loadHistory(sessionId)` | | `createConversationRequest` | Store Action:`createSession()` | | `resetConversation` / `cleanup` | Store Action:`resetSession()` | **Store 核心 State 设计**: ```ts interface ChatState { messages: ChatMessage[]; // 当前会话消息列表 sending: boolean; // 是否正在发送/等待回复 loading: boolean; // 是否正在加载历史 error: string | null; // 全局错误提示 streamingMessage: Partial | null; // 流式增量消息 currentSessionId: string; // 当前会话 ID } ``` > **说明**:zn-ai 当前使用 WebSocket 直联后端,ClawX 使用 Gateway RPC;本次迁移**只搬架构、不搬协议**,Store 内部仍继续使用现有的 `WebSocketManager`。 ### 2. 组件拆分(UI 层迁移) 将 `ChatBox.vue` 按职责拆成以下子组件(放置于 `zn-ai/src/pages/home/components/chat/`): #### 2.1 `ChatMessage.vue` 职责:渲染单条消息。 - Props:`msg: ChatMessage`、`isStreaming?: boolean` - 复用现有 `ChatRoleMe.vue`、`ChatRoleAI.vue`、`ChatAvatar.vue`、`ChatNameTime.vue`、`ChatAttach.vue`、`ChatAIMark.vue`、`ChatOperation.vue` 的能力。 - **新增**:支持 `streamingMessage` 的渲染(内容可能未完成)。 #### 2.2 `ChatInput.vue`(由现有 `ChatInputArea.vue` 升级) 职责:输入框 + 发送/停止按钮。 - Props:`modelValue: string`、`sending: boolean`、`disabled?: boolean` - Events:`send`、`stop`、`update:modelValue`、`attach` - **新增**:当 `sending = true` 时按钮显示停止图标并触发 `stop`。 #### 2.3 `ChatEmpty.vue`(WelcomeScreen 等价物) 职责:空会话欢迎页。 - 迁移现有 `ChatBox.vue` 中的引导页 DOM(大标题“你好,我今天能帮你什么?”)。 - 可保留现有的 `TaskCenter` 插槽或快捷操作按钮区域。 #### 2.4 `ChatErrorBar.vue` 职责:底部错误条。 - Props:`error: string | null` - Events:`dismiss` - 样式参考 ClawX 的红色背景条:`bg-red-50` + 左侧 `AlertCircle` 图标 + 右侧“Dismiss”按钮。 #### 2.5 `ChatLoadingOverlay.vue`(可选) 职责:history 加载时透明遮罩 + LoadingSpinner。 - 若 zn-ai 现有 `ChatLoading.vue` 已满足,可直接复用。 ### 3. ChatBox.vue 瘦身改造 改造后 `ChatBox.vue` 只承担:**Store 订阅 + 组件拼装 + 少量 prop 透传**。 ```html ``` ```ts ``` ### 4. 流式消息与 Indicator 支持 在 Store 的 WebSocket `onMessage` 回调中: 1. **首次收到内容**:创建 `streamingMessage`(AI 角色、初始内容)。 2. **后续收到增量**:`streamingMessage.messageContent += data.content`。 3. **收到 finish**:将 `streamingMessage` 推入 `messages[]`,然后清空 `streamingMessage`,结束 `sending`。 4. **异常/超时**:清空 `streamingMessage` 并设置 `error`。 **新增 `ChatTypingIndicator.vue`**(参考 ClawX `TypingIndicator`): - 左侧 AI 头像,右侧三个跳动的圆点,用于 `sending && !streamingMessage` 时占位。 ### 5. 生命周期与页面级整合(`home/index.vue`) 在 `home/index.vue` 中补充 Store 生命周期绑定: ```ts import { useChatStore } from '@store/chat'; const chatStore = useChatStore(); onMounted(() => { chatStore.initConnection(); }); onBeforeUnmount(() => { chatStore.resetSession(); }); ``` > 当用户从首页切到其他页面时,调用 `resetSession()` 关闭 WebSocket、清理定时器,避免后台继续接收消息。 ### 6. 历史消息加载与空会话清理 - **选择历史会话**:`ChatHistory.vue` 的 `@select-chat` 事件触发 `chatStore.loadHistory(conversationId)`。 - **新建对话**:`@new-chat` 触发 `chatStore.createSession()`,然后清空 `messages`。 - **切页清理**:Store 中实现 `cleanupEmptySession()`,如果当前会话没有任何消息且不是历史来源,则自动重置 `conversationId`,避免产生幽灵会话。 ### 7. 样式与兼容性保持 - **颜色主题**:继续使用 zn-ai 现有的 `#2B7FFF` 蓝色高亮、`#E5E8EE` 边框色。 - **头像布局**:左右头像顺序不变(AI 左、用户右)。 - **输入框样式**:`ChatInput` 保留现有的圆角、阴影、发送按钮样式。 - **引导页**:保留现有的 `TaskCenter` 和欢迎文案。 --- ## 涉及文件 | 新建/修改 | 文件路径 | 说明 | |---|---|---| | 新建 | `zn-ai/src/store/chat.ts` | Pinia Chat Store,承载状态与 WebSocket 逻辑 | | 新建 | `zn-ai/src/pages/home/components/chat/ChatMessage.vue` | 单条消息渲染(聚合现有原子组件) | | 新建 | `zn-ai/src/pages/home/components/chat/ChatEmpty.vue` | 空会话欢迎页 | | 新建 | `zn-ai/src/pages/home/components/chat/ChatErrorBar.vue` | 底部全局错误提示条 | | 新建 | `zn-ai/src/pages/home/components/chat/ChatTypingIndicator.vue` | 发送中跳动圆点 | | 升级 | `zn-ai/src/pages/home/components/ChatInputArea.vue` | 增加 `sending` 态与 `stop` 事件 | | 重构 | `zn-ai/src/pages/home/ChatBox.vue` | 大幅瘦身,仅负责拼装子组件 | | 调整 | `zn-ai/src/pages/home/index.vue` | 引入 Store 生命周期、事件透传 | --- ## 验收标准 - [ ] `ChatBox.vue` 代码量从 800+ 行降至 150 行以内,不再包含 WebSocket 细节。 - [ ] 新建 `chat.ts` Pinia Store 能正常初始化 WebSocket、发送消息、接收回复。 - [ ] 用户发送消息后,输入框清空,列表底部出现 `ChatTypingIndicator`;收到首包内容后切换为 `ChatMessage` 流式渲染。 - [ ] 收到完成标识后,流式消息固化到消息列表,状态恢复正常。 - [ ] 发生超时或错误时,底部出现 `ChatErrorBar`,可点击清除。 - [ ] 切换历史会话时,通过 Store 加载历史消息并正确渲染。 - [ ] 新建对话时,列表回到空状态(`ChatEmpty`)。 - [ ] 离开首页时 WebSocket 被正确关闭,无后台消息泄漏。