The operator/admin console (apps/admin-ui) — a Next.js 14 web GUI for managing
the platform. This page is the public reference (pages, configuration, running
it) and the technical reference (architecture, live-vs-seed, extension points).
A dense, information-first dashboard. Sixteen pages behind one app shell (collapsible sidebar + top bar with tenant switcher, theme toggle, and a review state control):
| Page | Route | Data source |
|---|---|---|
| Dashboard | / |
seed (stats, recent activity, connector health) + live system-status banner |
| Query Playground | /playground |
live POST /v1/query / /v1/retrieve / /v1/feedback → seed simulation (Step 7.9b) |
| Chat | /chat |
live POST /v1/chat/completions (SSE) / /v1/embeddings / GET /v1/models → seed simulation (Step 7.9b) |
| Live Status | /status |
live WS /v1/status/ws (health) + breakers / quotas / drift / feedback / cost / experiments cards (REST) → seed fallback |
| Metrics | /metrics |
live WS /v1/status/ws → seed fallback (Step 3.11) |
| Logs | /logs |
live SSE /v1/status/logs/stream → seed fallback (Step 3.11) |
| Query Trace | /trace |
live GET /v1/query/{id}/trace → seed fallback (Step 5.1/5.6b) |
| Corpora | /corpora |
live GET /v1/corpora (list) + POST (create) + DELETE → seed fallback (writes are local-only in demo mode, Step 3.10) |
| Connectors | /connectors |
seed |
| Ingest | /ingest |
live multipart POST /v1/ingest/document → seed simulation (Step 7.9b) |
| Glossary | /glossary |
seed |
| Webhooks | /webhooks |
live /v1/webhooks/subscriptions + /test → seed fallback |
| Audit Log | /audit |
seed |
| API Keys | /api-keys |
seed · Preview |
| Tenants | /tenants |
seed · Preview |
| Config Viewer | /config |
seed |
The Observability nav group (Live Status / Metrics / Logs / Query Trace) is the Step 3.11 addition; the Dashboard gained a one-line system-status banner that drills into it (≤3-click drilldown to any component, metric, or log record).
Below the health roll-up, the Live Status page stacks the operational cards
that surface each reliability/quality subsystem — Circuit breakers (Step 4.4,
one-click force-close), Quotas & rate limits (Step 4.5, one-click reset),
Drift monitors (Step 5.5/5.6a, per-metric verdict + one-click Rebaseline to
"accept the new normal"), Feedback & satisfaction (Step 5.4/5.6a, the
per-tenant mean satisfaction, sentiment split, and headline signal counts),
Cost anomaly (Step 5.6c, the per-tenant recent-spend verdict — recent vs
rolling baseline, ratio + z-score), and A/B experiments (Step 5.7d, one row
per running experiment — the lift, a verdict badge, control→candidate means, the
confidence interval, and per-arm sample counts, from GET /v1/status/experiments).
Each card is live-wired with a seed fallback so it always renders.
The Observability surfaces cross-link (Step 5.6f): Live Status has a Trace
quick-link, the Query Trace viewer's Logs button jumps to /logs?trace=<id>
to scope the tail to that query's lines, and a log record's detail drawer offers
View this trace's logs (a clearable trace-filter chip) — so you can pivot from a
query → its provenance/spans → its logs in a couple of clicks.
The Operate nav group (Step 7.9b) drives the request path itself:
- Query Playground (
/playground) — runPOST /v1/query(full pipeline) orPOST /v1/retrieve(retrieval-only) with everyQueryRequestknob exposed (corpus scope,top_k, rerank / pack / generate switches, an Advanced disclosure forpack_budget/generate_max_tokens/generate_temperature/filters). The result view shows the routing decision strip (query shape, bm25 fallback, corpus-routing strategy, packed-token usage, A/B experiment tag), the generated answer with clickable[n]citation chips and the hallucination-guard verdict, the citations list, the ranked chunks table (score + trust level; row → detail sheet), and a stacked per-stage timings bar (skipped stages marked). A feedback action bar (👍 / 👎 / 1–5 stars / copy / regenerate / comment) submitsPOST /v1/feedbackwith the exact signal enum, and View full trace deep-links to/trace?rid=<request_id>(the Query Trace page now accepts aridquery param). - Ingest (
/ingest, under Knowledge) — drag-and-drop multipartPOST /v1/ingest/documentinto a chosen corpus: a staged upload queue with per-file progress, theIngestResultchunk/embedding counts on success (and a View in Corpora link), the error + a Retry on failure, and the pipeline explainer (parse → chunk → enrich → PII-redact → embed → index — PII is redacted before embedding). - Chat (
/chat) — an OpenAI-compatible playground in three tabs: Chat (streamingPOST /v1/chat/completionsthread; the right rail exposes model / temperature / max-tokens / stream plus the non-standardragoptions — RAG on by default; each reply is annotated with the terminal chunk'sragblock: shape, chunk count, citations, guard verdict,finish_reason, token usage), Embeddings (POST /v1/embeddings, one input per line, float or base64, per-vector dimension + L2 norm + sparkline), and Models (GET /v1/models— the actually-wired backends, not a catalog).
Stack: Next.js 14 (App Router), TypeScript, Tailwind CSS, hand-rolled shadcn/ui-style primitives, Geist fonts, lucide-react.
Corpora, Webhooks, the Observability pages (Live Status / Metrics / Logs / Query Trace), and the Operate pages (Query Playground / Ingest / Chat) have gateway endpoints today, so the console is a hybrid:
- Live-wired pages fetch from the gateway when
NEXT_PUBLIC_GATEWAY_URLis set + reachable, and fall back to local seed data otherwise (shown with a Demo data badge, which becomes a green Live indicator when streaming). So the console always renders, and lights up real data when pointed at a gateway. Corpora/Webhooks fetch over REST; the Observability pages stream over WebSocket (useStatusStream) and SSE (useLogStream), which no-op to seed when no gateway is configured. The Live Status operational cards (breakers, quotas, drift, feedback, cost) fetch over REST viauseLive, which treats an empty/disabled response as "no live data yet" and keeps the seed (with the demo badge). The Operate pages are action surfaces, so with no gateway they simulate the request instead of falling back to a list — a deterministic worked query (whoserequest_idresolves on the Query Trace page), a simulated upload queue, and a client-side token-streamed chat reply (typing effects honourprefers-reduced-motion). - Seed-only pages use typed seed data. API Keys and Tenants carry a Preview marker — their backends land with Phase 6 governance.
Identity: most routes are header-driven (dev mode) — the active tenant →
X-Tenant-Id + a fixed admin principal. POST /v1/query / /v1/retrieve /
/v1/feedback carry identity in the body (tenant_id + principal_id),
exactly like the wire types; the playground shows the resolved pair under its run
buttons.
| Env var | Default | Effect |
|---|---|---|
NEXT_PUBLIC_GATEWAY_URL |
unset | When set, Corpora + Webhooks + Live Status / Metrics / Logs / Query Trace + Query Playground / Ingest / Chat read this gateway (REST + WS + SSE); unset → seed data / simulation. The gateway's RAG_GATEWAY_CORS_ORIGINS must allow the console origin (defaults to http://localhost:3100). |
pnpm install
pnpm --filter @ragplatform/admin-ui dev # http://localhost:3100
# against a running gateway:
NEXT_PUBLIC_GATEWAY_URL=http://localhost:8000 pnpm --filter @ragplatform/admin-ui devpnpm --filter @ragplatform/admin-ui typecheck
pnpm --filter @ragplatform/admin-ui lint
pnpm --filter @ragplatform/admin-ui test
pnpm --filter @ragplatform/admin-ui build- Shell (
components/shell/):sidebar,topbar,nav,app-shell. The top bar's tenant switcher re-scopes every page; the theme toggle flipsnext-themes; a "State" control previews loading/empty/error renders. - Primitives (
components/ui/):primitives.tsx(Button/Badge/Card/Table/ Input/Switch/ChipInput/Slider/Tabs/…),overlays.tsx(Dialog/Sheet/Dropdown/ Tooltip),toaster.tsx. Hand-rolled, Radix-free, styled to the handoff tokens. - Shared (
components/shared.tsx):PageHeader,DataPanel(state switch),KVTable/JsonBlock/YamlBlock, connector icons, and the Operate helpers (CorpusMultiSelect,GuardBadge,CitedText—[n]markers → citation chips). - Data layer (
lib/):types.ts,config.ts,mock.ts(seed),api.ts(gateway client),time.ts,use-live.ts(the hybrid hook). - Providers (
components/providers.tsx): theme + app context (active tenant, sidebar collapse, review state) + toast queue.
Design tokens (shadcn CSS variables, light/dark), animations, and YAML highlight
colors live in app/globals.css, mirroring apps/admin-ui/design/handoff.
- Wire a seed-only page to a real backend — add a gateway endpoint, add a
fetcher to
lib/api.ts, swap the page'suseLive(mockSelector, fetcher)fetcher in, and drop the Preview marker. - Add a page — create
app/<route>/page.tsx, add it tocomponents/shell/ nav.ts, reuse the primitives +DataPanel. - Add a primitive — extend
components/ui/primitives.tsx(oroverlays.tsx) using the same token classes.
- architecture/admin-ui.md — design + verification.
- guides/admin-ui-quickstart.md — 5-minute tour.
- ADR-0019 — the decision.
apps/admin-ui/design/handoff/README.md— the source design spec + screenshots.