Chat
Chat UI, streaming API, persistence, and mobile integration notes.
The web chat feature lives at /chat when NEXT_PUBLIC_ENABLE_CHAT_PAGE=true. It uses useChat from @ai-sdk/react with a custom transport that posts to POST /api/chat.
Web UI
| File | Role |
|---|---|
apps/web/src/app/(app)/(full-page)/chat/page.tsx | Page shell |
apps/web/src/components/ai/chat-workspace.tsx | Lists/creates chats via tRPC; loads messages |
apps/web/src/components/ai/ai-chat.tsx | useChat, provider/model/intelligence picker |
apps/web/src/components/ai/ai-chat-header.tsx | Header + provider Select |
apps/web/src/hooks/use-configured-ai-providers.ts | Providers available to the current user |
Chat workspace flow
chat.list— sidebar of existing conversations.chat.create— new chat row (title defaults from first message).chat.get— messages + stored settings (providerId,modelId,intelligence).- User sends a message →
useChatstreams from/api/chat. - On finish, assistant message is persisted;
chat.updateSettingssaves provider/model changes.
Streaming API
Route: apps/web/src/app/api/chat/route.ts
Max duration: 60 seconds (export const maxDuration = 60)
Request body
Auth and gates
- Session via Better Auth (
auth.api.getSession). - Email verification policy via
canAccessAuthenticatedApp. - Chat row ownership verified before streaming.
Server pipeline
- Load user preferences →
resolveUserAiProvider createLanguageModelwith user's API keycreateZtsAgentwith merged tool sets (see Agents)createAgentUIStreamResponsestreams UI messages to the clientonFinishpersists the assistant reply and updates chatupdatedAt
Provider API keys never leave the server; the client only sends providerId / modelId.
Client transport
AiChat configures DefaultChatTransport to merge chatId, providerId, modelId, and intelligence into every request body:
Settings changes call api.chat.updateSettings so the next session restores the user's picks.
Mobile
apps/mobile/src/features/chat/ChatPanel.tsx demonstrates useChat against ${getBaseUrl()}/api/chat with session cookies.
Important: The web API requires chatId and validates chat ownership. The mobile demo currently sends only providerId in the transport body. To use the same route on mobile:
- Create or select a chat via tRPC (
chat.create/chat.list) and passchatIdin the transport body, or - Add a dedicated mobile-friendly route that auto-creates chats.
Align mobile with the chatRequestSchema in route.ts before shipping chat to production on mobile.
Database
Chat and message rows are managed through tRPC routers under packages/trpc/src/routers/chat.ts (or equivalent). Run migrations after schema changes:
Troubleshooting
| Symptom | Check |
|---|---|
| No providers in dropdown | User enabled a provider + saved a key in Settings → AI |
401 on /api/chat | Session cookie / auth state |
| 400 missing chatId | Transport body must include chatId (web workspace does this) |
| Provider errors | Key validity, provider in ENABLED_AI_PROVIDERS, model in catalog |
Related
- Providers — where API keys come from
- Agents — what runs inside
/api/chat - Better Auth — session requirements