12.3.3 实时生成内容——流式文本生成:useChat/useCompletion 实战
一句话破题
useChat 用于多轮对话,useCompletion 用于单次生成——选对 Hook,事半功倍。
useChat vs useCompletion
| 特性 | useChat | useCompletion |
|---|---|---|
| 用途 | 多轮对话 | 单次文本生成 |
| 消息历史 | 自动维护 | 不维护 |
| 典型场景 | 聊天机器人 | 文章生成、代码补全 |
| API 端点 | /api/chat | /api/completion |
useChat 深度使用
tsx
'use client';
import { useChat, Message } from 'ai/react';
export default function ChatBot() {
const {
messages, // 消息历史
input, // 输入框内容
handleInputChange,
handleSubmit,
isLoading, // 是否正在生成
error, // 错误信息
reload, // 重新生成最后一条消息
stop, // 停止生成
setMessages, // 手动设置消息
append, // 追加消息
} = useChat({
api: '/api/chat',
initialMessages: [
{ id: '1', role: 'system', content: '你是一个友好的助手' },
],
onFinish: (message) => {
console.log('生成完成:', message);
},
onError: (error) => {
console.error('发生错误:', error);
},
});
return (
<div className="flex flex-col h-screen">
{/* 消息列表 */}
<div className="flex-1 overflow-auto p-4">
{messages.filter(m => m.role !== 'system').map((m) => (
<div
key={m.id}
className={`mb-4 ${m.role === 'user' ? 'text-right' : 'text-left'}`}
>
<span className={`inline-block p-3 rounded-lg ${
m.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-200'
}`}>
{m.content}
</span>
</div>
))}
{isLoading && (
<div className="text-gray-500">AI 正在思考...</div>
)}
</div>
{/* 输入区域 */}
<form onSubmit={handleSubmit} className="p-4 border-t">
<div className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="输入消息..."
className="flex-1 p-2 border rounded"
disabled={isLoading}
/>
{isLoading ? (
<button type="button" onClick={stop} className="px-4 py-2 bg-red-500 text-white rounded">
停止
</button>
) : (
<button type="submit" className="px-4 py-2 bg-blue-500 text-white rounded">
发送
</button>
)}
</div>
</form>
{error && (
<div className="p-4 bg-red-100 text-red-700">
出错了: {error.message}
<button onClick={() => reload()} className="ml-2 underline">
重试
</button>
</div>
)}
</div>
);
}useCompletion 实战
tsx
'use client';
import { useCompletion } from 'ai/react';
export default function ArticleGenerator() {
const {
completion, // 生成的文本
input,
handleInputChange,
handleSubmit,
isLoading,
error,
} = useCompletion({
api: '/api/completion',
});
return (
<div className="p-4">
<form onSubmit={handleSubmit}>
<textarea
value={input}
onChange={handleInputChange}
placeholder="输入文章主题..."
className="w-full p-2 border rounded"
rows={3}
/>
<button
type="submit"
disabled={isLoading}
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
>
{isLoading ? '生成中...' : '生成文章'}
</button>
</form>
{completion && (
<div className="mt-4 p-4 bg-gray-100 rounded whitespace-pre-wrap">
{completion}
</div>
)}
</div>
);
}对应的 API 路由:
typescript
// app/api/completion/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
export async function POST(req: Request) {
const { prompt } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
prompt: `请根据以下主题生成一篇文章:\n\n${prompt}`,
});
return result.toDataStreamResponse();
}高级:自定义消息渲染
tsx
import { useChat } from 'ai/react';
import ReactMarkdown from 'react-markdown';
export default function MarkdownChat() {
const { messages, ... } = useChat();
return (
<div>
{messages.map((m) => (
<div key={m.id}>
{m.role === 'assistant' ? (
<ReactMarkdown>{m.content}</ReactMarkdown>
) : (
<p>{m.content}</p>
)}
</div>
))}
</div>
);
}AI 协作指南
- 核心意图:让 AI 帮你实现特定的对话功能或生成功能。
- 需求定义公式:
"请帮我用 useChat 实现一个支持 Markdown 渲染、带有停止生成按钮的聊天界面。" - 关键术语:
useChat、useCompletion、messages、isLoading、stop
避坑指南
- 系统消息不要在 UI 中显示:过滤掉
role === 'system'的消息。 - 处理空消息:用户可能会发送空消息,需要在表单层面验证。
- 注意消息 ID:每条消息需要唯一 ID,SDK 会自动生成,但手动添加消息时需注意。
