在上一章中,我们打通了 AI 对话的核心链路,实现了基础的流式聊天和 Markdown 渲染。但这距离一个可以交付给真实用户的“企业级产品”还有很长的路要走。
在企业级应用中,体验即生产力。用户不会容忍一个每次刷新页面就丢失聊天记录、无法停止生成、代码无法一键复制的 AI 助手。本章我们不增加新的 AI 能力,而是专注于“打磨体验”,将零散的功能点整合成一套生产级的聊天系统。
1. 为什么需要企业级聊天体验?
AI 聊天界面的状态管理远比传统表单复杂。我们需要处理以下核心痛点:
- 状态持久化:浏览器刷新后,历史对话不能丢失。
- 交互控制:当 AI 回答过长或方向错误时,用户需要随时“踩刹车”(停止生成)或“重头再来”(重新生成)。
- 视觉反馈:AI 正在思考、正在调用工具、正在输出时,UI 需要有明确的视觉状态。
- 阅读体验:长代码需要一键复制,长对话需要自动滚动到底部,深色模式需要无缝切换。
2. 全局状态管理与聊天体验架构
为了支撑这些体验,我们需要对现有的状态管理进行升级。
设计思考:
Vercel AI SDK 的useChat已经内置了stop()和reload()方法,我们不需要自己手写 AbortController 或重新发送请求。但为了实现聊天历史持久化,我们需要结合 Zustand 或 React Context 将messages与数据库同步。
3. 核心体验功能实现
3.1 停止生成与重新生成
这是 AI 聊天中最基础的控制能力。
// src/components/chat/ChatControls.tsximport{Button}from'@heroui/react';import{useChat}from'ai/react';exportfunctionChatControls({stop,reload,isLoading}:Pick<ReturnType<typeofuseChat>,'stop'|'reload'|'isLoading'>){return(<div className="flex gap-2 mb-2">{/* 停止生成:仅在流式输出时显示 */}{isLoading&&(<Button size="sm"color="danger"variant="flat"onPress={stop}>停止生成</Button>)}{/* 重新生成:仅在流式输出结束后显示 */}{!isLoading&&(<Button size="sm"color="primary"variant="flat"onPress={reload}>重新生成</Button>)}</div>);}3.2 代码块一键复制
AI 生成的代码往往很长,手动选中复制体验极差。我们需要在 Markdown 渲染组件中加入复制按钮。
// src/components/chat/CodeBlock.tsximport{useState}from'react';import{Button}from'@heroui/react';exportfunctionCodeBlock({children,className}:{children:string;className?:string}){const[copied,setCopied]=useState(false);consthandleCopy=async()=>{awaitnavigator.clipboard.writeText(children);setCopied(true);setTimeout(()=>setCopied(false),2000);};return(<div className="relative group"><Button size="sm"className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity"onPress={handleCopy}>{copied?' 已复制':' 复制'}</Button>{/* 这里接入上一章的 SyntaxHighlighter */}<pre className={className}><code>{children}</code></pre></div>);}3.3 自动滚动到底部
当 AI 持续输出时,页面必须自动跟随滚动,否则用户需要手动拖拽滚动条。
// src/hooks/useAutoScroll.tsimport{useEffect,useRef}from'react';exportfunctionuseAutoScroll(dependency:any){constbottomRef=useRef<HTMLDivElement>(null);useEffect(()=>{bottomRef.current?.scrollIntoView({behavior:'smooth'});},[dependency]);returnbottomRef;}// 在 ChatWindow 中使用:// const bottomRef = useAutoScroll(messages[messages.length - 1]?.content);// <div ref={bottomRef} />3.4 消息状态与 Token 统计
在生产环境中,我们需要让用户知道 AI 当前的状态,以及本次对话消耗了多少 Token(用于成本控制)。
// src/components/chat/MessageStatus.tsxexportfunctionMessageStatus({isLoading,tokenCount}:{isLoading:boolean;tokenCount?:number}){if(isLoading){return<div className="text-xs text-gray-400 animate-pulse">AI正在思考并生成回复...</div>;}if(tokenCount){return<div className="text-xs text-gray-400">本次消耗 Token:{tokenCount}</div>;}returnnull;}3.5 主题切换与响应式布局
企业级应用必须支持亮/暗色模式,并且在移动端有完美的适配。HeroUI 原生支持 next-themes。
// src/app/layout.tsximport{ThemeProvider}from'next-themes';exportdefaultfunctionRootLayout({children}){return(<html lang="zh-CN"suppressHydrationWarning><body><ThemeProvider attribute="class"defaultTheme="system"enableSystem><HeroUIProvider>{children}</HeroUIProvider></ThemeProvider></body></html>);}4. 聊天历史持久化(数据层)
体验再好,刷新丢数据也是不可接受的。我们需要在useChat的onFinish回调中,将完整的对话存入 PostgreSQL。
// src/app/api/chat/route.tsexportasyncfunctionPOST(req:Request){const{messages,conversationId}=awaitreq.json();constresult=streamText({model:openai('gpt-4o'),messages,});// 流式响应结束后,将 AI 回复存入数据库result.onFinish(async({finishReason,usage})=>{awaitprisma.message.create({data:{conversationId,role:'assistant',content:result.text,// 获取完整文本tokenCount:usage.totalTokens,}});});returnresult.toDataStreamResponse();}5. 测试验证
验证清单:
- 发送长文本,点击“停止生成”,AI 立即停止输出。
- 点击“重新生成”,AI 重新回答上一个问题。
- 发送包含代码的问题,鼠标悬停代码块,点击“复制”按钮,粘贴内容正确。
- 切换系统主题,页面无缝切换亮/暗色。
- 刷新页面,历史聊天记录依然存在。
6. 常见问题与踩坑分析
问题 1:自动滚动在用户手动向上翻阅时被打断
原因:只要messages更新,scrollIntoView就会触发,导致用户无法查看历史消息。
解决:在useAutoScroll中加入判断,如果用户当前不在底部(scrollTop + clientHeight < scrollHeight - 50),则不触发自动滚动。
问题 2:onFinish 中获取不到完整的 AI 回复
原因:流式响应是异步的,直接在 streamText 返回后读取 result.text 可能为空。
解决:必须使用 result.onFinish 回调,或者在流结束后通过 await result.text 获取。
本章总结
- 我们实现了停止生成、重新生成等核心交互控制。
- 加入了代码一键复制、自动滚动、主题切换等生产级 UI 体验。
- 通过 onFinish 回调实现了聊天历史的数据库持久化。
- 引入了 Token 统计和消息状态展示,增强了系统的透明度。
至此,你的聊天界面已经达到了商业产品的体验标准。
但目前的 AI 依然是一个“只会说不会做”的模型。从下一章开始,我们将进入 AI Agent 的核心领域——Tool Calling(工具调用),让 AI 真正拥有执行任务的能力。