Streaming AI ответов в React и Next.js — это техника потоковой генерации текста, при которой пользователь видит ответ AI по мере его создания, слово за словом. Это значительно улучшает UX и снижает воспринимаемое время ожидания. В этой статье мы реализуем полноценный AI-чат с потоковой передачей.
Серверная часть: Next.js Route Handler
В Next.js App Router создайте Route Handler, который проксирует запросы к ModelSwitch API и возвращает SSE-поток:
// app/api/chat/route.ts
import OpenAI from "openai";
const client = new OpenAI({
baseURL: process.env.MODELSWITCH_BASE_URL || "https://api.modelswitch.ru/v1",
apiKey: process.env.MODELSWITCH_API_KEY!,
});
export async function POST(req: Request) {
const { messages, model = "gpt-4o" } = await req.json();
const stream = await client.chat.completions.create({
model,
messages,
stream: true,
});
const encoder = new TextEncoder();
const readable = new ReadableStream({
async start(controller) {
for await (const chunk of stream) {
const text = chunk.choices[0]?.delta?.content || "";
if (text) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ text })}
`));
}
}
controller.enqueue(encoder.encode("data: [DONE]
"));
controller.close();
},
});
return new Response(readable, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}
API-ключ хранится только на сервере в .env.local и никогда не попадает в клиентский бандл.
Клиентская часть: React-хук useChat
Создайте кастомный хук для управления чатом и обработки SSE-потока:
// hooks/useChat.ts
"use client";
import { useState, useCallback } from "react";
interface Message {
role: "user" | "assistant";
content: string;
}
export function useChat() {
const [messages, setMessages] = useState<Message[]>([]);
const [isLoading, setIsLoading] = useState(false);
const sendMessage = useCallback(async (content: string) => {
const userMessage: Message = { role: "user", content };
const updated = [...messages, userMessage];
setMessages(updated);
setIsLoading(true);
try {
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages: updated, model: "gpt-4o" }),
});
const reader = res.body?.getReader();
const decoder = new TextDecoder();
let assistantText = "";
setMessages((prev) => [...prev, { role: "assistant", content: "" }]);
while (reader) {
const { done, value } = await reader.read();
if (done) break;
const lines = decoder.decode(value).split("\n");
for (const line of lines) {
if (line.startsWith("data: ") && line !== "data: [DONE]") {
const { text } = JSON.parse(line.slice(6));
assistantText += text;
setMessages((prev) => {
const copy = [...prev];
copy[copy.length - 1] = { role: "assistant", content: assistantText };
return copy;
});
}
}
}
} finally {
setIsLoading(false);
}
}, [messages]);
return { messages, sendMessage, isLoading };
}
Компонент чата
Используйте хук в React-компоненте для отображения чата:
// components/Chat.tsx
"use client";
import { useState } from "react";
import { useChat } from "@/hooks/useChat";
export function Chat() {
const { messages, sendMessage, isLoading } = useChat();
const [input, setInput] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || isLoading) return;
sendMessage(input);
setInput("");
};
return (
<div className="flex flex-col h-[600px]">
<div className="flex-1 overflow-y-auto space-y-4 p-4">
{messages.map((msg, i) => (
<div key={i} className={msg.role === "user" ? "text-right" : "text-left"}>
<p className="inline-block px-4 py-2 rounded-lg bg-gray-100">
{msg.content}
</p>
</div>
))}
</div>
<form onSubmit={handleSubmit} className="flex gap-2 p-4">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Введите сообщение..."
className="flex-1 border rounded px-3 py-2"
/>
<button type="submit" disabled={isLoading}>Отправить</button>
</form>
</div>
);
}
Переключение моделей на лету
Одно из главных преимуществ ModelSwitch — возможность менять модель одной строкой. Добавьте селектор модели в компонент чата, передавая значение в sendMessage. На сервере меняется только параметр model в запросе. Поддерживаются все 300+ моделей: GPT-4o, Claude, Gemini, Llama, Mistral и другие.
Streaming работает одинаково для всех моделей через единый API ModelSwitch, поэтому клиентский код остаётся без изменений.
Заключение
Next.js App Router и React идеально подходят для AI-приложений с потоковой генерацией. Route Handler на сервере защищает API-ключ, а кастомный хук useChat обеспечивает плавный UX. ModelSwitch делает этот процесс ещё проще: один API-ключ, один формат и streaming для всех моделей.