AI Chat

AI chat with the Vercel AI Gateway in the Nuxt template.

The template includes an AI chat Example with a streaming Vue interface and a Nitro endpoint backed by Vercel AI Gateway. The server currently uses the fixed model openai/gpt-5.2.

What it provides

  • A chat page at /ai/chat and a reusable ChatBot.vue component.
  • AI Elements for conversations, messages, prompt input, attachments, reasoning, sources, loading, copy, and regenerate actions.
  • Streaming UI messages through the AI SDK's Chat class from @ai-sdk/vue.
  • A lazy-initialized AI Gateway client and streamed Nitro response.
  • Authentication by default, an explicit public-demo switch, and Zod request validation.

Important files

FileRole
app/pages/(marketing)/ai/chat.vueChat page shell
app/components/ai/ChatBot.vueChat state, prompt controls, and message rendering
app/components/ai-elements/*Reusable chat interface primitives
server/api/chat.tsValidated Gateway request and stream response
server/utils/ai-auth.tsrequireAiAuth demo/auth policy
nuxt.config.tsaiGatewayApiKey and public.aiPublicDemo runtime config

Setup

Set the Gateway key as a server-only variable:

NUXT_AI_GATEWAY_API_KEY=your-ai-gateway-api-key

Restart pnpm dev after changing .env. The key must not use a NUXT_PUBLIC_ prefix.

Request and streaming flow

ChatBot.vue creates new Chat({}), sends UI messages to /api/chat, and renders each streamed message part. Sources, reasoning, text, submitted state, attachments, copy, and regenerate controls already have UI components.

server/api/chat.ts uses defineLazyEventHandler so the Gateway client is created once after first use. Each request then:

  1. calls requireAiAuth(event);
  2. parses a non-empty messages array with Zod;
  3. converts UI messages with convertToModelMessages;
  4. calls streamText with gateway("openai/gpt-5.2"); and
  5. returns toUIMessageStreamResponse({ sendSources: true, sendReasoning: true }).

The UI can display source and reasoning parts when the selected model/provider actually emits them.

Current control limitations

The model dropdown and Search toggle are currently UI starters. The client sends model and webSearch, but the server schema only reads messages and always calls openai/gpt-5.2; no search tool is configured. Selecting DeepSeek or toggling Search therefore does not change backend behavior.

For a single model, update the fixed Gateway model in server/api/chat.ts and remove misleading controls. For user-selectable models, add a Zod enum on the server and map it through a small allowlist. Do not pass an arbitrary client model identifier directly to a paid provider. Search requires an actual tool or provider option plus handling for its source parts.

Access control

requireAiAuth requires a session unless the following variable is exactly true:

NUXT_PUBLIC_AI_PUBLIC_DEMO=true

This switch is intentionally public because it changes client-visible demo behavior. Leave it unset or false in production. Authentication and request shape validation are integrated, but they do not control how much an authenticated account can spend.

Production hardening

Before exposing chat to real users, add:

  • per-user and per-plan usage quotas;
  • token/output limits and model-specific cost caps;
  • prompt/output moderation appropriate to the product;
  • a server-side model allowlist and removal/wiring of UI-only controls;
  • structured usage, latency, provider-error, and cost logging without storing sensitive prompts by default;
  • cancellation/timeouts and user-facing error states; and
  • rate limiting as a recommended add-on (it is not shipped by the template).

Verify

  • Missing NUXT_AI_GATEWAY_API_KEY fails clearly in server logs.
  • Logged-out requests return 401 when public demo is disabled.
  • A logged-in prompt streams text and reasoning/source UI does not crash.
  • Empty or malformed messages returns a 400-class validation response.
  • The deployed model and visible controls agree.
  • Quotas and cost alerts stop excessive paid usage before launch.

On this page