Web client

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_score or 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_ingested is true, 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 sessionStorage and restored on reload.
  • Keyboard — Enter sends, Shift+Enter newline, Escape stops 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, the Retry-After header 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_id shows 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-Id to 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" render dir="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-visible with 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.

Terminal
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

Terminal
bun run build      # tsc -b && vite build → web/dist/
bun run typecheck

Serve 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:5173 with 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