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
76 changes: 40 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

AI Workbench is a self-hosted product surface for building, inspecting,
and operating retrieval-backed AI applications on **DataStax Astra**.
It gives teams one place to manage workspaces, catalogs, vector stores,
document ingest, saved queries, API keys, and retrieval experiments.
It gives teams one place to manage workspaces, knowledge bases,
chunking / embedding / reranking services, document ingest, API keys,
and retrieval experiments.

Under the product UI is a stable HTTP runtime. The default TypeScript
runtime ships in the same Docker image as the UI; alternative
Expand All @@ -12,11 +13,15 @@ language-native runtimes ("green boxes") live under

## At a glance

- **Workspace command center.** Workspaces isolate catalogs, vector
stores, documents, saved queries, jobs, credentials, and API keys.
- **Knowledge operations.** Ingest raw text or files into catalogs,
track sync/async job state, and bind content to the vector store that
powers retrieval.
- **Workspace command center.** Workspaces isolate knowledge bases,
execution services, documents, jobs, credentials, and API keys.
- **Knowledge bases as first-class.** A KB owns its Astra collection
end-to-end and binds the chunking + embedding + (optional)
reranking services that produce its content. The collection is
auto-provisioned on create.
- **Knowledge operations.** Ingest raw text or files into a KB,
track sync/async job state, and let the KB's bound services drive
chunking and embedding.
- **Retrieval playground.** Run text, vector, hybrid, and rerank
searches in the browser against real workspace data.
- **Production-friendly controls.** Start in memory, switch to file
Expand Down Expand Up @@ -51,17 +56,21 @@ language-native runtimes ("green boxes") live under
└──────────────── same HTTP contract ───────────────────┘
▼ (per-runtime Astra SDK)
┌─────────────────────────────┐
│ Astra Data API │
│ Tables (control plane): │
│ wb_workspaces │
│ wb_catalog_by_ws │
│ wb_vector_store_by_ws │
│ wb_documents_by_cat │
│ Collections (data │
│ plane): one per │
│ vector store │
└─────────────────────────────┘
┌──────────────────────────────────┐
│ Astra Data API │
│ Tables (control plane): │
│ wb_workspaces │
│ wb_config_knowledge_ │
│ bases_by_workspace │
│ wb_config_chunking/ │
│ embedding/reranking │
│ _service_by_workspace │
│ wb_rag_documents_ │
│ by_knowledge_base │
│ Collections (data plane): │
│ wb_vectors_<kb_id> │
│ (one per knowledge base) │
└──────────────────────────────────┘
```

See [`docs/architecture.md`](docs/architecture.md) for the full model.
Expand Down Expand Up @@ -109,26 +118,21 @@ All routes documented at `/docs` (Scalar UI) and
| `GET / POST` | `/api/v1/workspaces` | List / create workspaces |
| `GET / PUT / DELETE` | `/api/v1/workspaces/{w}` | Workspace CRUD (DELETE cascades) |
| `POST` | `/api/v1/workspaces/{w}/test-connection` | Resolve configured workspace credential refs |
| `GET / POST` | `/api/v1/workspaces/{w}/catalogs` | List / create catalogs |
| `GET / PUT / DELETE` | `/api/v1/workspaces/{w}/catalogs/{c}` | Catalog CRUD (DELETE cascades to documents + saved queries) |
| `GET / POST` | `/api/v1/workspaces/{w}/catalogs/{c}/documents` | List / create document metadata |
| `GET / PUT / DELETE` | `/api/v1/workspaces/{w}/catalogs/{c}/documents/{d}` | Document metadata CRUD (DELETE cascades chunks via the bound vector store) |
| `GET` | `/api/v1/workspaces/{w}/catalogs/{c}/documents/{d}/chunks` | List the chunks under a document (id, chunkIndex, text, payload) |
| `POST` | `/api/v1/workspaces/{w}/catalogs/{c}/documents/search` | Catalog-scoped search (vector / text, optional hybrid + rerank) |
| `POST` | `/api/v1/workspaces/{w}/catalogs/{c}/ingest` | Sync ingest (chunk → embed → upsert → register Document) |
| `POST` | `/api/v1/workspaces/{w}/catalogs/{c}/ingest?async=true` | Same pipeline, returns 202 + job pointer |
| `GET / POST` | `/api/v1/workspaces/{w}/catalogs/{c}/queries` | List / create saved queries |
| `GET / PUT / DELETE` | `/api/v1/workspaces/{w}/catalogs/{c}/queries/{q}` | Saved-query CRUD |
| `POST` | `/api/v1/workspaces/{w}/catalogs/{c}/queries/{q}/run` | Replay a saved query through catalog-scoped search |
| `GET / POST` | `/api/v1/workspaces/{w}/knowledge-bases` | List / create knowledge bases (POST auto-provisions the underlying vector collection) |
| `GET / PUT / DELETE` | `/api/v1/workspaces/{w}/knowledge-bases/{kb}` | KB CRUD (DELETE drops the collection + cascades RAG documents) |
| `GET / POST` | `/api/v1/workspaces/{w}/knowledge-bases/{kb}/documents` | List / register a document in a KB |
| `GET / PUT / DELETE` | `/api/v1/workspaces/{w}/knowledge-bases/{kb}/documents/{d}` | Document metadata CRUD (DELETE cascades chunks in the KB's collection) |
| `GET` | `/api/v1/workspaces/{w}/knowledge-bases/{kb}/documents/{d}/chunks` | List the chunks under a document |
| `POST` | `/api/v1/workspaces/{w}/knowledge-bases/{kb}/ingest` | Sync ingest (chunk → embed → upsert → register Document) |
| `POST` | `/api/v1/workspaces/{w}/knowledge-bases/{kb}/ingest?async=true` | Same pipeline, returns 202 + job pointer |
| `POST` | `/api/v1/workspaces/{w}/knowledge-bases/{kb}/records` | Upsert vector or text records (text → server-side `$vectorize` when supported, otherwise client-side embed) |
| `DELETE` | `/api/v1/workspaces/{w}/knowledge-bases/{kb}/records/{rid}` | Delete one |
| `POST` | `/api/v1/workspaces/{w}/knowledge-bases/{kb}/search` | KB-scoped search (vector / text, optional hybrid + rerank) |
| `GET / POST / DELETE` | `/api/v1/workspaces/{w}/chunking-services` | Chunking-service CRUD |
| `GET / POST / DELETE` | `/api/v1/workspaces/{w}/embedding-services` | Embedding-service CRUD |
| `GET / POST / DELETE` | `/api/v1/workspaces/{w}/reranking-services` | Reranking-service CRUD |
| `GET` | `/api/v1/workspaces/{w}/jobs/{jobId}` | Poll an async-ingest job |
| `GET` | `/api/v1/workspaces/{w}/jobs/{jobId}/events` | SSE stream of job updates until terminal state |
| `GET / POST` | `/api/v1/workspaces/{w}/vector-stores` | List / create vector-store descriptors (POST provisions the collection too) |
| `GET` | `/api/v1/workspaces/{w}/vector-stores/discoverable` | List data-plane collections not yet wrapped in a descriptor |
| `POST` | `/api/v1/workspaces/{w}/vector-stores/adopt` | Wrap an existing collection in a descriptor without re-provisioning |
| `GET / PUT / DELETE` | `/api/v1/workspaces/{w}/vector-stores/{v}` | Descriptor CRUD (DELETE drops the collection) |
| `POST` | `/api/v1/workspaces/{w}/vector-stores/{v}/records` | Upsert vector or text records (text → server-side `$vectorize` when supported, otherwise client-side embed) |
| `DELETE` | `/api/v1/workspaces/{w}/vector-stores/{v}/records/{rid}` | Delete one |
| `POST` | `/api/v1/workspaces/{w}/vector-stores/{v}/search` | Vector or text search; supports `hybrid`, `lexicalWeight`, `rerank` |
| `GET / POST` | `/api/v1/workspaces/{w}/api-keys` | List / issue workspace API keys |
| `DELETE` | `/api/v1/workspaces/{w}/api-keys/{keyId}` | Revoke a workspace API key |

Expand Down
68 changes: 27 additions & 41 deletions apps/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ consumes `/api/v1/workspaces` on the default TypeScript runtime.
## Status

**Shipped.** First-run onboarding wizard, workspace list / detail /
edit / destructive delete, full CRUD over catalogs, vector-store
descriptors, and workspace-scoped API keys. Async ingest from the
browser (file upload → chunk → embed → upsert) with live progress
streamed via SSE. Saved-query CRUD per catalog, runnable from the UI.
Playground for ad-hoc text / vector / hybrid / rerank queries against
any vector store. OIDC login + silent refresh and paste-a-token
fallback are both wired through the same auth layer.
edit / destructive delete, full CRUD over knowledge bases,
chunking / embedding / reranking services, and workspace-scoped API
keys. Async ingest from the browser (file upload → chunk → embed →
upsert) with live progress streamed via SSE. Playground for ad-hoc
text / vector / hybrid / rerank queries against any knowledge base.
OIDC login + silent refresh and paste-a-token fallback are both wired
through the same auth layer.

HCD and OpenRAG kinds are visible in the onboarding picker but
intentionally non-selectable ("Coming soon" badge) — the runtime
Expand Down Expand Up @@ -95,38 +95,27 @@ navigation shows the shared loader while the chunk streams.
|---|---|
| `/` | Workspaces list. Redirects to `/onboarding` when empty. |
| `/onboarding` | Two-step wizard — pick a backend kind, then fill details. HCD / OpenRAG tiles render but are non-selectable. |
| `/workspaces/:uid` | Detail + edit + destructive delete (type-to-confirm). Hosts the catalogs, vector-stores, and API-keys panels for this workspace. |
| `/workspaces/:uid/catalogs/:catalogId` | Catalog explorer — sortable / filterable document table with file-type badges, sizes, statuses, and a click-through detail dialog. Multi-file / folder ingest queue lives here. |
| `/playground` | Ad-hoc text / vector / hybrid / rerank queries against a workspace's vector stores. See [`docs/playground.md`](../../docs/playground.md). |
| `/workspaces/:uid` | Detail + edit + destructive delete (type-to-confirm). Hosts the knowledge-bases, services, and API-keys panels for this workspace. |
| `/workspaces/:uid/knowledge-bases/:kbId` | Knowledge-base explorer — sortable / filterable document table with file-type badges, sizes, statuses, and a click-through detail dialog. Multi-file / folder ingest queue lives here. |
| `/playground` | Ad-hoc text / vector / hybrid / rerank queries against a workspace's knowledge bases. See [`docs/playground.md`](../../docs/playground.md). |

The workspace detail page composes four panels (collapsible cards):

| Panel | What it does |
|---|---|
| Catalogs | List + create + delete catalogs. Each row expands to a quick-look document preview and houses the saved-queries section. The "Open" button on every row jumps to the catalog explorer for the full table; "Ingest" pops the multi-file / folder upload queue. |
| Vector stores | List + create + delete vector-store descriptors. Create flow provisions the underlying collection on the bound driver. |
| Knowledge bases | List + create + delete knowledge bases. Create flow auto-provisions the underlying vector collection sized to the bound embedding service. The "Open" button on every row jumps to the KB explorer for the full document table; "Ingest" pops the multi-file / folder upload queue. |
| Services | List + create + delete chunking, embedding, and reranking service definitions. Services are reusable across knowledge bases in the same workspace. |
| API keys | List + issue + revoke workspace-scoped `wb_live_*` keys. Fresh keys are shown once, then masked. |
| Detail / edit | The kind-aware edit form (kind is read-only after create) and the destructive delete dialog. |

The catalog explorer adds:
The KB explorer adds:

- A document table with sortable columns (name, size, chunks, status, ingestedAt) and an inline filename/source-id filter.
- Color-coded `FileTypeBadge` (Markdown violet, structured-data emerald, tabular amber, code blue, etc.) and pill-shaped `DocumentStatusBadge` (animated glyph for in-flight states).
- Per-row trash button that pops a confirm dialog and runs the cascade-delete: the bound vector store's chunks are wiped before the document row is dropped, so deleted documents don't surface in playground searches.
- Per-row trash button that pops a confirm dialog and runs the cascade-delete: the KB's chunks are wiped before the document row is dropped, so deleted documents don't surface in playground searches.
- Click-through metadata dialog showing the full Document record, the failure message verbatim when status is `failed`, **and** the chunks the runtime extracted (chunk index, id, and snippet text — text comes from the reserved `chunkText` payload key the ingest pipeline stamps).
- An ingest queue dialog accepting drag-drop, multi-file picker, or a folder picker (`webkitdirectory`). Files run sequentially through async ingest with a per-row live progress bar — sequential rather than parallel so embedding-provider rate limits stay predictable and a misbehaving file doesn't tank the others.

The vector-stores panel on the workspace detail also exposes an
**Adopt existing** button. It opens a dialog listing collections
that already live in the workspace's data plane but aren't yet
wrapped in a workbench descriptor (created by another tool, by
hand, by an older workbench install whose state was wiped). One
click adopts the collection — the runtime reads the live vector /
lexical / rerank options off the data plane and stamps a matching
descriptor without re-provisioning. Mock workspaces always see
the empty state since the mock driver has no notion of "external"
collections.

## Stack

- **Vite + React 19 + TypeScript** — standard modern baseline.
Expand All @@ -137,7 +126,7 @@ collections.
- **React Hook Form + Zod** for forms; the same Zod schemas that
describe API shapes drive form validation, so the UI and backend
can't disagree about request shape.
- **React Router** for the five routes (`/`, `/onboarding`, `/workspaces/:uid`, `/workspaces/:uid/catalogs/:catalogId`, `/playground`).
- **React Router** for the five routes (`/`, `/onboarding`, `/workspaces/:uid`, `/workspaces/:uid/knowledge-bases/:kbId`, `/playground`).
- **Sonner** for toasts.
- **Lucide React** for icons.

Expand All @@ -162,11 +151,10 @@ apps/web/
│ │ └── utils.ts ← cn() + formatDate()
│ ├── hooks/
│ │ ├── useWorkspaces.ts ← list/get/create/update/delete
│ │ ├── useCatalogs.ts ← catalog CRUD
│ │ ├── useDocuments.ts ← per-catalog document list
│ │ ├── useVectorStores.ts ← vector-store descriptor CRUD
│ │ ├── useKnowledgeBases.ts ← knowledge-base CRUD
│ │ ├── useServices.ts ← chunking/embedding/reranking service CRUD
│ │ ├── useDocuments.ts ← per-KB document list
│ │ ├── useIngest.ts ← async ingest + SSE progress
│ │ ├── useSavedQueries.ts ← saved-query CRUD + /run
│ │ ├── usePlaygroundSearch.ts ← /search dispatch + result hits
│ │ ├── useApiKeys.ts ← workspace API-key mutations
│ │ ├── useAuthToken.ts ← reactive bearer-token hook
Expand All @@ -190,22 +178,19 @@ apps/web/
│ │ ├── TestConnectionPanel.tsx
│ │ ├── ApiKeysPanel.tsx
│ │ ├── CreateApiKeyDialog.tsx
│ │ ├── CatalogsPanel.tsx ← catalog list + per-row docs preview
│ │ ├── CreateCatalogDialog.tsx
│ │ ├── KnowledgeBasesPanel.tsx ← KB list + per-row docs preview
│ │ ├── CreateKnowledgeBaseDialog.tsx
│ │ ├── ServicesPanel.tsx ← chunking/embedding/reranking services
│ │ ├── DocumentTable.tsx ← sortable doc table for the explorer
│ │ ├── DocumentDetailDialog.tsx
│ │ ├── DocumentStatusBadge.tsx
│ │ ├── FileTypeBadge.tsx
│ │ ├── IngestQueueDialog.tsx ← multi-file / folder ingest queue
│ │ ├── SavedQueriesSection.tsx
│ │ ├── VectorStoresPanel.tsx
│ │ ├── CreateVectorStoreDialog.tsx
│ │ └── AdoptCollectionDialog.tsx ← discover + adopt existing collections
│ │ └── IngestQueueDialog.tsx ← multi-file / folder ingest queue
│ └── pages/
│ ├── WorkspacesPage.tsx
│ ├── OnboardingPage.tsx
│ ├── WorkspaceDetailPage.tsx
│ ├── CatalogExplorerPage.tsx
│ ├── KnowledgeBaseExplorerPage.tsx
│ └── PlaygroundPage.tsx
```

