AI Chat
Stream OpenAI responses through the Vercel AI SDK and AI Elements.
The template includes an Example chat surface at /ai/chat. It combines AI
Elements, the AI SDK's useChat hook, and a streaming /api/chat route.
The route is public and currently has no authentication, rate limit, quota, moderation, or server-side model allowlist. Treat it as a development example, not a production chat endpoint.
What it provides
- Streaming UI messages with text, reasoning, and source-part renderers.
- File attachment selection, stop, retry, copy, and model controls.
- An OpenAI-backed Route Handler using
streamText(). - Reusable AI Elements under
src/components/ai-elements/.
Important files
| File | Role |
|---|---|
src/components/ai/ChatBot.tsx | Client UI, useChat, transport, model picker |
src/components/ai-elements/ | Conversation, message, prompt, reasoning, and source UI |
src/app/api/chat/route.ts | Public streaming POST endpoint |
src/app/[locale]/(marketing)/ai/chat/page.tsx | Page at /ai/chat |
Setup
Add the server-only OpenAI key to .env:
OPENAI_API_KEY=your-openai-api-keyRestart pnpm dev, open /ai/chat, send a message, and confirm that the answer
streams into the conversation. The key is read by @ai-sdk/openai; never expose
it through a NEXT_PUBLIC_* variable.
Request and streaming flow
ChatBotcreates aDefaultChatTransportfor/api/chatand includes the selectedmodelandwebSearchvalues in the request body.useChatmanages messages and thesubmitted/streamingstates.- The route converts
UIMessage[]withconvertToModelMessages()and callsstreamText({ model: openai(modelId), messages }). toUIMessageStreamResponse({ sendSources: true, sendReasoning: true })streams UI message parts back to the browser.- AI Elements render text, reasoning, sources, attachments, and controls.
Attachments are sent by the client as message file parts. Whether a selected model can actually consume them depends on that model and provider behavior.
Models and response options
ChatBot.tsx currently offers these UI choices:
| Label | Model ID |
|---|---|
| GPT 5.2 | gpt-5.2 |
| Deepseek R1 | deepseek-r1 |
The first UI choice, gpt-5.2, is selected by default. Separately, the route
falls back to gpt-4o only when the request omits model.
The picker is not a security boundary. /api/chat currently passes any
client-supplied model string to openai(...). Also verify that every UI model
ID is available through your configured OpenAI provider account; a label in
the picker does not guarantee provider support.
To change the choices, edit models in ChatBot.tsx. For production, define the
same allowlist on the server and reject unknown IDs before calling streamText:
const allowedModels = new Set(["gpt-5.2"]);
const modelId = allowedModels.has(requestedModel)
? requestedModel
: "gpt-5.2";The response enables sendReasoning and sendSources, and the UI has renderers
for both part types. Parts only appear when the selected model/provider emits
them.
The Search toggle
The current webSearch option does not configure web search. When enabled,
the route adds tools: {}—an empty tool set. No search provider, OpenAI search
tool, or custom tool is registered, so the model cannot browse from this code.
To make it real, configure an AI SDK-compatible search tool in the server route, validate when it may run, and render its tool/source parts. Do not rely on the client boolean for authorization or billing decisions.
Production hardening
Before exposing the route to users:
- Require a server-verified session and enforce tenant/user access.
- Validate message count, roles, text length, attachments, and request size.
- Enforce a server-side model/tool allowlist.
- Add per-user and per-IP rate limits, usage quotas, and concurrency limits.
- Add moderation and a policy for uploaded content.
- Cap tokens, tool calls, duration, and expensive model access.
- Record usage, latency, failures, and cost without logging sensitive prompts by default.
- Return safe structured errors and handle aborted/provider-failed streams.
