| type | App Guide | |||
|---|---|---|---|---|
| title | Chat Platform | |||
| description | The apps/chat island — app router, zustand, assistant-ui, route-handler auth guards, and the model registry. | |||
| timestamp | 2026-07-07 | |||
| tags |
|
Migration in flight (2026-07): the chat backend is slated to move to a Mastra-based
apps/chat-apiservice (PR #5126, draft; tutor architecture in PR #5129). This page describes the current AI-SDK reality and stays authoritative until those PRs merge — but check their status before investing heavily in the route-handler/AI-SDK layer. Staged doc/skill changes:project/plans_future/2026-07-07-wiki-skills-migration-roadmap.md.
This app is an island — do not apply the pages-router conventions here. It is the only Next.js app-router app (port 3004), talks to the backend's Prisma models directly through its own API route handlers (no GraphQL ops), uses zustand for client state (nowhere else in the repo), and renders chat via assistant-ui (@assistant-ui/react + react-ai-sdk) over the Vercel AI SDK (@ai-sdk/*). Domain models live in packages/prisma chat.prisma (chatbots, threads, messages, credits as Decimal(18,6)).
src/app/api/chatbots/[chatbotId]/…— route handlers (chat streaming, attachments, threads).src/lib/server/— server-only helpers:apiGuards.ts,chatModelRegistry.ts,openaiResponsesOptions.ts,imagePreview.ts.src/stores/— zustand:chatStore,composerStore,settingsStore.src/components/thread.tsxandsrc/hooks/— assistant-ui composition.- Local model proxy: the
litellmcompose service (port 4000).
Three steps: getParticipantId → getChatbotOr404 → requireParticipation. The composed helper withChatbotAuth(req, chatbotId) (src/lib/server/apiGuards.ts) covers the standard { courseId: true } case — use it for new routes; fall back to the individual guards only for a custom chatbot select. Participant identity comes from the same participant JWT cookies as the PWA (Auth Model); local chat dev therefore needs the backend's APP_SECRET and DATABASE_URL visible to the chat app, or cookies won't verify and Prisma can't load chatbots.
chatModelRegistry.ts loads CHAT_MODEL_REGISTRY_JSON (deployment override in deploy/env-uzh-*/values.yaml). Registry gotchas that have caused production incidents:
- Omitted
supportsImageAttachmentsdefaults to false — every image-capable model must set it explicitly in deployment values or the attach button disappears. - Zero-credit course chatbots need a usable fallback model (
CHAT_FALLBACK_MODEL_ID, defaultgpt-4.1-mini) AND explicit chatbotallowedModelIdsmust include it. Audit/fix withpackages/prisma-data/src/scripts/2026-06-15_ensure_chatbot_fallback_model.ts. - OpenAI Responses backends: keep
CHAT_OPENAI_STORE_RESPONSES=truein shared/staged deployments — withstore: false, LiteLLM/Azure can return "item not found" when a model references prior response items across tool-call steps. Local OpenRouter-style setups can leave it false.
Credit fields are Prisma Decimal — never truthy-check them (Data & Migrations).
- Zustand async actions must set fallback state in
catch, not just log — otherwise the UI hangs in loading state on network errors. - Edited-message image hydration needs the persisted source message id (
attachmentSourceMessageId) distinct from the fresh local message id (src/hooks/useThreadManagement.ts,src/stores/chatStore.ts). ComposerPrimitive.AttachmentDropzonemust wrap both normal and edit composer roots — it owns the drag/drop capture that prevents native browser file navigation (src/components/thread.tsx).- Login redirects:
src/app/noLogin/page.tsxmust pass an absolute chat URL as the PWA loginredirect_to; a relative path makes the PWA redirect to its own domain and 404.
Pure-logic vitest lives in apps/chat/test/ (safe without services); apps/chat/vitest.config.ts mirrors the @/* alias from the app tsconfig — keep them in sync. E2E coverage is Playwright-only (playwright/tests/Y-chat.spec.ts — no Cypress counterpart).