mcpField
A
ai-ui-patterns

ai-ui-patterns

github.com/patternsdev开发2572.0k 次安装28 周前更新
代码仓库

关于 ai-ui-patterns

ai-ui-patterns 是面向 React 开发者的 AI 界面设计指南,涵盖 Vite 与 Next.js 实现。内容聚焦对话状态、流式响应和错误处理等模式,并介绍 Vercel AI SDK 与可复用组件。

主要功能

  1. 01管理提示词与对话状态
  2. 02流式呈现 AI 响应
  3. 03输入防抖处理
  4. 04错误反馈与重试
  5. 05构建可复用界面组件
  6. 06比较 Vite 与 Next.js 架构

适用场景

  1. 01为 React 应用构建聊天界面
  2. 02实现实时 AI 自动补全
  3. 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 的 useChat hook 管理对话状态和流式响应
  • 将 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>
  );
}
整合实现
  1. 后端 API 路由: 无论是使用 Next.js 路由处理程序还是独立的 Express 服务,都要创建一个端点,用于接收消息、调用 AI 模型并将响应以流式方式返回。

  2. 状态管理: 使用 Vercel AI SDK 的 useChat 钩子(或自行使用 useState 实现),管理对话状态。

  3. 流式传输: 在服务端和客户端都启用流式传输,以获得响应迅速的用户体验。

  4. 防抖与速率限制: 对于自动补全等功能,对 API 调用进行防抖处理。对于聊天,在响应流式传输期间禁用输入。

  5. 错误处理: 使用 try/catch 包裹 API 调用,在发生错误时向用户反馈,并考虑加入重试机制。

  6. 可复用组件: 构建与数据获取逻辑解耦的展示组件(ChatMessage、InputBox)。可考虑使用 AI Elements 中可用于生产环境的组件。

  7. 样式: 使用 TailwindCSS(或你偏好的样式方案)创建简洁、响应式的聊天界面。

架构对比:Next.js 与 Vite
方面Next.jsVite + Node 后端
API 路由内置(pages/api/ 或 app/api/)需要单独的 Express/Node 服务
流式传输原生支持 Edge Runtime需要使用 res.write() 手动实现
部署Vercel(已优化)或自托管分别部署前端(静态)和后端
复杂度较低(一体化)较高(两个代码库)
灵活性遵循框架约定完全掌控

对于大多数 AI 聊天应用,Next.js 凭借集成的 API 路由和流式传输支持,可提供更简单的开发体验。不过,如果你已有 Vite/React 应用,或更倾向于拥有更多控制权,这里介绍的模式也适用于搭配独立后端使用。

来源

参考资料

评论

支持 Markdown