Web client
A reference single-page app in web/ demonstrates the
v1 API end-to-end (Vite + React + TypeScript). It ships production-grade UX for trust,
accessibility, and conversation management — not just a demo.
Audience: developers who want a working front-end reference with real UX patterns. What you will accomplish: run the SPA, understand every feature, and adapt the patterns into your own UI.
What it shows
Streaming chat
POST /api/v1/chat/stream is parsed as Server-Sent Events (token → sources → done).
Stop aborts the request mid-stream via an AbortController. A typing indicator
(animated dots) appears before the first token, and the last assistant bubble carries a
Regenerate button that re-sends the preceding user question.
Markdown rendering with code highlighting
Assistant answers render through react-markdown + remark-gfm (tables, strikethrough,
autolinks). Fenced code blocks are buffered until the closing fence arrives — no
half-rendered ``` blobs flicker on screen. Once complete, code blocks are
syntax-highlighted by Shiki (lazy-loaded, one chunk per language), each with a Copy
button. Falls back to plain <pre> if Shiki hasn’t loaded yet.
Trust signals
Every assistant message carries visual trust indicators:
- Confidence badge — derived from
meta.grounded_scoreor the best source score, bucketed into High (≥ 0.7, green), Medium (0.4–0.7, amber), or Low (< 0.4, red). Hover for the raw score. - Mode chip — shows
meta.mode(strict / open / learning / learning_review) so the user always knows which rule set produced the answer. - Provenance chip — when
meta.self_ingestedistrue, the badge warns “AI-synthesized — not from official docs” in a distinct color so it never looks authoritative. - Structured citations — collapsible Sources cards show
label,page, a score meter (0–1 bar), and an expandable snippet. Each citation has a Copy button that copies the full citation text. Arrow keys navigate focus through citation actions.
Strict-mode refusal UX
When mode=strict and the backend finds no supported answer, the bot refuses. Instead of a
blank bubble, the UI renders a refusal card — “Not in the knowledge base” — with a
primary CTA “Answer from general knowledge” that re-sends the same question in open
mode without the user retyping.
Smart autoscroll
The message list auto-scrolls to the bottom only while the user is pinned to the bottom (within a 60 px threshold). If the user scrolls up to read history, auto-scroll stops and a floating ”↓ New messages” button appears. Clicking it jumps to the bottom and resumes pinning. No scroll hijacking.
Suggested prompt chips
On the empty state and after every assistant turn, the UI shows 3–4 context-aware prompt chips:
- “Answer from general knowledge” (switches
mode=open) - “Explain in Arabic” (sends
lang=ar) - “Summarize this”
- A sample policy question
Clicking a chip sends immediately — useful for new users who don’t know what to ask.
Input & controls
- Character counter — live count with color change near the 2000-character server limit; send is disabled past the limit.
- Persisted controls — mode and language are stored in
sessionStorageand restored on reload. - Keyboard —
Entersends,Shift+Enternewline,Escapestops streaming while busy.
Error handling
- Friendly errors — problem+json responses (
{title, detail, status}) are mapped to human-friendly inline messages (e.g., “Too many messages. Please wait a moment” for 429). - Rate-limit countdown — on
429, theRetry-Afterheader is parsed and a live countdown bar appears above the composer. Send is disabled until the timer reaches zero. - Correlation ID — every error with a
correlation_idshows a “Report issue” button that copies the ID to the clipboard for support tickets.
Health & connectivity
The health badge polls /health every 15 seconds:
- Green dot — all dependencies OK.
- Amber dot — degraded (e.g., one dependency down). Hover for the failing service name.
- Red dot — backend unreachable or critically down.
Clicking the badge triggers an on-demand /ready probe and shows which dependency is
failing (Redis, ChromaDB, etc.) in a tooltip detail panel.
Conversation management
- New conversation — the + New button in the header rotates
X-User-Idto a fresh value and clears the message history. The old conversation remains on the backend for its TTL. - Export transcript — a ↓ Export dropdown (visible when messages exist) downloads the full conversation as JSON (structured, with metadata) or Text (human-readable with role labels).
- TTL note — the footer shows “Conversations kept ~24 hours” so users know memory is temporary.
Inline feedback
Every completed assistant message has 👍 / 👎 buttons. Clicking sends POST /api/v1/feedback with the question, answer, correlation_id, and rating. Downvote reveals
an optional “What was wrong?” text input (max 200 chars). After submission, the buttons
are disabled and a confirmation appears.
Accessibility
- RTL / Arabic — messages carrying
lang="ar"renderdir="rtl"via logical CSS properties; screen readers pronounce Arabic correctly. - Deferred screen-reader announcements — a dedicated off-screen
aria-live="polite"region announces each completed assistant message once (first 150 chars) instead of per-token chatter. - Visible focus rings —
focus-visiblewith 2 px accent outline and offset. - Reduced motion — under
prefers-reduced-motion: reduce, the typing indicator becomes static dots, and animations are suppressed.
Run it
The dev server proxies /api and /health to 127.0.0.1:8000, so start the backend
separately first.
cd web
bun install # or: npm install
bun run dev # http://localhost:5173 (proxies /api and /health to :8000)If it fails: If requests 502/404, the backend isn't running on :8000 — start it with uvicorn main:app --port 8000.
Build it
bun run build # tsc -b && vite build → web/dist/
bun run typecheckServe dist/ from any static host; point the API at it via CORS (CORS_ORIGINS) or
reverse-proxy /api to the backend.
Verify your result
- Verify: The SPA loads at
http://localhost:5173with a green health dot. - Verify: Streaming answers render token-by-token with collapsible citations.
- Verify: Uploading a supported file queues an ingest; the Review panel works in
learning_review. - Verify: Strict refusals show a refusal card with a one-click "Answer from general knowledge" CTA.
- Verify: Every completed assistant message has 👍/👎 feedback buttons.
- Verify: Autoscroll only happens when pinned to bottom; scrolling up shows "New messages".
Common failure modes
Related next steps
- Understand streaming events in Streaming.
- Operate the review queue in Review workflow.
- Configure CORS for production in Security.
- Learn how citations and grounding work in Trust & citations.
- Wire feedback into your own UI in Rating answers.