Files
zn-ai/ChatPageMigrationPlan.md
2026-04-14 07:36:40 +08:00

10 KiB
Raw Blame History

Chat 对话功能迁移重构计划

参考来源

  • 源文件: ClawX/src/pages/Chat/index.tsx 及其子组件
  • 目标文件: zn-ai/src/pages/home/index.vuezn-ai/src/pages/home/ChatBox.vue

源实现思路分析

ClawX/src/pages/Chat/index.tsx 的核心架构:

  1. 状态层集中化所有聊天状态messages、sending、loading、error、streamingMessage 等)托管在 useChatStoreZustand页面组件只负责订阅与渲染。
  2. 组件原子化
    • ChatMessage单条消息渲染markdown、thinking、tool_use、附件图片
    • ChatInput:输入框 + 发送/停止按钮。
    • ChatToolbar会话选择器、thinking 显隐切换、刷新按钮。
    • ExecutionGraphCard:用户消息后的任务执行可视化卡片。
  3. 流式消息处理:通过 streamingMessagestreamingTools 实现未落库前的增量渲染;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 管理(initWebSocketsendWebSocketMessage Store ActionsinitConnectionsendMessagestopRun
pendingMap / pendingTimeouts 超时回退 Store 内部 Map + 定时器(不暴露给 UI
handleWebSocketMessage Store ActionhandleStreamEvent
loadConversationMessages Store ActionloadHistory(sessionId)
createConversationRequest Store ActioncreateSession()
resetConversation / cleanup Store ActionresetSession()

Store 核心 State 设计

interface ChatState {
  messages: ChatMessage[];       // 当前会话消息列表
  sending: boolean;              // 是否正在发送/等待回复
  loading: boolean;              // 是否正在加载历史
  error: string | null;          // 全局错误提示
  streamingMessage: Partial<ChatMessage> | 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

职责:渲染单条消息。

  • Propsmsg: ChatMessageisStreaming?: boolean
  • 复用现有 ChatRoleMe.vueChatRoleAI.vueChatAvatar.vueChatNameTime.vueChatAttach.vueChatAIMark.vueChatOperation.vue 的能力。
  • 新增:支持 streamingMessage 的渲染(内容可能未完成)。

2.2 ChatInput.vue(由现有 ChatInputArea.vue 升级)

职责:输入框 + 发送/停止按钮。

  • PropsmodelValue: stringsending: booleandisabled?: boolean
  • Eventssendstopupdate:modelValueattach
  • 新增:当 sending = true 时按钮显示停止图标并触发 stop

2.3 ChatEmpty.vueWelcomeScreen 等价物)

职责:空会话欢迎页。

  • 迁移现有 ChatBox.vue 中的引导页 DOM大标题“你好我今天能帮你什么
  • 可保留现有的 TaskCenter 插槽或快捷操作按钮区域。

2.4 ChatErrorBar.vue

职责:底部错误条。

  • Propserror: string | null
  • Eventsdismiss
  • 样式参考 ClawX 的红色背景条:bg-red-50 + 左侧 AlertCircle 图标 + 右侧“Dismiss”按钮。

2.5 ChatLoadingOverlay.vue(可选)

职责history 加载时透明遮罩 + LoadingSpinner。

  • 若 zn-ai 现有 ChatLoading.vue 已满足,可直接复用。

3. ChatBox.vue 瘦身改造

改造后 ChatBox.vue 只承担:Store 订阅 + 组件拼装 + 少量 prop 透传

<template>
  <div class="flex flex-col h-full py-6 px-6 overflow-hidden">
    <!-- 空状态 -->
    <ChatEmpty v-if="isEmpty" />

    <!-- 消息列表 -->
    <div v-else ref="listRef" class="flex-1 overflow-y-auto py-6 space-y-6">
      <div
        v-for="msg in chatStore.messages"
        :key="msg.messageId"
        class="flex items-start gap-3"
        :class="msg.messageRole === MessageRole.ME ? 'justify-end' : 'justify-start'"
      >
        <ChatAvatar v-if="msg.messageRole === MessageRole.AI" :src="aiAvatar" />
        <ChatMessage :msg="msg" />
        <ChatAvatar v-if="msg.messageRole === MessageRole.ME" :src="userAvatar" />
      </div>

      <!-- 流式消息占位(未落库前的 AI 回复) -->
      <div v-if="chatStore.streamingMessage" class="flex items-start gap-3 justify-start">
        <ChatAvatar :src="aiAvatar" />
        <ChatMessage :msg="chatStore.streamingMessage" is-streaming />
      </div>

      <!-- 发送中 Indicator -->
      <ChatTypingIndicator v-if="showTypingIndicator" />
    </div>

    <!-- 错误条 -->
    <ChatErrorBar :error="chatStore.error" @dismiss="chatStore.clearError()" />

    <!-- 输入区 -->
    <div class="flex flex-col gap-3 mt-4">
      <ChatInput
        v-model="inputMessage"
        :sending="chatStore.sending"
        @send="onSend"
        @stop="chatStore.stopRun()"
        @attach="onAttach"
      />
    </div>
  </div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useChatStore } from '@store/chat';
import { MessageRole } from './model/ChatModel';
// ... 引入各子组件

const chatStore = useChatStore();
const inputMessage = ref('');

const isEmpty = computed(() =>
  chatStore.messages.length === 0 && !chatStore.sending && !chatStore.streamingMessage
);

const showTypingIndicator = computed(() =>
  chatStore.sending && !chatStore.streamingMessage
);

const onSend = () => {
  const text = inputMessage.value.trim();
  if (!text) return;
  chatStore.sendMessage(text);
  inputMessage.value = '';
};
</script>

4. 流式消息与 Indicator 支持

在 Store 的 WebSocket onMessage 回调中:

  1. 首次收到内容:创建 streamingMessageAI 角色、初始内容)。
  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 生命周期绑定:

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 被正确关闭,无后台消息泄漏。