diff --git a/.claude/launch.json b/.claude/launch.json index 3b09996..96c688b 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -21,7 +21,18 @@ "-c", "cd apps/admin-ui && exec node node_modules/next/dist/bin/next dev -p 3100" ], - "port": 3100 + "port": 3100, + "autoPort": false + }, + { + "name": "admin-ui-preview", + "runtimeExecutable": "sh", + "runtimeArgs": [ + "-c", + "cd apps/admin-ui && export NEXT_PUBLIC_GATEWAY_URL='' && exec node node_modules/next/dist/bin/next dev -p 3101" + ], + "port": 3101, + "autoPort": false }, { "name": "docs-site", @@ -31,6 +42,16 @@ "cd website && exec npm run serve -- --port 3200 --no-open" ], "port": 3200 + }, + { + "name": "rube-local", + "runtimeExecutable": "sh", + "runtimeArgs": [ + "-c", + "cd /Users/deepkumarsinghkushwah/Code/GitHub/rube && exec npm run preview:local" + ], + "port": 7654, + "autoPort": false } ] } diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..356bfa6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,22 @@ +# Both images (Dockerfile, apps/admin-ui/Dockerfile) build from the repo root, +# so host-only / platform-specific dirs must not leak into the build context — +# most importantly node_modules, which would clobber the Linux deps the +# admin-ui builder installs (macOS-native binaries break `next build`). +.git +**/.DS_Store + +# Node — the admin-ui builder installs fresh Linux deps; never copy host ones. +**/node_modules +**/.next + +# Python — venvs + caches are rebuilt inside the image. +.venv +**/.venv +**/__pycache__ +**/.pytest_cache +**/.mypy_cache +**/.ruff_cache +**/*.pyc + +# Local-only config must not bake into images (configure via build args / mounts). +**/.env*.local diff --git a/.gitignore b/.gitignore index e0c900b..e6e36c4 100644 --- a/.gitignore +++ b/.gitignore @@ -233,6 +233,8 @@ __marimo__/ # Streamlit .streamlit/secrets.toml +# macOS +.DS_Store # macOS duplicate files (e.g. "foo 2.py") * 2.* diff --git a/BUGS.md b/BUGS.md new file mode 100644 index 0000000..ee06ee1 --- /dev/null +++ b/BUGS.md @@ -0,0 +1,284 @@ +# AgentContextOS — Bug Tracker + +> **How to use:** Log every bug you hit while testing here. Copy the +> [Bug entry template](#bug-entry-template) into the **Open** section, give it the next +> `BUG-NNN` id, and fill what you know — a half-filled entry beats an un-logged one. +> When a bug is fixed, flip its **Status**, add the PR/commit, and move it to **Resolved**. +> Keep the [Summary](#summary) counts and the [Index](#index) row in sync. +> +> Companion to [TRACKER.md](TRACKER.md) (build status). This file is *testing findings*, not roadmap. + +--- + +## Summary + +| Severity | Open | In progress | Resolved | Total | +|---|---|---|---|---| +| 🔴 Critical | 0 | 0 | 0 | 0 | +| 🟠 High | 2 | 0 | 1 | 3 | +| 🟡 Medium | 1 | 0 | 3 | 4 | +| 🟢 Low | 0 | 0 | 3 | 3 | +| **All** | **3** | **0** | **7** | **10** | + +> **This session:** BUG-002, BUG-003, BUG-004, BUG-007, BUG-008, BUG-009 fixed + verified +> (code + tests + browser). BUG-005 / BUG-006 investigated → structural backend changes, +> deferred with root cause below. BUG-010 environmental (couldn't reproduce). The two High +> "Open" are BUG-005 (audit) and BUG-010 (availability, can't-reproduce). + +--- + +## Index + +| ID | Title | Severity | Status | Area | Found | +|---|---|---|---|---|---| +| [BUG-001](#bug-001--new-corpus-does-not-persist-vanishes-on-reload) | New corpus does not persist (vanishes on reload) | 🟠 High | ✅ Resolved | admin-ui | 2026-06-11 | +| [BUG-002](#bug-002--edit-corpus-menu-item-does-nothing) | Edit corpus menu item does nothing | 🟡 Medium | ✅ Resolved | admin-ui | 2026-06-11 | +| [BUG-003](#bug-003--edit-webhook-subscription-menu-item-does-nothing) | Edit webhook subscription menu item does nothing | 🟡 Medium | ✅ Resolved | admin-ui + gateway | 2026-06-11 | +| [BUG-004](#bug-004--live-status-shows-seeddemo-data-as-live-with-no-demo-badge) | Live Status shows seed/demo data as "Live" with no demo badge | 🟡 Medium | ✅ Resolved | admin-ui | 2026-06-11 | +| [BUG-005](#bug-005--immutable-audit-log-returns-0-events-for-real-governance-actions) | Immutable audit log returns 0 events for real governance actions | 🟠 High | 🔵 Open | gateway | 2026-06-11 | +| [BUG-006](#bug-006--ingest-reports-success-but-corpus-document_count-stays-0) | Ingest reports success but corpus document_count stays 0 | 🟡 Medium | 🔵 Open | gateway | 2026-06-11 | +| [BUG-007](#bug-007--metrics-captured-timestamp-triggers-a-react-hydration-mismatch) | Metrics "Captured" timestamp triggers a React hydration mismatch | 🟢 Low | ✅ Resolved | admin-ui | 2026-06-11 | +| [BUG-008](#bug-008--lands-with-phase-6-preview-copy-is-stale-project-is-ga) | "Lands with Phase 6" preview copy is stale (project is GA) | 🟢 Low | ✅ Resolved | admin-ui | 2026-06-11 | +| [BUG-009](#bug-009--query-playground-shows-demo-data-badge-before-the-first-run) | Query Playground shows "Demo data" badge before the first run | 🟢 Low | ✅ Resolved | admin-ui | 2026-06-11 | +| [BUG-010](#bug-010--gateway-process-exited-mid-session-availability) | Gateway process exited mid-session (availability) | 🟠 High | 🔁 Can't reproduce | gateway | 2026-06-11 | + +--- + +## Open + +### BUG-002 — Edit corpus menu item does nothing + +- **Severity:** 🟡 Medium +- **Status:** ✅ Resolved +- **Area:** admin-ui (Corpora page) — the update ("U" of CRUD) path is unimplemented across UI + gateway + store +- **Found:** 2026-06-11 · **Version/commit:** `main` @ 6cbab40 + uncommitted step-3.10 work on `build/phase-3/step-3.10-corpus-admin-crud` +- **Environment:** admin-ui console (Next.js), demo or live mode. + +**Steps to reproduce** +1. Open **Corpora** → on any existing row, open the **⋯** row-actions menu. +2. Click **Edit**. + +**Expected:** An edit dialog opens to change the display name / description (or other mutable fields), then saves and persists. + +**Actual:** Nothing happens — the menu closes, no dialog, no request. + +**Evidence / root cause:** The **Edit** menu item is rendered with **no `onClick`** — unlike its siblings "View details" (`onClick={() => setSelected(c)}`) and "Delete" (`onClick={() => setDeleting(c)}`) ([corpora/page.tsx:130](apps/admin-ui/src/app/corpora/page.tsx#L130)). The entire update path is missing: +1. **UI:** no `editing` state, no `EditCorpusDialog` — Edit is a placeholder item ([corpora/page.tsx:130](apps/admin-ui/src/app/corpora/page.tsx#L130)). +2. **Client:** no `updateCorpus` in `lib/api.ts` — only `fetchCorpora` / `createCorpus` / `deleteCorpus`. +3. **Gateway:** no `PUT`/`PATCH /v1/corpora/{id}` — the router has `GET` list + `POST` + `GET {id}` + `DELETE` only ([corpora.py](apps/gateway/src/rag_gateway/corpora.py)). No `CorpusStore.update` on the SPI. + +**Notes / suspected cause:** The BUG-001 fix implemented **C**reate + **D**elete but skipped **U**pdate, leaving Edit as a dead menu item. This is in scope for the current `step-3.10-corpus-admin-crud` branch. Full fix spans: gateway `PATCH /v1/corpora/{id}` + `CorpusStore.update` (noop + pg) → `updateCorpus` in `api.ts` → an edit dialog + `onClick` in the page. **Partial workaround:** delete + recreate (loses the corpus id and any ingested docs). + +**Fix:** Implemented the full update path — gateway `PATCH /v1/corpora/{id}` + `CorpusStore.update` (SPI + noop + pg, tenant-scoped, embedding model/dim immutable) + `updateCorpus` in `api.ts` + an Edit dialog wired to the row menu. Contract + gateway-route tests added; OpenAPI re-exported; verified live (edit → save → persists to gateway). + +--- + +### BUG-003 — Edit webhook subscription menu item does nothing + +- **Severity:** 🟡 Medium · **Status:** ✅ Resolved +- **Area:** admin-ui (Webhooks page) + gateway — update ("U") path unimplemented, mirroring [BUG-002](#bug-002--edit-corpus-menu-item-does-nothing) +- **Found:** 2026-06-11 · **Version/commit:** `build/phase-3/step-3.10-corpus-admin-crud` @ 6e0c2c4 + +**Steps to reproduce** +1. Open **Webhooks** → create a subscription (or use an existing row). +2. Open the **⋯** row-actions menu → click **Edit**. + +**Expected:** An edit dialog opens to change URL / event types / description, then saves. + +**Actual:** Nothing happens — menu closes, no dialog, no request. (Verified twice in isolation.) + +**Evidence / root cause:** The webhook row **Edit** item has no working handler; there is no `updateWebhook` in `lib/api.ts` and no `PATCH /v1/webhooks/subscriptions/{id}` on the gateway. Same class of defect as BUG-002, for webhooks. + +**Fix:** Mirrored the BUG-002 fix for webhooks — gateway `PATCH /v1/webhooks/subscriptions/{id}` (signing secret stays masked) + `SubscriptionStore.update` (SPI + noop) + `updateWebhook` in `api.ts` + an Edit dialog; the row's active toggle now also persists in live mode. Gateway-route tests added; OpenAPI re-exported; verified live (edit → save → persists). + +--- + +### BUG-004 — Live Status shows seed/demo data as "Live" with no demo badge + +- **Severity:** 🟡 Medium · **Status:** ✅ Resolved +- **Area:** admin-ui (Live Status page) +- **Found:** 2026-06-11 · **Environment:** live mode, gateway with no breakers/quotas/etc. registered. + +**Steps to reproduce** +1. Run the gateway with noop backends (no circuit breakers / quota data). +2. Open **Live Status**. + +**Expected:** Sub-sections that fall back to seed data are visibly marked (a demo badge), as Playground/Audit/API-Keys do. + +**Actual:** Header shows "Live"; the **Circuit breakers / Quotas / Cost / Drift / Experiments** cards render realistic seed numbers (e.g. quotas "4,120,000 / 5,000,000 tokens", "$96.40 / $250.00") with **no demo indicator** — yet `/v1/status/breakers` returns `{"breakers":[],"total":0}`. Fabricated numbers read as live telemetry. + +**Evidence:** per-section `useLive(... source)` exists in [status/page.tsx](apps/admin-ui/src/app/status/page.tsx) (breakers L128, quotas L221, drift L325) but `source !== "live"` isn't surfaced. Genuinely-live data (overall health, components, API-surface telemetry) is correct. + +**Fix:** Each seed-fallback card (breakers / quotas / drift / feedback / cost / experiments) now renders a `SourceBadge` driven by its own `useLive` source — "Demo data" when the gateway has no data for it, "Live" when it does. The page header stays "Live" for the genuinely-live health stream, so live telemetry and seed numbers are no longer conflated. Verified live (header "Live", all six cards "Demo data"). (status/page.tsx) + +--- + +### BUG-005 — Immutable audit log returns 0 events for real governance actions + +- **Severity:** 🟠 High · **Status:** 🔵 Open (needs gateway triage — may be noop-store limitation) +- **Area:** gateway (audit) / admin-ui (Audit Log falls back to demo) +- **Found:** 2026-06-11 · **Environment:** live mode, noop AuditStore. + +**Steps to reproduce** +1. Against a fresh gateway, create + delete a corpus, create + delete a webhook, ingest a document (all via the UI). +2. `curl -H "X-Tenant-Id: acme" "http://127.0.0.1:8000/v1/audit?limit=200"`. + +**Expected:** Governance-relevant actions appear in the immutable audit log. + +**Actual:** `{"total":0}`. The Audit Log page therefore shows demo data (honestly badged). The structured *logs* (`/v1/status/logs`) DID capture `corpus.created`/`corpus.deleted`, so the events occur — but the audit store/endpoint surfaces nothing. + +**Notes:** **Investigated (this session) — root cause confirmed:** `/v1/audit` reads the same store the `AuditWriter` writes to (`app.state.audit_store = audit_writer.store`), but the corpus / webhook handlers only emit structured logs (`_log.info("corpus.created")`) and never call `AuditWriter.write` — so governance actions are logged but never audited. **Deferred (not fixed this session):** wiring `AuditWriter` consistently across every governance handler (corpus / webhook / ingest / …) with correct `AuditEvent` shapes is a governance design task + product decision (which actions are auditable), not a one-line fix. + +**Fix:** _TBD — deferred, see Notes (structural)_ + +--- + +### BUG-006 — Ingest reports success but corpus document_count stays 0 + +- **Severity:** 🟡 Medium · **Status:** 🔵 Open (needs gateway triage — may be noop-store limitation) +- **Area:** gateway (ingest → corpus store) +- **Found:** 2026-06-11 · **Environment:** live mode, noop stores. + +**Steps to reproduce** +1. Create a corpus; ingest a document into it via **Ingest** (`POST /v1/ingest/document` → 200, "1 chunk / 1 embedding"). +2. Reload **Corpora** (or `GET /v1/corpora`). + +**Expected:** Corpus `document_count` reflects ingested document(s). + +**Actual:** `document_count` stays **0**, so the UI shows "ingested" yet "0 documents" — confusing in a demo. + +**Notes:** **Investigated (this session) — root cause confirmed:** no `document_count` increment exists anywhere in the gateway; the ingest pipeline indexes chunks/embeddings but never touches the corpus counter (the create handler's "maintained by the ingest path" comment is aspirational). **Deferred (not fixed this session):** a correct fix needs a new `CorpusStore` surface (e.g. `increment_document_count` / recompute) wired from the ingest path — feature work, not a one-line fix. + +**Fix:** _TBD — deferred, see Notes (structural)_ + +--- + +### BUG-007 — Metrics "Captured" timestamp triggers a React hydration mismatch + +- **Severity:** 🟢 Low · **Status:** ✅ Resolved +- **Area:** admin-ui (Metrics page) +- **Found:** 2026-06-11 · **Environment:** full page load / refresh of `/metrics` (dev shows console errors; prod silently re-renders). + +**Steps to reproduce** +1. Hard-load `/metrics` (full SSR, not client nav). + +**Expected:** No hydration warnings. + +**Actual:** 8× `Text content did not match. Server: "7:05:28 PM" Client: "19:05:29"` — a 12h/24h locale mismatch. + +**Evidence / root cause:** [metrics/page.tsx:170](apps/admin-ui/src/app/metrics/page.tsx#L170) `new Date(metrics.captured_at).toLocaleTimeString()` is locale/timezone-dependent → SSR (server locale) ≠ client. Fires only on full load, not client-side nav. + +**Fix:** Added `suppressHydrationWarning` to the `Proc` value node ([metrics/page.tsx:170](apps/admin-ui/src/app/metrics/page.tsx#L170)) — the React-sanctioned escape hatch for locale/timezone-dependent timestamps. Verified: `tsc` + all admin-ui unit tests green. + +--- + +### BUG-008 — "Lands with Phase 6" preview copy is stale (project is GA) + +- **Severity:** 🟢 Low · **Status:** ✅ Resolved +- **Area:** admin-ui (API Keys, Tenants — copy) +- **Found:** 2026-06-11 + +**Actual:** API Keys ([api-keys/page.tsx:55](apps/admin-ui/src/app/api-keys/page.tsx#L55)) and Tenants ([tenants/page.tsx:53](apps/admin-ui/src/app/tenants/page.tsx#L53)) banners say management "lands with Phase 6 governance" — but the platform is at v1.0.0 GA (Phase 6 long complete). Misleading copy. + +**Fix:** Reworded both preview banners to drop the stale "lands with Phase 6 governance" claim → "the data shown here is local and not yet backed by the gateway." (api-keys/page.tsx, tenants/page.tsx) + +--- + +### BUG-009 — Query Playground shows "Demo data" badge before the first run + +- **Severity:** 🟢 Low · **Status:** ✅ Resolved +- **Area:** admin-ui (Query Playground) +- **Found:** 2026-06-11 + +**Actual:** With a live gateway configured, the Playground header shows a **"Demo data"** badge on initial load (result `source` defaults to `"mock"`); it clears after the first live query. Slightly misleading even though the corpus selector is already live. ([playground/page.tsx:183,355](apps/admin-ui/src/app/playground/page.tsx)) + +**Fix:** Gated the badge on `phase !== "idle"` so "Demo data" only appears after a run that actually used mock data, not on initial load of a live-configured page. (playground/page.tsx) + +--- + +### BUG-010 — Gateway process exited mid-session (availability) + +- **Severity:** 🟠 High · **Status:** 🔁 Can't reproduce (needs info) +- **Area:** gateway (availability) +- **Found:** 2026-06-11 · **Environment:** local `uvicorn` gateway on :8000. + +**Actual:** During UI testing the gateway process (PID 35523) vanished — `POST /v1/corpora` returned `ERR_CONNECTION_REFUSED` seconds after a successful `GET`. No 5xx (which a request-triggered crash would produce), so most likely stopped externally (parallel terminal/session). Restarted under preview control; could not attribute to a UI action. + +**Notes:** If reproducible, capture gateway stderr at exit. Likely environmental, not a code defect. + +**Fix:** n/a (environmental) — logged for visibility. + +--- + +## In progress + +_Nothing being worked on yet._ + +--- + +## Resolved + +### BUG-001 — New corpus does not persist (vanishes on reload) + +- **Severity:** 🟠 High · **Status:** ✅ Resolved +- **Area:** admin-ui (Corpora page) — root cause spanned gateway + backend +- **Found / Resolved:** 2026-06-11 / 2026-06-11 · **Where:** working tree on `build/phase-3/step-3.10-corpus-admin-crud` (uncommitted at time of fix) + +**Symptom:** *Create corpus* showed a "Corpus created" toast but the row vanished on reload — the UI never called a backend, and `/v1/corpora` was read-only (no create endpoint; default store in-memory). + +**Fix:** Built the corpus write path end-to-end — `POST /v1/corpora` + `DELETE /v1/corpora/{id}` on the gateway, persistent `CorpusStore` create/delete (noop + `pg_corpus_store`), `createCorpus`/`deleteCorpus` in `lib/api.ts`, and the Corpora page now calls them when live ([corpora/page.tsx:195](apps/admin-ui/src/app/corpora/page.tsx#L195), [:246](apps/admin-ui/src/app/corpora/page.tsx#L246)); demo mode still simulates locally. ⚠️ **Update/Edit was not included → [BUG-002](#bug-002--edit-corpus-menu-item-does-nothing).** + +--- + +## Bug entry template + +Copy this block, paste it under **Open**, bump the id, and fill it in. Leave a field blank +(or delete the line) if it doesn't apply — don't let a missing detail stop you from logging. + +```markdown +### BUG-NNN — + +- **Severity:** 🔴 Critical | 🟠 High | 🟡 Medium | 🟢 Low +- **Status:** 🔵 Open +- **Area:** +- **Found:** · **Version/commit:** +- **Environment:** + +**Steps to reproduce** +1. +2. +3. + +**Expected:** + +**Actual:** + +**Evidence:** + +**Notes / suspected cause:** + +**Fix:** +``` + +--- + +## Legend + +### Severity + +| | Meaning | +|---|---| +| 🔴 **Critical** | Data loss, security/tenant-isolation breach, or the platform is unusable. Drop everything. | +| 🟠 **High** | A core surface is broken (query/ingest/retrieve fails, wrong results). No safe workaround. | +| 🟡 **Medium** | Real bug with a workaround, or an edge case off the happy path. | +| 🟢 **Low** | Cosmetic, copy, or minor polish. | + +### Status + +| | Meaning | +|---|---| +| 🔵 **Open** | Logged, not yet being worked. | +| 🟣 **In progress** | Someone is actively fixing it. | +| ✅ **Resolved** | Fixed and verified — has a PR/commit. | +| ⚪ **Won't fix** | Intentional, out of scope, or by design — say why. | +| 🔁 **Can't reproduce** | Couldn't reproduce; needs more detail to reopen. | diff --git a/Dockerfile b/Dockerfile index 52d4fee..9f6ccd0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,12 @@ FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder WORKDIR /app +# Optional uv extras baked into the gateway venv. Empty by default keeps the +# production image lean (noop wiring serves every route with zero infra). Set +# GATEWAY_EXTRAS=backends to pull in rag-backends (asyncpg, etc.) so a +# config-driven serve can use the Postgres corpus store / real backends. +ARG GATEWAY_EXTRAS="" + # Copy workspace manifests first so this layer is cached unless deps change. COPY pyproject.toml uv.lock ./ COPY packages/core/pyproject.toml packages/core/ @@ -20,7 +26,7 @@ COPY packages/ packages/ COPY apps/gateway/ apps/gateway/ COPY sdks/python/ sdks/python/ RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --frozen --no-dev + uv sync --frozen --no-dev ${GATEWAY_EXTRAS:+--extra ${GATEWAY_EXTRAS}} # ── Runtime stage ───────────────────────────────────────────────────────────── FROM python:3.12-slim-bookworm AS runtime diff --git a/apps/admin-ui/Dockerfile b/apps/admin-ui/Dockerfile index 9c3d0ce..fc9b02f 100644 --- a/apps/admin-ui/Dockerfile +++ b/apps/admin-ui/Dockerfile @@ -11,6 +11,12 @@ FROM node:20-bookworm-slim AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY apps/admin-ui/ ./ +# Next.js inlines NEXT_PUBLIC_* into the client bundle at build time, so the +# gateway URL must be present during `next build` (a runtime -e is too late). +# Pass --build-arg NEXT_PUBLIC_GATEWAY_URL=http://localhost:8000 to wire the +# console to a live gateway; left empty, the UI falls back to demo/seed data. +ARG NEXT_PUBLIC_GATEWAY_URL="" +ENV NEXT_PUBLIC_GATEWAY_URL=$NEXT_PUBLIC_GATEWAY_URL RUN npm run build # Minimal runtime: just the standalone server + static assets, non-root. @@ -18,6 +24,9 @@ FROM node:20-bookworm-slim AS runner WORKDIR /app ENV NODE_ENV=production ENV PORT=3100 +# Bind the standalone server to all interfaces so the published port is +# reachable from outside the container (default would be localhost only). +ENV HOSTNAME=0.0.0.0 RUN addgroup --system --gid 1001 nodejs \ && adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public diff --git a/apps/admin-ui/public/.gitkeep b/apps/admin-ui/public/.gitkeep new file mode 100644 index 0000000..f2be36a --- /dev/null +++ b/apps/admin-ui/public/.gitkeep @@ -0,0 +1,3 @@ +# Keeps apps/admin-ui/public/ present so the Docker build's +# `COPY --from=builder /app/public ./public` step succeeds even when the +# console ships no static assets. Drop static files (favicon, images) here. diff --git a/apps/admin-ui/src/app/api-keys/page.tsx b/apps/admin-ui/src/app/api-keys/page.tsx index 4a16f7b..587ac1f 100644 --- a/apps/admin-ui/src/app/api-keys/page.tsx +++ b/apps/admin-ui/src/app/api-keys/page.tsx @@ -52,7 +52,7 @@ export default function ApiKeysPage() { />
- API-key management is a preview — the backend lands with Phase 6 governance. Data shown here is local. + API-key management is a preview — the data shown here is local and not yet backed by the gateway.
diff --git a/apps/admin-ui/src/app/chat/page.tsx b/apps/admin-ui/src/app/chat/page.tsx index 7c067a9..0a34954 100644 --- a/apps/admin-ui/src/app/chat/page.tsx +++ b/apps/admin-ui/src/app/chat/page.tsx @@ -130,7 +130,7 @@ function RagSummary({ msg }: { msg: ThreadMessage }) { function ChatTab({ models }: { models: ModelInfo[] }) { const { tenant } = useApp(); - const { rows: corpora } = useLive( + const { rows: corpora, source: corporaSource } = useLive( (tid) => CORPORA.filter((c) => c.tenant_id === tid), fetchCorpora, ); @@ -157,6 +157,14 @@ function ChatTab({ models }: { models: ModelInfo[] }) { setMessages([]); setRagCorpusIds([]); }, [tenant.id]); + // Default RAG scope to all of the tenant's live corpora once loaded, so chat + // grounds on your data with an explicit corpus_ids (empty would search all + // anyway, but the explicit payload is clearer). Live data only. + useEffect(() => { + if (corporaSource === "live" && corpora.length > 0) { + setRagCorpusIds(corpora.map((c) => c.id)); + } + }, [corporaSource, corpora]); useEffect( () => () => { if (streamTimer.current) clearInterval(streamTimer.current); diff --git a/apps/admin-ui/src/app/corpora/page.tsx b/apps/admin-ui/src/app/corpora/page.tsx index 9616187..c35c5ec 100644 --- a/apps/admin-ui/src/app/corpora/page.tsx +++ b/apps/admin-ui/src/app/corpora/page.tsx @@ -26,7 +26,7 @@ import { TableSkeleton, Textarea, } from "@/components/ui/primitives"; -import { createCorpus, deleteCorpus, fetchCorpora } from "@/lib/api"; +import { createCorpus, deleteCorpus, fetchCorpora, updateCorpus } from "@/lib/api"; import { GATEWAY_URL } from "@/lib/config"; import { CORPORA } from "@/lib/mock"; import { absTime, relTime } from "@/lib/time"; @@ -50,6 +50,7 @@ export default function CorporaPage() { const [selected, setSelected] = useState(null); const [creating, setCreating] = useState(false); + const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); return ( @@ -127,7 +128,9 @@ export default function CorporaPage() { } onClick={() => setSelected(c)}> View details - }>Edit + } onClick={() => setEditing(c)}> + Edit + } onClick={() => setDeleting(c)}> Delete @@ -225,6 +228,34 @@ export default function CorporaPage() { /> )} + {editing && ( + setEditing(null)} + onSave={async ({ display_name, description }) => { + let updated: Corpus; + if (GATEWAY_URL && source === "live") { + try { + updated = await updateCorpus(tenant.id, editing.id, { display_name, description }); + } catch { + toast({ title: "Failed to update corpus", variant: "error" }); + return; + } + } else { + updated = { + ...editing, + display_name, + description, + updated_at: new Date().toISOString(), + }; + } + setRows((prev) => prev.map((c) => (c.id === updated.id ? updated : c))); + setEditing(null); + toast({ title: "Corpus updated", description: updated.display_name, variant: "success" }); + }} + /> + )} + setDeleting(null)} @@ -326,3 +357,63 @@ function NewCorpusDialog({ ); } + +function EditCorpusDialog({ + corpus, + onClose, + onSave, +}: { + corpus: Corpus; + onClose: () => void; + onSave: (v: { display_name: string; description: string }) => void; +}) { + const [name, setName] = useState(corpus.display_name); + const [description, setDescription] = useState(corpus.description ?? ""); + const dirty = + name.trim() !== corpus.display_name || description.trim() !== (corpus.description ?? ""); + + return ( + +
+ + setName(e.target.value)} + placeholder="Support Knowledge Base" + /> + + +