Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "gateway",
"runtimeExecutable": "uv",
"runtimeArgs": [
"run",
"uvicorn",
"rag_gateway.serve:create_app",
"--factory",
"--port",
"8000"
],
"port": 8000
},
{
"name": "admin-ui",
"runtimeExecutable": "sh",
Expand Down
207 changes: 207 additions & 0 deletions apps/admin-ui/design/claude-design-prompt-operate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# Claude Design prompt — Admin UI **Operate pages** (Query Playground · Ingest · Chat)

> Focused brief for three High-priority surfaces. Paste the **PROMPT** block into Claude.ai
> (artifacts) to generate them as one navigable React mockup that matches the shipped console.
> Grounded 1:1 in the real gateway wire types (`rag_core.gateway_types`, `rag_core.openai_types`),
> so it maps straight onto the Next.js 14 implementation.
>
> Endpoints covered: `POST /v1/query` · `POST /v1/retrieve` · `POST /v1/feedback` ·
> `POST /v1/ingest/document` · `POST /v1/chat/completions` (SSE) · `GET /v1/models` ·
> `POST /v1/embeddings`.

---

## PROMPT

You are extending an existing, shipped **operator console** for **AgentContextOS**, a
multi-tenant RAG platform. Build the **three pages below** as **one self-contained React
artifact** with **Tailwind CSS**, **light + dark mode**, and realistic inline mock data — **no
backend calls**, simulate everything (including streaming) client-side. The pages must look like
they were always part of the app; reproduce the existing design system exactly.

### Design system (reproduce faithfully)

- **Aesthetic:** Vercel / Linear / Stripe operator dashboard — neutral slate/zinc, information-
dense, shadcn/ui look: `rounded-lg` cards, subtle 1px borders, soft shadows, `text-sm` default,
`tabular-nums` for numbers, **monospace for every id / hash / token / vector value**. Geist
Sans + Geist Mono, lucide icons, visible focus rings, AA contrast in both themes.
- **Shell:** render the app inside the existing chrome — a collapsible **left sidebar** with the
product name + "Admin" badge and grouped nav, and a **top bar** with a **tenant switcher**
(`acme`, `globex`, `initech`), a cosmetic search, a light/dark toggle, and a `svc-admin`
principal menu. The **active tenant scopes every request** (it becomes `tenant_id`). Add a new
sidebar group **"Operate"** holding **Query Playground**, **Chat**, and an **"Ingest"** item
under the existing **Knowledge** group. The nav must actually switch between the three pages.
- **Reusable primitives (same names/variants):** `Card`, `Button` (default/secondary/outline/
ghost/destructive; sm/md/icon), `Badge` (secondary/outline/success=emerald/error=red/
warning=amber/info=blue/violet), `StatusDot` (emerald=ok, amber=degraded, red=failed,
muted=unknown, optional ping), `Table`, `EmptyState`, `ErrorState`, `Skeleton`, `Dialog`,
`Sheet` (right drawer), `Dropdown`, `Tooltip`, `Toast`, `ChipInput`, `Slider`, `Switch`,
inline-SVG `Sparkline` + `Bar` (**no charting library**).
- **Every data view shows four designed states** — `ready`, `loading` (skeletons / streaming),
`empty` (designed empty state), `error` (retry) — with a small toggle to preview each, like the
rest of the app. Relative timestamps with absolute on hover.

Use believable, varied mock data (several chunks with different scores, a few long values to test
truncation, mixed statuses). Build these three pages:

---

### Page 1 — Query Playground *(group "Operate"; icon: search/play)*

Run a real RAG query and inspect every stage of the result. Two actions:
**Run query** → `POST /v1/query` (full pipeline), and **Retrieve only** → `POST /v1/retrieve`
(no rerank / pack / generate). A 👍/👎/copy/regenerate bar submits `POST /v1/feedback`.

**Left control rail** (the request — these are the exact `QueryRequest` fields):
- `query` — a textarea (the question).
- `corpus_ids` — a multiselect of corpora (`acme-handbook`, `runbooks`, `api-docs`, `policies`);
empty = search all.
- `top_k` — a slider 1–50 (default 10).
- Three `Switch`es: **Rerank** (`rerank`), **Pack context** (`pack`), **Generate answer**
(`generate`).
- An "Advanced" disclosure: `pack_budget` (tokens), `generate_max_tokens`, `generate_temperature`
(0–2 slider), and `filters` as a small key→value editor (e.g. `doc_type = runbook`).
- Buttons: **Run query** (primary) and **Retrieve only** (secondary). Show the resolved
`tenant_id` / `principal_id` (from the tenant switcher + `svc-admin`) as small muted text.

**Right results column** (the response — exact `QueryResponse` shape):
- **Decision strip:** badges for `decision.shape` (e.g. `factoid` / `exploratory` / `multi_hop`),
`decision.bm25_fallback` (amber when true), `corpus_decision.strategy` + selected-corpora count,
a **cache-hit** badge, and an **experiment** tag when `experiment` is present
(`{ experiment_id, variant }`).
- **Answer card** (only when Generate is on; `answer` is non-null): the `answer.text` rendered with
inline **citation chips** `[1] [2]` that scroll to / highlight the matching chunk **and** fire a
`citation_click` feedback signal. Footer: `answer.model`, token usage
(`answer.input_tokens` + `answer.output_tokens`), and a **guard** badge from `answer.guard`
(`{ verdict, claims_n, blocked_n }` → emerald `supported` / red `blocked` / amber when some
claims blocked). **Feedback action bar:** 👍 (`thumb_up`) · 👎 (`thumb_down`) · a 1–5 star
control (`rating`) · **Copy answer** (`answer_copied`) · **Regenerate** (`regenerated`) · a
collapsible **"Leave a comment"** box (`comment`, with a "comment is PII-redacted server-side"
hint). Each submits `POST /v1/feedback` and shows a toast from the `FeedbackAck`
(`{ feedback_id, signal, score, stored, comment_redacted }`) — e.g. "Feedback recorded · score
+1.0". (Signal enum exactly: `thumb_up`, `thumb_down`, `rating`, `comment`, `citation_click`,
`answer_copied`, `regenerated`, `accepted`, `dwell`.)
- **Citations list:** `citations[]` = `{ chunk_id, document_id, source_uri, excerpt, score,
position_in_context }`, ordered by position, each linking `[n] → source_uri`.
- **Ranked chunks table:** one row per `chunks[]` item = `{ id, content, score, document_id,
trust_level, metadata }` — columns rank #, **score** (badge), `source_uri`/`document_id`,
**trust_level** badge, and a truncated excerpt. Row click → right **`Sheet`** with full
`content` + a KV `metadata` table + copyable `chunk_id`. (On **Retrieve only**, the response is
`RetrieveResponse` — `chunks` are chunk-refs, no answer/citations; render just this table + the
decision strip + timings.)
- **Timings bar:** a horizontal stacked `Bar` of `timings` =
`{ understand_ms, route_ms, rerank_ms, pack_ms, generate_ms, total_ms }` with per-stage labels,
plus the total in mono.
- **"View full trace →"** deep-links to the existing **Query Trace** page for this
`response.request_id`.
- **States:** empty = "Run a query to see results"; loading = skeleton chunk rows, and when
Generate is on **simulate the answer streaming in** token-by-token (respect
`prefers-reduced-motion`); error = `ErrorState` with the gateway error shape
`{ error: { code, message, request_id, trace_id } }` and a Retry.

