# 前端集成模式 > 记录 ZCLAW Desktop 前端与 ZCLAW 后端的集成模式和最佳实践。 --- ## 1. 架构概览 ``` ┌─────────────────────────────────────────────────────────┐ │ React UI │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ ChatArea│ │ Sidebar │ │Settings │ │ Panels │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ │ │ │ │ │ └────────────┴────────────┴────────────┘ │ │ │ │ │ Zustand Store │ │ ┌──────────────┼──────────────┐ │ │ │ │ │ │ │ chatStore gatewayStore settingsStore │ │ │ │ │ │ └───────────┼──────────────┼──────────────┼──────────────┘ │ │ │ └──────────────┼──────────────┘ │ GatewayClient ┌─────────┴─────────┐ │ │ WebSocket (流式) REST API (非流式) │ │ └─────────┬─────────┘ │ ZCLAW Backend (port 50051) ``` --- ## 2. 状态管理 ### 2.1 Store 分层 | Store | 职责 | 关键状态 | |-------|------|----------| | `chatStore` | 聊天消息、对话、流式状态 | messages, conversations, isStreaming | | `gatewayStore` | Gateway 连接、Agent、Hands | connectionState, clones, hands | | `settingsStore` | 用户设置、主题 | backendType, theme | ### 2.2 chatStore 核心模式 ```typescript // chatStore.ts interface ChatState { // 状态 messages: Message[]; conversations: Conversation[]; isStreaming: boolean; // 操作 addMessage: (message: Message) => void; updateMessage: (id: string, updates: Partial) => void; sendMessage: (content: string) => Promise; } // sendMessage 实现 sendMessage: async (content: string) => { // 1. 添加用户消息 addMessage({ id: `user_${Date.now()}`, role: 'user', content }); // 2. 创建助手消息占位符 const assistantId = `assistant_${Date.now()}`; addMessage({ id: assistantId, role: 'assistant', content: '', streaming: true }); set({ isStreaming: true }); try { // 3. 优先使用流式 API if (client.getState() === 'connected') { await client.chatStream(content, { onDelta: (delta) => { // 累积更新内容 updateMessage(assistantId, { content: /* 当前内容 + delta */ }); }, onComplete: () => { updateMessage(assistantId, { streaming: false }); set({ isStreaming: false }); }, onError: (error) => { updateMessage(assistantId, { content: `⚠️ ${error}`, error }); }, }); } else { // 4. Fallback 到 REST API const result = await client.chat(content); updateMessage(assistantId, { content: result.response, streaming: false }); } } catch (err) { // 5. 错误处理 updateMessage(assistantId, { content: `⚠️ ${err.message}`, error: err.message }); } } ``` ### 2.3 gatewayStore 核心模式 ```typescript // gatewayStore.ts interface GatewayState { connectionState: 'disconnected' | 'connecting' | 'connected'; clones: AgentProfile[]; hands: Hand[]; connect: () => Promise; loadClones: () => Promise; loadHands: () => Promise; } // 连接流程 connect: async () => { const client = getGatewayClient(); set({ connectionState: 'connecting' }); try { await client.connect(); set({ connectionState: 'connected' }); // 自动加载数据 await get().loadClones(); await get().loadHands(); } catch (err) { set({ connectionState: 'disconnected' }); throw err; } } ``` --- ## 3. GatewayClient 模式 ### 3.1 单例模式 ```typescript // gateway-client.ts let instance: GatewayClient | null = null; export function getGatewayClient(): GatewayClient { if (!instance) { instance = new GatewayClient(); } return instance; } ``` ### 3.2 流式聊天实现 ```typescript class GatewayClient { private streamCallbacks = new Map(); async chatStream( message: string, callbacks: StreamCallbacks, options?: { sessionKey?: string; agentId?: string } ): Promise<{ runId: string }> { const runId = generateRunId(); const agentId = options?.agentId || this.defaultAgentId; // 存储回调 this.streamCallbacks.set(runId, callbacks); // 连接 WebSocket const ws = this.connectZCLAWStream(agentId, runId, options?.sessionKey, message); return { runId }; } private handleZCLAWStreamEvent(runId: string, event: unknown) { const callbacks = this.streamCallbacks.get(runId); if (!callbacks) return; const e = event as ZCLAWEvent; switch (e.type) { case 'text_delta': callbacks.onDelta(e.content || ''); break; case 'response': callbacks.onComplete(); this.streamCallbacks.delete(runId); break; case 'error': callbacks.onError(e.content || 'Unknown error'); this.streamCallbacks.delete(runId); break; } } } ``` ### 3.3 回调类型定义 ```typescript interface StreamCallbacks { onDelta: (delta: string) => void; onTool?: (tool: string, input: string, output: string) => void; onHand?: (name: string, status: string, result?: unknown) => void; onComplete: () => void; onError: (error: string) => void; } ``` --- ## 4. 组件模式 ### 4.1 使用 Store ```typescript // ChatArea.tsx function ChatArea() { // 使用 selector 优化性能 const messages = useChatStore((state) => state.messages); const isStreaming = useChatStore((state) => state.isStreaming); const sendMessage = useChatStore((state) => state.sendMessage); // ... } ``` ### 4.2 流式消息渲染 ```typescript function MessageBubble({ message }: { message: Message }) { return (
{message.content} {message.streaming && ( )}
); } ``` ### 4.3 错误边界 ```typescript class ErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError(error: Error) { return { hasError: true }; } componentDidCatch(error: Error, info: React.ErrorInfo) { console.error('Component error:', error, info); } render() { if (this.state.hasError) { return this.setState({ hasError: false })} />; } return this.props.children; } } ``` --- ## 5. 代理配置 ### 5.1 Vite 开发代理 ```typescript // vite.config.ts export default defineConfig({ server: { port: 1420, proxy: { '/api': { target: 'http://127.0.0.1:50051', changeOrigin: true, secure: false, ws: true, // WebSocket 支持 }, }, }, }); ``` ### 5.2 动态后端切换 ```typescript // 根据后端类型切换代理目标 const BACKEND_PORTS = { zclaw: 18789, zclaw: 50051, }; const backendType = localStorage.getItem('zclaw-backend') || 'zclaw'; const targetPort = BACKEND_PORTS[backendType]; ``` --- ## 6. 持久化 ### 6.1 Zustand Persist ```typescript export const useChatStore = create()( persist( (set, get) => ({ // ... state and actions }), { name: 'zclaw-chat-storage', partialize: (state) => ({ conversations: state.conversations, currentModel: state.currentModel, }), onRehydrateStorage: () => (state) => { // 重建 Date 对象 if (state?.conversations) { for (const conv of state.conversations) { conv.createdAt = new Date(conv.createdAt); conv.updatedAt = new Date(conv.updatedAt); } } }, } ) ); ``` --- ## 7. 最佳实践 ### 7.1 不要直接调用 WebSocket ```typescript // ❌ 错误 - 在组件中直接创建 WebSocket function ChatArea() { const ws = new WebSocket(url); // 不要这样做 } // ✅ 正确 - 通过 GatewayClient function ChatArea() { const sendMessage = useChatStore((state) => state.sendMessage); // sendMessage 内部使用 GatewayClient } ``` ### 7.2 处理连接状态 ```typescript // 显示连接状态给用户 function ConnectionStatus() { const state = useGatewayStore((state) => state.connectionState); return (
{state}
); } ``` ### 7.3 优雅降级 ```typescript // 流式失败时降级到 REST try { await client.chatStream(message, callbacks); } catch (streamError) { console.warn('Stream failed, falling back to REST:', streamError); const result = await client.chat(message); // 处理 REST 响应 } ``` --- ## 更新历史 | 日期 | 变更 | |------|------| | 2026-03-14 | 初始版本 |