尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

基于React+WebSocket构建AI Agent实时交互前端:从状态管理到消息协议

基于React+WebSocket构建AI Agent实时交互前端:从状态管理到消息协议 1. 项目缘起为什么需要一个能与AI Agent实时交互的前端最近在捣鼓AI Agent发现一个挺普遍的问题很多教程和开源项目都把精力放在了后端逻辑和模型调用上但最终用户需要一个直观的界面来和这个“智能体”对话。你不可能每次都让用户去敲命令行或者看日志文件。一个响应迅速、交互流畅的Web前端是连接用户与Agent复杂大脑的桥梁。这不仅仅是做个聊天框那么简单它涉及到状态管理、实时通信、错误处理和用户体验等一系列前端工程化的挑战。我这次的目标很明确就是抛开复杂的后端框架聚焦于如何从零开始构建一个专门用于与AI Agent交互的Web前端。这个前端需要能实时收发消息展示Agent的“思考过程”比如工具调用、执行结果并且要足够健壮能处理网络波动、长时任务等实际情况。技术栈上我选择了React TypeScript WebSocket这套组合拳。React的组件化非常适合构建复杂的交互界面TypeScript能在开发阶段就帮我们规避大量类型错误而WebSocket则是实现全双工实时通信的不二之选。接下来我会带你一步步走完搭建的全过程并分享那些只有真正动手做过才会遇到的“坑”和解决方案。2. 项目初始化与核心依赖选型万事开头难但一个好的开始能省去后面无数麻烦。我们首先用Vite来快速搭建一个React TypeScript的项目骨架。Vite相比传统的Create React App在开发阶段的启动速度和热更新体验上有质的飞跃这对需要频繁调试的实时应用来说至关重要。打开终端执行以下命令npm create vitelatest ai-agent-frontend -- --template react-ts cd ai-agent-frontend npm install这条命令会创建一个名为ai-agent-frontend的新项目并预设好React和TypeScript的基本配置。接下来我们需要安装核心的依赖库。除了React本身我们主要需要以下几个状态管理对于复杂的应用状态如对话列表、连接状态、Agent执行流我推荐使用Zustand。它比Redux更轻量API更直观没有那么多模板代码非常适合中小型项目。UI组件库为了快速搭建美观且一致的界面我选择了Ant Design。它的Message、Modal、Input、Button等组件能极大提升开发效率。当然你也可以用Shadcn/ui、MUI或者纯自己写样式。WebSocket客户端虽然浏览器原生支持WebSocket API但为了更好的重连机制、心跳检测和消息队列管理我们使用react-use-websocket这个Hook库。它封装了常见的WebSocket操作让我们能更专注于业务逻辑。HTTP客户端除了WebSocket有时也需要一些传统的HTTP请求例如获取历史记录、上传文件。Axios是经过时间检验的选择。日期处理处理消息时间戳day.js是轻量级的Moment.js替代品。一次性安装它们npm install zustand antd ant-design/icons react-use-websocket axios day.js npm install -D types/node安装完成后我习惯先清理一下src/App.tsx和src/App.css的默认内容从一个干净的画布开始。同时在tsconfig.json中确保compilerOptions里包含了types: [node]这能避免后续引入Node.js相关类型如process.env时报错。注意关于CSS方案。Vite默认支持CSS Modules和多种预处理器。如果你打算大量自定义样式可以考虑安装tailwindcss。但为了本教程的专注性我们主要使用Ant Design的组件并辅以少量的自定义CSS。3. 构建应用状态中枢用Zustand管理对话与连接在开始画界面之前我们必须先设计好数据如何流动。一个与AI Agent交互的应用其核心状态包括对话消息列表用户和Agent来回发送的消息。WebSocket连接状态连接中、已连接、断开、重连中。Agent执行状态Agent是否正在“思考”或执行工具。可能的错误信息。如果把这些状态散落在各个组件里很快就会变得难以维护。因此我们创建一个全局状态Store。在src目录下新建store文件夹并创建useChatStore.ts// src/store/useChatStore.ts import { create } from zustand; import { persist } from zustand/middleware; // 可选用于持久化存储到localStorage // 定义消息类型 export interface Message { id: string; content: string; sender: user | agent; timestamp: number; type?: thinking | tool_call | tool_result | final; // 用于区分Agent的中间状态 toolName?: string; // 工具调用名称 toolInput?: any; // 工具调用输入 toolOutput?: any; // 工具调用输出 } interface ChatState { // 状态 messages: Message[]; connectionStatus: connecting | connected | disconnected | error; isAgentThinking: boolean; error: string | null; // Actions (操作) addMessage: (message: Message) void; updateMessage: (id: string, updates: PartialMessage) void; clearMessages: () void; setConnectionStatus: (status: ChatState[connectionStatus]) void; setIsAgentThinking: (thinking: boolean) void; setError: (error: string | null) void; // 派生状态/计算属性 getMessageById: (id: string) Message | undefined; } export const useChatStore createChatState()( persist( // 使用persist中间件对话记录刷新页面后不丢失 (set, get) ({ messages: [], connectionStatus: disconnected, isAgentThinking: false, error: null, addMessage: (message) set((state) ({ messages: [...state.messages, message] })), updateMessage: (id, updates) set((state) ({ messages: state.messages.map((msg) msg.id id ? { ...msg, ...updates } : msg ), })), clearMessages: () set({ messages: [] }), setConnectionStatus: (status) set({ connectionStatus: status }), setIsAgentThinking: (thinking) set({ isAgentThinking: thinking }), setError: (error) set({ error }), getMessageById: (id) get().messages.find((msg) msg.id id), }), { name: ai-agent-chat-storage, // localStorage中的key partialize: (state) ({ messages: state.messages }), // 只持久化消息 } ) );这个Store定义了我们的数据模型和所有修改数据的方法。persist中间件让消息列表能自动保存到浏览器的localStorage即使刷新页面聊天记录也不会消失这是一个提升用户体验的小细节。为什么选择Zustand而不是Context API或Redux对于这个规模的实时应用Context API在状态频繁更新时可能导致不必要的重渲染需要配合useMemo和useCallback进行优化心智负担较重。Redux则显得过于重型。Zustand的Store独立于组件树组件通过Hook订阅其需要的部分状态更新精准代码简洁是当前非常流行的轻量级状态管理方案。4. 建立实时通信桥梁WebSocket连接与消息协议WebSocket是实时交互的生命线。我们不仅要建立连接还要设计一套客户端与后端AI Agent服务通信的协议并处理各种网络异常。首先在src目录下创建lib文件夹然后创建websocket.ts文件封装WebSocket逻辑// src/lib/websocket.ts import { useChatStore } from ../store/useChatStore; // 定义客户端发送给服务端的消息格式 export type ClientMessage | { type: chat; content: string; messageId: string; // 客户端生成的消息ID用于关联回复 } | { type: ping; }; // 定义服务端发送给客户端的消息格式 export type ServerMessage | { type: agent_response; messageId: string; // 关联的客户端消息ID content: string; status: complete; } | { type: agent_thought; messageId: string; content: string; // 思考内容 step: number; } | { type: tool_call; messageId: string; toolName: string; toolInput: any; callId: string; } | { type: tool_result; messageId: string; callId: string; // 关联的tool_call ID output: any; success: boolean; } | { type: error; messageId?: string; error: string; } | { type: pong; }; // WebSocket服务类 class WebSocketService { private socket: WebSocket | null null; private reconnectAttempts 0; private maxReconnectAttempts 5; private reconnectDelay 1000; // 初始重连延迟1秒 private heartbeatInterval: NodeJS.Timeout | null null; private url: string; constructor(url: string) { this.url url; } connect(): void { const store useChatStore.getState(); store.setConnectionStatus(connecting); store.setError(null); try { this.socket new WebSocket(this.url); this.socket.onopen () { console.log(WebSocket连接已建立); store.setConnectionStatus(connected); this.reconnectAttempts 0; // 连接成功重置重连计数 this.startHeartbeat(); // 开始心跳 }; this.socket.onmessage (event) { try { const data: ServerMessage JSON.parse(event.data); this.handleServerMessage(data); } catch (error) { console.error(解析服务端消息失败:, error, event.data); } }; this.socket.onclose (event) { console.log(WebSocket连接关闭代码: ${event.code}, 原因: ${event.reason}); store.setConnectionStatus(disconnected); this.stopHeartbeat(); this.attemptReconnect(); }; this.socket.onerror (error) { console.error(WebSocket错误:, error); store.setConnectionStatus(error); store.setError(网络连接出现异常); this.stopHeartbeat(); }; } catch (error) { console.error(创建WebSocket连接失败:, error); store.setConnectionStatus(error); store.setError(无法建立连接); this.attemptReconnect(); } } private handleServerMessage(msg: ServerMessage): void { const store useChatStore.getState(); switch (msg.type) { case agent_thought: // 处理Agent的“思考”过程可以更新某条消息或单独显示 store.addMessage({ id: thought_${Date.now()}, content: 思考: ${msg.content}, sender: agent, timestamp: Date.now(), type: thinking, }); break; case tool_call: // 处理工具调用展示给用户Agent正在做什么 store.addMessage({ id: tool_${msg.callId}, content: 调用工具: ${msg.toolName}, sender: agent, timestamp: Date.now(), type: tool_call, toolName: msg.toolName, toolInput: msg.toolInput, }); break; case tool_result: // 更新对应工具调用的结果 const toolMsg store.messages.find(m m.type tool_call (m as any).callId msg.callId); if (toolMsg) { store.updateMessage(toolMsg.id, { content: ${toolMsg.content} - 结果: ${JSON.stringify(msg.output)}, toolOutput: msg.output, type: tool_result, }); } break; case agent_response: // 最终回复 store.addMessage({ id: agent_${Date.now()}, content: msg.content, sender: agent, timestamp: Date.now(), type: final, }); store.setIsAgentThinking(false); break; case error: store.setError(msg.error); // 如果有关联的消息ID可以在对应消息上显示错误 if (msg.messageId) { // ... 更新特定消息状态为错误 } break; case pong: // 心跳回应正常处理即可 break; } } sendMessage(message: ClientMessage): boolean { if (this.socket this.socket.readyState WebSocket.OPEN) { this.socket.send(JSON.stringify(message)); return true; } else { console.warn(WebSocket未就绪消息发送失败); return false; } } disconnect(): void { this.stopHeartbeat(); if (this.socket) { this.socket.close(1000, 用户主动断开); this.socket null; } useChatStore.getState().setConnectionStatus(disconnected); } private startHeartbeat(): void { this.heartbeatInterval setInterval(() { if (this.socket?.readyState WebSocket.OPEN) { this.sendMessage({ type: ping }); } }, 30000); // 每30秒发送一次心跳 } private stopHeartbeat(): void { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval null; } } private attemptReconnect(): void { if (this.reconnectAttempts this.maxReconnectAttempts) { console.error(已达到最大重连次数(${this.maxReconnectAttempts})停止重连); return; } this.reconnectAttempts; const delay this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1); // 指数退避 console.log(将在 ${delay}ms 后尝试第 ${this.reconnectAttempts} 次重连...); setTimeout(() this.connect(), delay); } } // 导出单例或创建函数。这里我们导出创建函数以便在组件中管理生命周期。 export const createWebSocketService (url: string) new WebSocketService(url);这个WebSocketService类做了几件关键的事连接管理封装了连接、断开、错误处理。消息协议定义了严格的ClientMessage和ServerMessage类型这是前后端协作的契约。清晰的协议是复杂交互的基础。自动重连实现了带指数退避的自动重连机制提升应用的健壮性。心跳保活定期发送ping消息防止连接因中间网络设备超时而被断开。状态同步在收到消息后自动调用Zustand Store的action来更新UI状态。关于消息ID的思考注意我们在chat消息中要求客户端生成messageId。这样做的好处是客户端可以精确地将服务端的响应包括思考、工具调用、最终回复关联到最初的问题上即使网络有延迟或消息乱序也能正确归位。这是一种常见的“客户端关联”模式。5. 打造用户界面聊天组件、输入区与状态展示有了状态管理和通信层现在可以构建用户看到的界面了。我们将创建几个核心组件。5.1 消息列表组件 (MessageList.tsx)这个组件负责渲染所有的聊天消息并根据消息类型用户、Agent思考、工具调用、最终回复展示不同的样式。// src/components/MessageList.tsx import React from react; import { Message as MessageType } from ../store/useChatStore; import { Comment, Tooltip, Typography, Card, Tag, Spin } from antd; import { UserOutlined, RobotOutlined, ToolOutlined, LoadingOutlined } from ant-design/icons; import dayjs from dayjs; const { Text } Typography; interface MessageListProps { messages: MessageType[]; } export const MessageList: React.FCMessageListProps ({ messages }) { const renderMessageContent (msg: MessageType) { switch (msg.type) { case thinking: return ( Card sizesmall style{{ background: #f0f5ff, borderColor: #adc6ff }} Spin indicator{LoadingOutlined spin /} sizesmall / {msg.content} /Card ); case tool_call: return ( Card sizesmall title{ToolOutlined / 执行工具/} Text strong工具名:/Text Tag colorblue{msg.toolName}/Tag br / Text strong输入参数:/Text pre style{{ fontSize: 12px, background: #f6f8fa, padding: 8px }} {JSON.stringify(msg.toolInput, null, 2)} /pre /Card ); case tool_result: return ( Card sizesmall title工具执行结果 style{{ borderLeft: 3px solid #52c41a }} Text strong输出:/Text pre style{{ fontSize: 12px, background: #f6f8fa, padding: 8px }} {JSON.stringify(msg.toolOutput, null, 2)} /pre /Card ); default: // 普通用户或Agent最终回复 return div style{{ whiteSpace: pre-wrap }}{msg.content}/div; } }; return ( div style{{ padding: 20px, overflowY: auto, flex: 1 }} {messages.map((msg) { const isUser msg.sender user; const avatar isUser ? UserOutlined / : RobotOutlined /; const author isUser ? 你 : AI Agent; const datetime dayjs(msg.timestamp).format(HH:mm:ss); return ( Comment key{msg.id} author{author} avatar{avatar} content{renderMessageContent(msg)} datetime{ Tooltip title{dayjs(msg.timestamp).format(YYYY-MM-DD HH:mm:ss)} span{datetime}/span /Tooltip } style{{ textAlign: isUser ? right : left, marginBottom: 16px, }} / ); })} /div ); };这个组件利用Ant Design的Comment组件来展示对话气泡并根据message.type渲染不同的内容块。对于工具调用和结果我们用Card和pre标签来格式化显示JSON数据使其更易读。5.2 消息输入与发送组件 (MessageInput.tsx)这是用户与Agent交互的主要入口。我们需要处理文本输入、发送并禁用输入框当Agent正在思考时。// src/components/MessageInput.tsx import React, { useState, KeyboardEvent } from react; import { Input, Button, Space, message as antdMessage } from antd; import { SendOutlined } from ant-design/icons; import { useChatStore } from ../store/useChatStore; import { v4 as uuidv4 } from uuid; // 需要安装npm install uuid types/uuid const { TextArea } Input; interface MessageInputProps { onSendMessage: (content: string, messageId: string) void; disabled?: boolean; } export const MessageInput: React.FCMessageInputProps ({ onSendMessage, disabled }) { const [inputValue, setInputValue] useState(); const isAgentThinking useChatStore((state) state.isAgentThinking); const connectionStatus useChatStore((state) state.connectionStatus); const handleSend () { const trimmedContent inputValue.trim(); if (!trimmedContent) { antdMessage.warning(请输入消息内容); return; } if (connectionStatus ! connected) { antdMessage.error(未连接到Agent服务请检查连接); return; } const newMessageId uuidv4(); // 生成唯一ID用于关联 onSendMessage(trimmedContent, newMessageId); setInputValue(); // 清空输入框 }; const handleKeyPress (e: KeyboardEventHTMLTextAreaElement) { // 支持 CtrlEnter 或 CmdEnter 发送 if (e.key Enter (e.ctrlKey || e.metaKey)) { e.preventDefault(); // 防止TextArea换行 handleSend(); } }; const isSendDisabled disabled || isAgentThinking || connectionStatus ! connected || !inputValue.trim(); return ( Space.Compact block style{{ width: 100%, padding: 20px, background: #fff, borderTop: 1px solid #f0f0f0 }} TextArea value{inputValue} onChange{(e) setInputValue(e.target.value)} onKeyDown{handleKeyPress} placeholder{connectionStatus connected ? 输入您的问题CtrlEnter发送... : 等待连接...} autoSize{{ minRows: 1, maxRows: 4 }} disabled{isAgentThinking || connectionStatus ! connected} style{{ resize: none }} / Button typeprimary icon{SendOutlined /} onClick{handleSend} disabled{isSendDisabled} loading{isAgentThinking} 发送 /Button /Space.Compact ); };这里有几个细节消息ID生成使用uuid库为每条用户消息生成全局唯一ID用于后端关联响应。发送触发除了按钮点击还支持CtrlEnter/CmdEnter快捷键发送这是聊天应用的常见习惯。状态联动输入框和发送按钮的禁用状态与WebSocket连接状态、Agent思考状态绑定防止用户在不适当时机操作。5.3 连接状态指示器 (ConnectionStatus.tsx)一个直观的状态指示器能让用户清楚知道当前应用的连接状况。// src/components/ConnectionStatus.tsx import React from react; import { Badge, Tag, Typography } from antd; import { WifiOutlined, DisconnectOutlined, ExclamationCircleOutlined, SyncOutlined } from ant-design/icons; import { useChatStore } from ../store/useChatStore; const { Text } Typography; const statusConfig { connecting: { color: orange, icon: SyncOutlined spin /, text: 连接中... }, connected: { color: green, icon: WifiOutlined /, text: 已连接 }, disconnected: { color: default, icon: DisconnectOutlined /, text: 未连接 }, error: { color: red, icon: ExclamationCircleOutlined /, text: 连接错误 }, } as const; export const ConnectionStatus: React.FC () { const connectionStatus useChatStore((state) state.connectionStatus); const error useChatStore((state) state.error); const config statusConfig[connectionStatus]; return ( div style{{ padding: 10px 20px, borderBottom: 1px solid #f0f0f0, background: #fafafa }} Space sizemiddle Tag icon{config.icon} color{config.color} {config.text} /Tag {error ( Text typedanger style{{ fontSize: 12px }} ExclamationCircleOutlined / {error} /Text )} /Space /div ); };5.4 整合主应用组件 (App.tsx)最后我们将所有组件和逻辑整合到主应用文件中。// src/App.tsx import React, { useEffect, useRef } from react; import { Layout, message as antdMessage } from antd; import { MessageList } from ./components/MessageList; import { MessageInput } from ./components/MessageInput; import { ConnectionStatus } from ./components/ConnectionStatus; import { useChatStore } from ./store/useChatStore; import { createWebSocketService, ClientMessage } from ./lib/websocket; import ./App.css; const { Header, Content, Footer } Layout; // 注意这里替换成你实际的AI Agent后端WebSocket地址 const WS_URL import.meta.env.VITE_WS_URL || ws://localhost:8080/ws; function App() { const { messages, addMessage, setConnectionStatus, setIsAgentThinking, connectionStatus } useChatStore(); const wsServiceRef useRefReturnTypetypeof createWebSocketService | null(null); // 初始化WebSocket连接 useEffect(() { wsServiceRef.current createWebSocketService(WS_URL); wsServiceRef.current.connect(); // 组件卸载时断开连接 return () { wsServiceRef.current?.disconnect(); }; }, []); // 空依赖数组确保只创建一次 const handleSendMessage (content: string, messageId: string) { // 1. 立即在UI中添加用户消息 addMessage({ id: messageId, content, sender: user, timestamp: Date.now(), }); // 2. 设置Agent思考状态 setIsAgentThinking(true); // 3. 通过WebSocket发送消息 const message: ClientMessage { type: chat, content, messageId, }; const sent wsServiceRef.current?.sendMessage(message); if (!sent) { antdMessage.error(消息发送失败请检查连接); setIsAgentThinking(false); // 可选将用户消息标记为发送失败 } }; return ( Layout style{{ minHeight: 100vh }} Header style{{ color: #fff, fontWeight: bold, fontSize: 18px }} AI Agent 交互控制台 /Header ConnectionStatus / Content style{{ display: flex, flexDirection: column }} MessageList messages{messages} / /Content Footer style{{ padding: 0 }} MessageInput onSendMessage{handleSendMessage} disabled{connectionStatus ! connected} / /Footer /Layout ); } export default App;在App.tsx中我们完成了最后的拼图连接生命周期管理在组件挂载时创建并连接WebSocket服务在卸载时断开连接。发送消息流程当用户发送消息时我们立即乐观更新UI添加用户消息然后通过WebSocket发送。这提供了即时的反馈。状态传递将必要的状态和回调函数传递给子组件。6. 样式优化与响应式适配基本的布局已经完成但为了让界面更美观我们需要一些CSS。修改src/App.css/* src/App.css */ #root { max-width: 1200px; margin: 0 auto; padding: 0; } /* 让消息列表区域可以滚动 */ .ant-layout-content { flex: 1; overflow: hidden; /* 防止整个布局滚动 */ } /* 自定义滚动条样式 */ .message-list-container { overflow-y: auto; height: 100%; } .message-list-container::-webkit-scrollbar { width: 6px; } .message-list-container::-webkit-scrollbar-track { background: #f1f1f1; } .message-list-container::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 3px; } .message-list-container::-webkit-scrollbar-thumb:hover { background: #a8a8a8; } /* 调整Ant Design组件默认样式 */ .ant-comment-content-author { display: flex; align-items: center; justify-content: space-between; } /* 使输入框在移动端更友好 */ media (max-width: 768px) { .ant-space-compact { flex-direction: column; } .ant-space-compact .ant-input { margin-bottom: 8px; } .ant-space-compact .ant-btn { width: 100%; } }这些样式优化了滚动体验并做了简单的移动端适配。你可以根据品牌风格进一步调整颜色和间距。7. 环境配置与开发调试项目基本成型现在需要配置环境变量并启动开发服务器。环境变量在项目根目录创建.env.development文件用于本地开发环境。VITE_WS_URLws://localhost:8080/ws在Vite中以VITE_开头的变量才会被暴露给客户端代码。我们在App.tsx中通过import.meta.env.VITE_WS_URL读取它。启动开发服务器npm run devVite会启动一个本地开发服务器通常地址是http://localhost:5173。打开浏览器访问即可。模拟后端进行测试在真正的AI Agent后端准备好之前我们可以创建一个简单的Node.js WebSocket服务器来模拟验证前端逻辑。在项目根目录创建mock-server.js// mock-server.js const WebSocket require(ws); const wss new WebSocket.Server({ port: 8080 }); wss.on(connection, function connection(ws) { console.log(客户端已连接); ws.on(message, function message(data) { console.log(收到客户端消息: %s, data); const clientMsg JSON.parse(data); if (clientMsg.type chat) { // 模拟Agent的思考过程 ws.send(JSON.stringify({ type: agent_thought, messageId: clientMsg.messageId, content: 我正在分析你的问题..., step: 1 })); setTimeout(() { ws.send(JSON.stringify({ type: tool_call, messageId: clientMsg.messageId, toolName: search_web, toolInput: { query: clientMsg.content }, callId: call_1 })); }, 500); setTimeout(() { ws.send(JSON.stringify({ type: tool_result, messageId: clientMsg.messageId, callId: call_1, output: { result: 找到了相关答案... }, success: true })); }, 1500); setTimeout(() { ws.send(JSON.stringify({ type: agent_response, messageId: clientMsg.messageId, content: 这是根据您的问题${clientMsg.content}生成的模拟回答。, status: complete })); }, 2000); } if (clientMsg.type ping) { ws.send(JSON.stringify({ type: pong })); } }); ws.on(close, () console.log(客户端已断开)); }); console.log(模拟WebSocket服务器运行在 ws://localhost:8080);使用node mock-server.js启动这个模拟服务器然后刷新前端页面就可以进行完整的交互测试了。8. 生产环境构建与部署注意事项当开发完成准备上线时需要执行构建并考虑部署细节。构建优化npm run build这会在dist目录生成优化后的静态文件HTML, CSS, JS。你可以使用npm run preview命令在本地预览构建结果。环境变量创建.env.production文件填入生产环境的WebSocket地址通常是wss协议。VITE_WS_URLwss://your-production-server.com/ws部署将dist文件夹内的所有文件上传到任何静态文件托管服务如Vercel/Netlify关联Git仓库自动部署。Nginx/Apache配置Web服务器将请求指向dist目录。对象存储CDN如阿里云OSS、腾讯云COS配合CDN加速。关键注意事项WebSocket安全 (WSS)生产环境必须使用wss://WebSocket Secure这与HTTPS对应。大多数云服务商和反向代理如Nginx都支持WebSocket代理需要相应配置。Nginx配置示例location /ws/ { proxy_pass http://backend_server; # 你的AI Agent后端地址 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_read_timeout 60s; # 长连接超时时间 }跨域问题 (CORS)如果前端和后端不在同一个域名下后端需要正确配置CORS头部允许前端域名进行WebSocket连接。错误监控考虑在前端集成Sentry等错误监控工具捕获运行时错误。版本管理在构建命令中注入版本号或构建时间戳避免浏览器缓存旧版本文件。9. 进阶优化与功能扩展思路一个基础可用的交互前端已经搭建完成。但在实际产品中我们还可以从以下几个方向进行深化1. 消息持久化与历史记录目前的持久化只依赖浏览器localStorage容量有限且无法跨设备。可以增加与后端API集成实现消息的云端存储和分页加载。增加“会话”Conversation的概念允许用户创建、切换、删除不同的对话线程。2. 更丰富的消息类型与渲染Markdown渲染使用react-markdown库渲染Agent返回的Markdown格式内容支持代码高亮、表格等。文件上传允许用户上传图片、文档前端将文件转换为Base64或上传到文件服务后将URL或标识符发送给Agent处理。结构化数据展示如果Agent返回表格、图表数据可以集成ECharts等图表库进行可视化渲染。3. 连接管理与用户体验手动重连按钮在连接断开时除了自动重连提供一个手动重连按钮。连接质量监测监测WebSocket的延迟和丢包在UI上给出网络质量提示。离线提示与消息队列在断开连接时将用户发送的消息暂存到本地队列待连接恢复后自动重发。4. Agent执行流程的可视化对于复杂的Agent如使用LangChain、CrewAI等工作流框架可以开发一个专用的“执行面板”以流程图或时间线的方式实时展示Agent的思考步骤、工具调用链和状态转换这对于调试和展示非常有用。5. 主题与可访问性支持深色/浅色主题切换。遵循WCAG标准确保色盲用户、键盘导航用户等都能无障碍使用。搭建这样一个前端的过程远不止是调用几个API和摆弄组件。它要求你对实时通信的稳定性、前端状态管理的复杂性、以及用户与AI系统交互的细节有深入的理解。每一步设计从消息ID的关联到自动重连的策略都直接影响着最终产品的可靠性和用户体验。希望这个从零开始的指南能为你构建自己的AI Agent交互界面提供一个坚实的起点。剩下的就是根据你的具体业务逻辑去完善和打磨每一个细节了。
返回列表