### Page 2 — Ingest *(group "Knowledge"; icon: upload)*

Push a document into a corpus via `POST /v1/ingest/document` (multipart). Form fields:
`tenant_id` (from switcher), `corpus_id`, `principal_id` (read-only `svc-admin`), `source_uri`
(optional), `file`.

- **Drop zone** (drag-and-drop + click-to-browse) listing accepted types: **PDF, DOCX, PPTX,
XLSX, MD, HTML, TXT, CSV, JSON, YAML** (the 10 supported parsers).
- **Form:** **Corpus** select (required), **Source URI** (optional), principal (read-only).
**Ingest** button (disabled until a file + corpus are chosen).
- **Upload queue** — one row per file: filename, size, a type icon, a `StatusDot` + badge driving
the **status** progression `pending → ingested / failed` (simulate a short delay), a progress
bar while uploading. On success show the `IngestResult` fields **`chunk_count`** and
**`embedding_count`** as badges and a **"View in Corpora →"** link; on failure show the error
message + a **Retry**. (`IngestResult = { status: "pending"|"ingested"|"failed", chunk_count,
embedding_count, document_id?, message? }`.)
- A muted **pipeline explainer** under the queue: *parse → chunk → enrich → PII-redact → embed →
index*, with the note "**PII is redacted before embedding**, so vectors never encode PII."
- **States:** empty = "Drop files to ingest"; loading = per-file progress; error = per-file failed
row with reason.

### Page 3 — Chat + Embeddings *(group "Operate"; icon: message-square)*

An OpenAI-compatible playground. Three **tabs**: **Chat** · **Embeddings** · **Models**.