Expand All @@ -218,7 +203,8 @@ apps/web/
`provider:path` shape inline and drops empty rows before submit.
The runtime rejects raw secrets with `400` anyway.
- **Destructive delete requires typing the workspace name.** Cascade
is real — catalogs, vector-store collections, and documents all go.
is real — knowledge bases, their underlying vector collections,
service definitions, and documents all go.
- **Empty state → onboarding redirect.** First-run users never see a
bare "no workspaces" screen; they land directly in the wizard.
- **List order is deterministic.** The runtime sorts by `createdAt`
Expand Down Expand Up @@ -246,11 +232,11 @@ apps/web/
| `npm test` | Unit + component tests under `src/**/*.{test,spec}.{ts,tsx}` (vitest + jsdom + RTL). Fast — no browser. |
| `npm run test:watch` | Same in watch mode. |
| `npm run test:coverage` | Same as `npm test` but with v8 coverage. **Gates `src/lib/**` at lines: 50, statements: 50, branches: 80, functions: 20.** Components are exercised end-to-end through Playwright; locking thresholds on them prematurely pushes toward shallow tests. |
| `npm run test:e2e` | Playwright golden-path spec. Builds the runtime + SPA, boots the runtime against the bundled `examples/workbench.yaml` (memory backend, auth disabled), drives Chromium through the onboarding → vector-store → upsert → playground flow. Reuses an existing `:8080` server in dev; CI starts a fresh one. |
| `npm run test:e2e` | Playwright golden-path spec. Builds the runtime + SPA, boots the runtime against the bundled `examples/workbench.yaml` (memory backend, auth disabled), drives Chromium through the onboarding → services → knowledge-base → upsert → playground flow. Reuses an existing `:8080` server in dev; CI starts a fresh one. |
| `npm run test:e2e:ui` | Same in Playwright's UI mode for debugging. |
| `npm run e2e:install` | One-time: `playwright install chromium --with-deps`. |

E2E specs deliberately stay on the **vector** lane. The route's `resolveQuery()` always builds an `Embedder` for any text query (so hybrid search has a vector handle); with a mock embedding descriptor the production embedder factory throws `embedding_unavailable`. Vector input bypasses that path entirely. Adding text-search coverage to the E2E suite needs either a real provider key in CI or a runtime override that lets a fake embedder run alongside production code — both deferred.
E2E specs deliberately stay on the **vector** lane. The route's `resolveQuery()` always builds an `Embedder` for any text query (so hybrid search has a vector handle); with a mock embedding-service config the production embedder factory throws `embedding_unavailable`. Vector input bypasses that path entirely. Adding text-search coverage to the E2E suite needs either a real provider key in CI or a runtime override that lets a fake embedder run alongside production code — both deferred.

## House rules

Expand Down
Loading
Loading