关于 ai-ui-patterns
ai-ui-patterns 是面向 React 开发者的 AI 界面设计指南,涵盖 Vite 与 Next.js 实现。内容聚焦对话状态、流式响应和错误处理等模式,并介绍 Vercel AI SDK 与可复用组件。
主要功能
- 01管理提示词与对话状态
- 02流式呈现 AI 响应
- 03输入防抖处理
- 04错误反馈与重试
- 05构建可复用界面组件
- 06比较 Vite 与 Next.js 架构
适用场景
- 01为 React 应用构建聊天界面
- 02实现实时 AI 自动补全
- 03安全地从服务端调用 AI API
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ patternsdev/ skills --skill ai-ui-patterns安装后在 Claude Code 中输入/ai-ui-patterns确认已加载。
详细介绍
AI UI 模式
目录
构建 AI 驱动的界面——从聊天机器人到智能助手——需要将后端 AI 服务与响应式 UI 组件仔细集成。在本章中,我们将探讨此类界面的 React 设计模式,重点介绍 两种实现方式:普通 React 应用(使用 Vite)和 Next.js 应用。我们将使用 OpenAI 的 API(通过 Vercel AI SDK)作为 AI 引擎,并使用 TailwindCSS 进行样式设计。主要内容包括提示词管理、流式响应、输入防抖、错误处理,以及这些模式在 Vite 和 Next.js 中的差异。我们还将介绍可复用的组件模式,以及用于构建精致聊天 UI 的 Vercel AI UI 组件(AI Elements)。
适用场景
- 构建可流式传输大模型响应的对话式 AI 界面时,可使用此模式
- 将 OpenAI、Anthropic 或其他 AI 提供商集成到 React 应用时,此模式很有帮助
- 需要提示词管理、流式传输、错误处理和 AI 专属 UI 模式时,可使用此模式
说明
- 使用 Vercel AI SDK 的
useChathook 管理对话状态和流式响应 - 将 API 密钥保存在服务端——使用 Next.js API 路由或单独的后端来调用 AI
- 启用流式传输(
stream: true),让聊天界面能够实时响应并输出内容 - 为自动补全功能对输入进行防抖;聊天时在响应流式传输期间禁用输入
- 构建可复用组件(ChatMessage、InputBox),使其与数据获取逻辑解耦
详情
注意: 虽然本文以 OpenAI 为例,但 Vercel AI SDK 支持多个模型提供商,包括 Gemini、OpenAI 和 Anthropic。通过 SDK 的统一接口,你可以轻松切换提供商——我们只是选择其中一种进行演示。
简介:React 中的 AI 界面
随着 ChatGPT 等大模型的兴起,AI 驱动的用户界面(UI)逐渐流行起来。与传统 UI 不同,AI 界面通常涉及对话交互、动态内容流式传输和异步后端调用。这给 React 开发者带来了独特的挑战,也催生了相应的模式。典型的 AI 聊天界面由前端(用于输入用户内容并显示响应)和后端(用于调用 AI 模型)组成。后端对于将 API 密钥和繁重的处理工作放在客户端之外至关重要,有助于提升安全性和性能。Vercel AI SDK 等工具简化了连接 AI 提供商(OpenAI、HuggingFace 等)并实时流式传输响应的过程。我们将探讨如何设置 Next.js 应用和 Vite(React)应用来处理这些问题,并讨论适用于两者的最佳实践。
涵盖的主要模式:
- 组织 AI 提示词数据并管理对话状态
- 将 AI 响应流式传输到 UI,以便实时反馈
- 对用户输入进行防抖,避免频繁调用 API
- 处理错误并在用户体验中提供回退方案
- 用于消息、输入等的可复用 UI 组件(使用 TailwindCSS)
- 架构差异:Next.js 路由处理程序与使用 Node 后端的 Vite
读完本章后,无论你偏好 Next.js 还是 Vite 工具链,都将能够构建响应迅速、稳健可靠的 React AI 驱动界面。
项目设置与工具
在开始编写代码之前,请确保已安装所需的软件包并完成配置:
-
React & Vite: 初始化一个 Vite + React 项目(例如
npm create vite@latest my-ai-app -- --template react)。对于 Next.js,可以使用npx create-next-app或 Next 13 App Router 模板。两种方式都可行,我们会在后续内容中介绍它们的区别。 -
TailwindCSS: 在项目中配置 Tailwind,以便快速设置样式。
-
OpenAI API & Vercel AI SDK: 安装 OpenAI 的库或 Vercel AI SDK。我们将使用 Vercel AI SDK(
npm i ai),它提供实用的 React Hooks(useChat、useCompletion)和服务端工具。该 SDK 与框架无关,可用于 Next.js、原生 React、Svelte 等。它简化了流式传输和状态管理,并且免费且开源。 -
API 密钥: 从 OpenAI 控制台获取 OpenAI API 密钥,并妥善保存。在 Next.js 中,将它放入
.env.local(例如OPENAI_API_KEY=sk-...),切勿提交此文件。在 Vite 应用中,不要在客户端代码中暴露密钥,而应使用后端代理或服务端环境变量。
设置 AI 端点(Next.js 与 Vite)
Next.js 实现: Next.js 允许我们将路由处理程序创建为无服务器函数。我们可以定义一个 API 路由,供 React 前端调用以获取 AI 响应:
// app/api/chat/route.ts (Next.js)
import { Configuration, OpenAIApi } from 'openai-edge';
import { OpenAIStream, StreamingTextResponse } from 'ai';
export const runtime = 'edge';
const config = new Configuration({ apiKey: process.env.OPENAI_API_KEY });
const openai = new OpenAIApi(config);
export async function POST(req: Request) {
const { messages } = await req.json();
const response = await openai.createChatCompletion({
model: 'gpt-3.5-turbo',
stream: true,
messages: messages.map((m: any) => ({ role: m.role, content: m.content }))
});
const stream = OpenAIStream(response);
return new StreamingTextResponse(stream);
}
在这个处理程序中,我们会接收一个包含消息数组(聊天记录)的 JSON 请求体。我们调用 OpenAI 的聊天补全接口,并设置 stream: true 来获取流式响应。然后,我们将响应封装在 AI SDK 提供的 StreamingTextResponse 中,以分块形式将其传回客户端。Next.js API 路由会将 API 密钥保留在服务端,并高效地传输数据流。
Vite(React)实现: Vite 应用没有内置服务端,因此我们需要自行创建后端来调用 OpenAI。这可以是一个简单的 Node/Express 服务:
// backend/server.js (Node/Express for Vite app)
import express from 'express';
import { Configuration, OpenAIApi } from 'openai';
const app = express();
app.use(express.json());
const config = new Configuration({ apiKey: process.env.OPENAI_API_KEY });
const openai = new OpenAIApi(config);
app.post('/api/chat', async (req, res) => {
try {
const { messages = [] } = req.body;
const systemMsg = { role: 'system', content: 'You are a helpful assistant.' };
const inputMessages = [systemMsg, ...messages];
const response = await openai.createChatCompletion({
model: 'gpt-3.5-turbo',
stream: false,
messages: inputMessages
});
const content = response.data.choices[0].message?.content;
res.json({ content });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal Server Error' });
}
});
app.listen(6000, () => console.log('API server listening on http://localhost:6000'));
在开发期间,可以将 Vite 开发服务器配置为把 /api 请求代理到这个后端(例如,在 vite.config.js 中设置 server.proxy['/api'] = 'http://localhost:6000')。关键在于,React 应用会调用一个相对路径 /api/chat 端点,代理或托管服务会将请求转发到你的服务端代码。这样就能隐藏 OpenAI 密钥。
在 Node 中启用流式传输: 上面的 Express 示例会在生成完成后返回完整响应(为简洁起见,设置为 stream: false)。要在 Node 中进行流式传输,可以使用 OpenAI 的 HTTP 流:设置 stream: true,并将响应作为数据流处理。这需要读取 response.data 数据流,并使用 res.write() 将数据块刷新到客户端。如果选择只返回完整响应(不使用流式传输),这些 UI 模式仍然大多适用,但流式传输能显著改善用户体验。
提示词处理和对话状态
任何 AI 界面的核心都是提示词管理——将用户输入(及上下文)组合成提示词或消息序列,发送给 AI 模型。在聊天场景中,我们会维护一个消息列表,每条消息都包含角色和内容。OpenAI 的 Chat API 要求消息采用 { role: 'user' | 'assistant' | 'system', content: string } 格式。通常,我们会先发送一条系统消息(用于设定助手的行为或上下文),然后随着对话进行,交替添加用户和助手的消息。
React 中的状态管理: 我们可以将对话存储在组件状态中。使用 Vercel SDK 的 React hook:
import { useChat } from 'ai/react';
function ChatInterface() {
const { messages, input, handleInputChange, handleSubmit } = useChat();
// ...
}
useChat hook 会帮我们处理很多事情:它会管理 messages 状态(一个消息对象数组)、用于存储当前文本输入的 input 状态,并提供 handleInputChange 和 handleSubmit 辅助函数。默认情况下,调用 useChat() 后,提交时会向 /api/chat 发送 POST 请求。
手动处理状态: 如果你不使用 useChat,可以通过 useState 或 context 管理状态。提交表单时,调用你的 API,然后将用户查询和助手的回复追加到消息数组中。
系统提示词和上下文: 一种常见模式是添加一条初始系统消息,用于描述助手的角色或知识库。例如,如果要构建文档助手,系统消息内容可以是:“你是一名文档助手。请结合文档中的示例进行回答。”
单轮与多轮: 如果你的界面只需回答单个问题(不保留对话记忆),可以改用 Vercel SDK 的 useCompletion hook。对于聊天机器人和多轮对话,首选 useChat 模式,因为它会保留消息历史记录,并在每次请求时发送这些记录。
将 AI 响应流式传输到 UI
现代 AI 界面的一大特点是流式输出:AI 生成 token 时,用户可以实时看到回复逐渐呈现。这对于改善用户体验至关重要,因为模型生成的答案可能很长,也可能需要较长时间。流式传输可以立即显示部分结果,不必让用户默默等待数秒。
流式传输的工作原理: 在 OpenAI API 中启用 stream: true 后,响应会以一系列数据块(数据事件)的形式发送,而不是一个 JSON 大对象。Vercel AI SDK 简化了这些数据块的处理。在服务器端,我们将响应转换为文本流(StreamingTextResponse)。在客户端,useChat hook 会负责读取此流,并在收到新文本时逐步更新消息状态。
如果你在 React 中手动实现流式传输(不使用 SDK),可以采用类似下面的方式:
const res = await fetch('/api/chat', { method: 'POST', body: JSON.stringify({ messages }) });
const reader = res.body.getReader();
const decoder = new TextDecoder();
let partial = "";
while(true) {
const { value, done } = await reader.read();
if (done) break;
partial += decoder.decode(value);
setAssistantMessage(partial);
}
自动滚动: 流式传输时,一个需要注意的用户体验细节是确保最新消息始终可见。一种处理方式是使用 useEffect 监听消息数组的长度,并在更新时自动滚动消息容器。
部分渲染与完成状态: 流式传输期间显示视觉指示器,例如闪烁的光标或“AI 正在输入……”消息。流结束后,完成消息的显示。
输入处理与防抖
对于聊天交互,通常在用户提交表单时发送查询。不过,在某些 AI 应用中,你可能希望持续响应输入,例如提供自动补全建议或由 AI 实时验证。在这些场景中,防抖非常重要。
为什么要防抖? 每次按键都调用 OpenAI API,效率极低且成本高昂。防抖会延迟 API 调用,直到用户停止输入一小段时间后才执行。
const [draft, setDraft] = useState("");
useEffect(() => {
if (!draft) return;
const timeout = setTimeout(() => {
getSuggestion(draft);
}, 500);
return () => clearTimeout(timeout);
}, [draft]);
对于带有明确“发送”操作的简单聊天机器人,通常不需要防抖——用户按下 Enter 键时再发送即可。不过,当 AI 正在生成回复时,禁用输入或阻止重复提交仍然很有用。
错误处理与韧性
在 AI 应用中,健壮的错误处理至关重要:
-
围绕 API 调用使用 Try/Catch: 在服务端,用 try/catch 包裹 OpenAI 调用。如果发生错误,返回适当的错误响应。
-
客户端错误状态: 处理响应指示错误的情况。
try {
await sendMessage({ text: input });
} catch (error) {
console.error("Failed to send message:", error);
}
-
向用户反馈: 出现问题时,务必告知用户。在聊天中内联显示错误,例如显示一条特殊的“系统”消息:“抱歉,出了点问题。请重试。”
-
重试机制: 考虑允许用户点击“重试”按钮再次尝试。
-
验证错误: 在调用 API 之前,先在客户端进行验证。输入为空时禁用发送,或截断超出指定长度的输入。
构建 UI:组件与样式模式
聊天消息组件: 创建一个 ChatMessage 组件,用于渲染单条消息气泡。根据角色设置不同的样式:
function ChatMessage({ role, content }) {
const isUser = role === 'user';
return (
<div className={`flex ${isUser ? 'justify-end' : 'justify-start'} mb-2`}>
<div className={`max-w-xl px-4 py-2 rounded-lg ${
isUser ? 'bg-blue-500 text-white' : 'bg-gray-200 text-gray-900'
}`}>
{content}
</div>
</div>
);
}
输入组件:
function InputBox({ value, onChange, onSubmit, disabled }) {
return (
<form onSubmit={onSubmit} className="flex gap-2">
<input
type="text"
value={value}
onChange={onChange}
disabled={disabled}
className="flex-1 border rounded px-3 py-2"
placeholder="Type your message..."
/>
<button type="submit" disabled={disabled} className="bg-blue-500 text-white px-4 py-2 rounded">
Send
</button>
</form>
);
}
组合:
function ChatInterface() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
return (
<div className="flex flex-col h-screen max-w-2xl mx-auto p-4">
<div className="flex-1 overflow-y-auto">
{messages.map((msg, i) => (
<ChatMessage key={i} role={msg.role} content={msg.content} />
))}
</div>
<InputBox
value={input}
onChange={handleInputChange}
onSubmit={handleSubmit}
disabled={isLoading}
/>
</div>
);
}
这种关注点分离方式便于测试和替换 UI 部件。逻辑(useChat)与显示组件相互解耦。
Vercel AI Elements(预构建聊天 UI 组件)
Vercel 的 AI Elements 库提供了一组现成的 React 组件,专为 AI 聊天界面设计:
- Conversation:容器组件,用于渲染消息列表并自动滚动。
- Prompt:针对聊天提示词优化的输入组件。
- TypingIndicator:显示 AI 正在“思考”或以流式方式生成响应。
- ErrorBoundary/ErrorMessage:妥善处理并显示错误。
import { Conversation, Prompt, TypingIndicator } from '@vercel/ai-elements';
function ChatApp() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
return (
<div className="h-screen flex flex-col">
<Conversation messages={messages} />
{isLoading && <TypingIndicator />}
<Prompt
value={input}
onChange={handleInputChange}
onSubmit={handleSubmit}
/>
</div>
);
}
整合实现
-
后端 API 路由: 无论是使用 Next.js 路由处理程序还是独立的 Express 服务,都要创建一个端点,用于接收消息、调用 AI 模型并将响应以流式方式返回。
-
状态管理: 使用 Vercel AI SDK 的
useChat钩子(或自行使用useState实现),管理对话状态。 -
流式传输: 在服务端和客户端都启用流式传输,以获得响应迅速的用户体验。
-
防抖与速率限制: 对于自动补全等功能,对 API 调用进行防抖处理。对于聊天,在响应流式传输期间禁用输入。
-
错误处理: 使用 try/catch 包裹 API 调用,在发生错误时向用户反馈,并考虑加入重试机制。
-
可复用组件: 构建与数据获取逻辑解耦的展示组件(
ChatMessage、InputBox)。可考虑使用 AI Elements 中可用于生产环境的组件。 -
样式: 使用 TailwindCSS(或你偏好的样式方案)创建简洁、响应式的聊天界面。
架构对比:Next.js 与 Vite
| 方面 | Next.js | Vite + Node 后端 |
|---|---|---|
| API 路由 | 内置(pages/api/ 或 app/api/) | 需要单独的 Express/Node 服务 |
| 流式传输 | 原生支持 Edge Runtime | 需要使用 res.write() 手动实现 |
| 部署 | Vercel(已优化)或自托管 | 分别部署前端(静态)和后端 |
| 复杂度 | 较低(一体化) | 较高(两个代码库) |
| 灵活性 | 遵循框架约定 | 完全掌控 |
对于大多数 AI 聊天应用,Next.js 凭借集成的 API 路由和流式传输支持,可提供更简单的开发体验。不过,如果你已有 Vite/React 应用,或更倾向于拥有更多控制权,这里介绍的模式也适用于搭配独立后端使用。