**Chat tab** — `POST /v1/chat/completions` (with `stream=true` → SSE). Request shape
`ChatCompletionRequest = { model, messages: [{ role, content }], stream, max_tokens, temperature,
stop, user, rag: { enabled, query?, corpus_ids[], top_k, rerank, pack, pack_budget } }`.
- **Thread:** user/assistant bubbles; a composer with **Send**. Assistant replies **stream in
token-by-token** (simulate a typing effect from `ChatCompletionChunk.choices[0].delta.content`
frames, ending on the terminal chunk + `[DONE]`).
- **Right rail controls:** **model** select (from the Models tab / `/v1/models`), **temperature**
slider, **max_tokens**, a **Stream** switch, and a **RAG** panel — `rag.enabled` switch,
**corpus** multiselect (`rag.corpus_ids`), `rag.top_k`, `rag.rerank`, `rag.pack`. RAG is **on by
default** (the platform's value-add over a bare LLM proxy).
- **Under each assistant message** (from the terminal chunk's `rag` annotations +
`usage`): a `rag` summary — `shape`, `chunks_n`, **citation chips** (`citations[]`), a
`bm25_fallback` badge when set, a **guard** verdict badge, the **finish_reason**
(`stop` / `content_filter`), and token **usage** = `{ prompt_tokens, completion_tokens,
total_tokens }`. When `rag.enabled` is off, show a small "RAG off — model-only answer" note.
- **States:** empty = a friendly starter ("Ask a question grounded in your corpora"); loading =
the streaming indicator; error = a failed-stream chunk surfaced inline (`finish_reason: "error"`)
in OpenAI's `{ error: { message, type, code } }` shape.

**Embeddings tab** — `POST /v1/embeddings`. Request `EmbeddingsRequest = { input: string |
string[], model, encoding_format: "float" | "base64", user }`.
- A textarea (**one input per line** = batch), a **model** select, an **encoding_format** toggle
(`float` / `base64`), and an **Embed** button.
- Results: **one vector card per input** showing **dimension**, **L2 norm**, and the first ~12
values in a mono grid + a tiny `Sparkline` of the values; for `base64`, show the encoded string
(truncated, copyable). A footer with `usage = { prompt_tokens, total_tokens }`. Response shape
`EmbeddingsResponse = { object: "list", data: [{ object: "embedding", embedding, index }],
model, usage }`.
- States: empty / loading / error as above.

**Models tab** — `GET /v1/models`. Response `ModelList = { object: "list", data: [{ id, object:
"model", owned_by }] }`. A small table of the wired models: `id` (mono, copy button), an inferred
**type** badge (embedder / llm / alias), and `owned_by`. Seed with `rag-gateway` (the generic
alias), an embedder id (`text-embedding-3-small`, 1536-dim), and an LLM id (`gpt-4o-mini`). Note
these are the **actually-wired backends**, not a fixed catalog.

---

### Interaction + quality bar (same as the shipped app)

- Real **modals, drawers/sheets, dropdowns, toasts, tabs, badges, switches, sliders, tooltips,
streaming text** — shadcn/ui style, keyboard accessible, both themes.
- Consistent spacing + date formatting; **monospace for ids, chunk ids, request ids, trace ids,
vector values, base64**; `tabular-nums` for scores / tokens / latencies.
- Honor `prefers-reduced-motion` for the chat/answer typing effects.
- Believable mock data: a worked query ("What is our incident escalation policy?") returning ~6
scored chunks across 2 corpora, a streamed answer with `[1][2]` citations and a `supported`
guard verdict; an ingest queue with one `ingested` (chunk_count 42 / embedding_count 42) and one
`failed` row; a chat thread with one RAG-grounded reply; three embeddings cards.

**Deliverable:** one polished, navigable React artifact with **Query Playground**, **Ingest**, and
**Chat + Embeddings** (3 tabs), each with its ready / loading / empty / error states, visually
indistinguishable from the existing console.

---

## Notes for implementation (for me, not the design prompt)

Real wire types live in `rag_core.gateway_types` (`QueryRequest` / `QueryResponse` /
`RetrieveRequest` / `RetrieveResponse` / `StageTimings` / `Answer` / `FeedbackRequest` /
`FeedbackAck` / `GatewayError`) and `rag_core.openai_types` (`ChatCompletionRequest` /
`...Response` / `...Chunk` / `EmbeddingsRequest` / `EmbeddingsResponse` / `ModelList` /
`RagOptions` / `RagAnnotations`). Don't hand-type — import from `dist/openapi.json` /
`@agentcontextos/sdk`.

Identity for these routes:
- `/v1/query`, `/v1/retrieve`, `/v1/feedback` carry identity **in the body** (`tenant_id` +
`principal_id`) — wire the active tenant + a fixed `svc-admin` principal.
- `/v1/chat/completions`, `/v1/embeddings`, `/v1/models`, `/v1/ingest/document` are **header-
driven** (`X-Tenant-Id` + `X-Principal-Id`, or `Authorization: Bearer`) — same as the existing
Corpora page.

Wiring steps per page (mirror the existing live→seed pattern): add the route to
`src/components/shell/nav.ts`, an `api.ts` function (`runQuery` / `retrieveOnly` / `submitFeedback`
/ `ingestDocument` / `chatCompletions` (SSE reader) / `createEmbeddings` / `fetchModels`), a seed
fallback in `src/lib/mock.ts`, and types in `src/lib/types.ts`. Streaming: read
`text/event-stream` and parse `data:` frames until `data: [DONE]`. Suggested PR slice: `7.9b`.

Note: the OpenAI chat route already returns a non-standard **`rag`** annotation block
(`enabled` / `shape` / `bm25_fallback` / `chunks_n` / `citations` / `guard`) — surface it; it's
the platform's differentiator over a bare LLM proxy.
Loading
Loading