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

FileRole
src/components/ai/ChatBot.tsxClient UI, useChat, transport, model picker
src/components/ai-elements/Conversation, message, prompt, reasoning, and source UI
src/app/api/chat/route.tsPublic streaming POST endpoint
src/app/[locale]/(marketing)/ai/chat/page.tsxPage at /ai/chat

Setup

Add the server-only OpenAI key to .env:

OPENAI_API_KEY=your-openai-api-key

Restart 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

  1. ChatBot creates a DefaultChatTransport for /api/chat and includes the selected model and webSearch values in the request body.
  2. useChat manages messages and the submitted/streaming states.
  3. The route converts UIMessage[] with convertToModelMessages() and calls streamText({ model: openai(modelId), messages }).
  4. toUIMessageStreamResponse({ sendSources: true, sendReasoning: true }) streams UI message parts back to the browser.
  5. 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:

LabelModel ID
GPT 5.2gpt-5.2
Deepseek R1deepseek-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.

On this page