⚠️ Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议
Skip to content

12.3.3 实时生成内容——流式文本生成:useChat/useCompletion 实战

一句话破题

useChat 用于多轮对话,useCompletion 用于单次生成——选对 Hook,事半功倍。

useChat vs useCompletion

特性useChatuseCompletion
用途多轮对话单次文本生成
消息历史自动维护不维护
典型场景聊天机器人文章生成、代码补全
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 渲染、带有停止生成按钮的聊天界面。"
  • 关键术语useChatuseCompletionmessagesisLoadingstop

避坑指南

  • 系统消息不要在 UI 中显示:过滤掉 role === 'system' 的消息。
  • 处理空消息:用户可能会发送空消息,需要在表单层面验证。
  • 注意消息 ID:每条消息需要唯一 ID,SDK 会自动生成,但手动添加消息时需注意。