
第35期 | AI Agent前端交互 今天你将学会实现 AI Agent 的前端交互工具调用展示 思考过程可视化设计多轮对话中 Agent 的「思考 → 调用工具 → 观察结果 → 回答」循环界面实现工具调用的实时展示搜索数据库、调用 API、执行操作理解 Agent 交互的 UX 设计原则——让用户看到 AI 在做什么 核心知识Agent 不只是聊天它有「思考-行动」循环普通聊天 AI 的工作流很简单用户问 → AI 回答。Agent 的工作流是一个循环用户提问 → Agent 思考我要做什么→ 选择工具 → 执行工具 → 观察结果 → 再次思考还需要做什么→ 选择工具 → 执行 → 观察 → ... → 最终回答前端需要可视化这个循环——让用户看到 Agent 在思考什么、调用了什么工具、得到了什么结果。这不是为了炫酷而是为了信任——用户看到 AI 的思考过程才能相信它的回答。Agent 交互的三种可视化模式模式1精简模式默认只展示最终回答中间的思考/工具调用折叠在「查看详情」中。用户帮我查一下订单 #12345 的状态 AI订单 #12345 的当前状态是「已发货」预计明天送达。 [查看思考过程 ▼]模式2详细模式可切换每一步思考、每次工具调用都展开显示。用户帮我查一下订单 #12345 的状态 Agent 思考用户要查订单状态我需要调用 orders_search 工具 调用工具orders_search 参数{ order_id: 12345 } 结果{ status: shipped, eta: 2026-06-27 } Agent 思考我拿到了订单信息现在可以回答用户了 AI订单 #12345 的当前状态是「已发货」预计明天送达。模式3实时模式推荐Agent 每一步都实时展示像看直播一样。用户帮我查一下订单 #12345 的状态 正在思考... ← 实时出现 正在调用 orders_search... ← 实时出现 ✓ 调用完成耗时 1.2s ← 实时出现 正在生成回答... ← 实时出现 AI订单 #12345 的当前状态是「已发货」预计明天送达。工具调用展示组件// features/agent/components/ToolCall.tsx interface ToolCall { id: string; name: string; // 工具名称orders_search / weather_query / calculator args: Recordstring, unknown; // 调用参数 result?: unknown; // 返回结果 status: running | completed | error; duration?: number; // 执行耗时ms error?: string; } interface ToolCallProps { call: ToolCall; expanded?: boolean; } // 工具图标映射 const toolIcons: Recordstring, { icon: string; label: string } { orders_search: { icon: , label: 搜索订单 }, weather_query: { icon: , label: 查询天气 }, calculator: { icon: , label: 计算 }, web_search: { icon: , label: 网页搜索 }, database_query: { icon: , label: 数据库查询 }, }; export function ToolCall({ call, expanded false }: ToolCallProps) { const toolInfo toolIcons[call.name] || { icon: , label: call.name }; const [isExpanded, setIsExpanded] useState(expanded); return ( div classNamemy-2 rounded-lg border border-gray-200 bg-gray-50 dark:border-gray-600 dark:bg-gray-800 {/* 头部工具名 状态 */} div classNameflex items-center justify-between px-3 py-2 cursor-pointer onClick{() setIsExpanded(!isExpanded)} div classNameflex items-center gap-2 text-sm span{toolInfo.icon}/span span classNamefont-medium{toolInfo.label}/span {call.status running ( Loader2 size{14} classNameanimate-spin text-blue-500 / )} {call.status completed ( CheckCircle size{14} classNametext-green-500 / )} {call.status error ( XCircle size{14} classNametext-red-500 / )} /div div classNameflex items-center gap-2 text-xs text-gray-400 {call.duration span{call.duration}ms/span} ChevronDown size{14} className{isExpanded ? rotate-180 : } / /div /div {/* 展开内容参数 结果 */} {isExpanded ( div classNamepx-3 pb-3 text-sm {/* 调用参数 */} div classNamemb-2 div classNametext-xs text-gray-500 mb-1调用参数/div pre classNamebg-gray-100 rounded p-2 text-xs font-mono overflow-x-auto dark:bg-gray-900 {JSON.stringify(call.args, null, 2)} /pre /div {/* 返回结果 */} {call.result ( div div classNametext-xs text-gray-500 mb-1返回结果/div pre classNamebg-gray-100 rounded p-2 text-xs font-mono overflow-x-auto dark:bg-gray-900 {typeof call.result string ? call.result : JSON.stringify(call.result, null, 2)} /pre /div )} {/* 错误信息 */} {call.error ( div classNametext-red-500 text-xs{call.error}/div )} /div )} /div ); }思考过程可视化// features/agent/components/ThinkingStep.tsx interface ThinkingStep { id: string; content: string; // Agent 的思考内容 timestamp: string; } interface ThinkingStepProps { step: ThinkingStep; } export function ThinkingStep({ step }: ThinkingStepProps) { return ( div classNamemy-1 flex items-start gap-2 text-sm div classNamew-5 h-5 rounded-full bg-yellow-100 flex items-center justify-center text-xs shrink-0 /div div classNametext-gray-600 dark:text-gray-300 italic {step.content} /div /div ); }Agent 消息类型定义Agent 的对话比普通聊天复杂——一条消息可能包含多种内容// features/agent/types/index.tsinterfaceAgentMessage{id:string;role:user|assistant;// Agent 消息的内容是一个数组每项可以是不同类型content:MessageContent[];timestamp:string;}// 内容类型定义typeMessageContent|TextContent// 普通文本|ThinkingContent// 思考过程|ToolCallContent// 工具调用|ToolResultContent;// 工具返回结果interfaceTextContent{type:text;text:string;}interfaceThinkingContent{type:thinking;thinking:string;}interfaceToolCallContent{type:tool_call;toolCall:ToolCall;}interfaceToolResultContent{type:tool_result;toolCallId:string;result:unknown;}Agent 消息渲染// features/agent/components/AgentMessage.tsx import { MarkdownRenderer } from ../../chat/components/MarkdownRenderer; import { ToolCall } from ./ToolCall; import { ThinkingStep } from ./ThinkingStep; interface AgentMessageProps { message: AgentMessage; displayMode: compact | detailed | live; } export function AgentMessage({ message, displayMode }: AgentMessageProps) { // 根据显示模式过滤内容 const visibleContent getVisibleContent(message.content, displayMode); return ( div classNamepy-2 {visibleContent.map((item, idx) { switch (item.type) { case text: return ( div key{idx} classNametext-gray-800 dark:text-gray-200 MarkdownRenderer content{item.text} / /div ); case thinking: return ThinkingStep key{idx} step{{ id: ${idx}, content: item.thinking, timestamp: }} /; case tool_call: return ( ToolCall key{idx} call{item.toolCall} expanded{displayMode detailed || displayMode live} / ); case tool_result: return null; // tool_result 已经嵌套在 tool_call 中展示 default: return null; } })} /div ); } // 根据显示模式决定展示哪些内容 function getVisibleContent( content: MessageContent[], mode: compact | detailed | live ): MessageContent[] { switch (mode) { case compact: // 精简模式只展示最终文本回答 return content.filter(c c.type text); case detailed: // 详细模式展示所有内容 return content; case live: // 实时模式展示所有内容实时追加 return content; default: return content; } }显示模式切换// features/agent/components/DisplayModeToggle.tsx import { Eye, EyeOff, Radio } from lucide-react; interface DisplayModeToggleProps { mode: compact | detailed | live; onChange: (mode: compact | detailed | live) void; } const modes [ { value: compact, icon: EyeOff, label: 精简, desc: 只看最终回答 }, { value: detailed, icon: Eye, label: 详细, desc: 查看每一步思考 }, { value: live, icon: Radio, label: 实时, desc: 实时观看Agent执行 }, ] as const; export function DisplayModeToggle({ mode, onChange }: DisplayModeToggleProps) { return ( div classNameflex gap-1 rounded-lg border border-gray-200 p-1 dark:border-gray-600 {modes.map((m) ( button key{m.value} onClick{() onChange(m.value)} className{flex items-center gap-1 px-3 py-1 rounded text-sm transition-colors ${mode m.value ? bg-blue-500 text-white : text-gray-500 hover:bg-gray-100}} m.icon size{14} / {m.label} /button ))} /div ); }Agent 流式响应的前端解析Agent 的 SSE 流比普通聊天更复杂——每种内容类型有不同的 SSE 格式data: {type: thinking, thinking: 用户要查订单我需要调用 orders_search}\n\n data: {type: tool_call, toolCall: {name: orders_search, args: {...}, status: running}}\n\n data: {type: tool_result, toolCallId: xxx, result: {...}}\n\n data: {type: text, content: 订单状态是...}\n\n data: [DONE]\n\n前端解析逻辑// lib/agent-stream-parser.tsexportfunctionparseAgentSSEStream(rawStream:ReadableStreamUint8Array){constdecodernewTextDecoder();letbuffer;returnnewReadableStreamMessageContent({asyncstart(controller){constreaderrawStream.getReader();try{while(true){const{done,value}awaitreader.read();if(done)break;bufferdecoder.decode(value,{stream:true});constlinesbuffer.split(\n);bufferlines.pop()||;for(constlineoflines){if(!line.startsWith(data: ))continue;constdataline.slice(6);if(data[DONE]){controller.close();return;}try{constparsedJSON.parse(data);switch(parsed.type){casethinking:controller.enqueue({type:thinking,thinking:parsed.thinking,});break;casetool_call:controller.enqueue({type:tool_call,toolCall:parsed.toolCall,});break;casetool_result:controller.enqueue({type:tool_result,toolCallId:parsed.toolCallId,result:parsed.result,});break;casetext:controller.enqueue({type:text,text:parsed.content,});break;}}catch{// 无法解析的行}}}}catch(error){controller.error(error);}},});}常见误区误区1只展示最终回答Agent 的核心价值不只是结果更是过程。用户需要看到 AI 思考了什么、调用了什么工具、为什么得出这个结论。误区2所有内容默认展开展开所有思考过程会让界面很乱。默认精简模式用户想了解细节时切换到详细模式。误区3工具调用信息对用户没用工具调用展示建立信任——用户看到 AI 真的搜索了数据库、调用了 API才能相信回答不是编造的。 AI协作实战实战场景设计 Agent 交互的完整 UX我给 AI 的 prompt设计一个 AI Agent 技术助手的完整交互界面。 功能需求 - 用户提问后Agent 会思考、搜索知识库、调用工具、最终回答 - 三种显示模式切换精简/详细/实时 - 工具调用展示名称、参数、结果、耗时 - 思考过程展示图标 灰色斜体文字 - 最终回答的 Markdown 渲染 引用来源 UI要求 - 消息列表垂直排列Agent 消息中的思考/工具/回答按时间顺序排列 - 工具调用卡片可展开/收起 - 实时模式下每一步都即时出现有动画过渡 - 用 shadcn/ui 风格 Tailwind CSS 请给出完整的组件树和核心组件代码。AI 的输出审查后组件树合理AgentInterface → AgentMessageList → AgentMessage → ThinkingStep / ToolCall / MarkdownRenderer / SourceReference DisplayModeToggle ChatInput我的调整✅ 组件拆分清晰❌ 实时模式下的动画过渡需要用 CSS transitionAI 用了opacity动画但max-height也要过渡才能让内容平滑展开 → 手动补上✅ 工具图标映射表实用——每个工具都有专属图标和中文标签学到了什么Agent 交互的 UX 关键是渐进式展示——不是一下子展示所有内容而是按 Agent 执行步骤逐步出现。实时模式下的动画过渡需要同时处理 opacity 和 max-height。 动手练习练习1简单实现 ToolCall 展示组件实现一个工具调用卡片组件展示工具名 状态图标运行中/完成/失败可展开的参数 结果区域执行耗时练习2中等实现 Agent 消息渲染用 AgentMessage 组件渲染一条包含思考 工具调用 文本回答的完整消息思考部分用 图标 灰色斜体工具调用用 ToolCall 组件文本部分用 MarkdownRenderer支持精简/详细两种模式切换练习3挑战实现完整的 Agent 交互界面组合所有组件实现完整的 Agent 交互ChatInput 输入框AgentMessageList 消息列表DisplayModeToggle 模式切换Agent SSE 流解析实时模式下每步即时出现 动画过渡 本期要点Agent 有思考-行动循环思考 → 选择工具 → 执行 → 观察 → 再思考 → 最终回答三种显示模式精简只看回答/ 详细看每步/ 实时直播式过程可视化建立信任用户看到 AI 真的搜索了、调用了、验证了才能相信回答消息内容是多类型的一条 Agent 消息可以包含文本 思考 工具调用 工具结果SSE 流解析更复杂区分 thinking/tool_call/tool_result/text 四种数据类型 下期预告下一期是模块四的综合实战——你将开发一个完整的 AI 助手应用对话 知识库 工具调用一个产品级的项目。如果你没有苹果电脑需要上传ios到APPStore可以访问以下网站iPA上传工具 - IPA解析与AppStore提交