feat: implement RAG-enabled chat hook and admin file upload component

This commit is contained in:
RaymondVerhoef
2026-05-24 16:59:11 +02:00
parent 881148357e
commit 8e01b21a50
5 changed files with 30 additions and 54 deletions

View File

@@ -49,7 +49,7 @@ All persistent data lives in **PocketBase**. The data access layer is in `src/li
## 3. The AI Integration (Anthropic)
The application calls the Anthropic API via a proxy to avoid CORS issues.
* **Location:** `src/lib/api.js`.
* **Location:** `src/lib/llm.js`.
* **Proxy:** In Docker, `/api/anthropic` is proxied via Caddy to the Anthropic API endpoint. In local dev, configure `vite.config.js` to proxy the same path. The API key is injected server-side by Caddy; there is **no client-side API key**.
* **Token limit:** `generateContent` uses `max_tokens: 8192`. Do not lower this — large knowledge-base files need the headroom.
* **JSON Enforcement:** Prompts *must* strictly enforce that Claude returns *only* raw JSON. Do not let the AI wrap the response in markdown blocks — a regex strip via `.match(/\{[\s\S]*\}/)` is already applied in all service files.
@@ -90,12 +90,10 @@ The app is fully containerized. PocketBase runs as a sidecar service.
* **Caddy (reverse proxy):** Handles SPA fallback, injects the Anthropic API key via a `Authorization` header on `/api/anthropic/*` requests, and proxies `/pb/*` to the PocketBase service. Config lives in `Caddyfile`.
* **PocketBase URL:** Resolved from `VITE_PB_URL` env var, or falls back to `window.location.origin + '/pb'` (see `src/lib/pb.js`).
## 8. GitHub Knowledge-Base Sync
The Admin upload panel (`src/components/admin/UploadZone.jsx`) can sync markdown/text files directly from a GitHub repository.
## 8. Local File Upload
The Admin upload panel (`src/components/admin/UploadZone.jsx`) allows admins to manually upload markdown/text files via a drag-and-drop interface.
* **Default folder:** `docs/knowledge-base/` of the `respellion/employee-handbook` repo. This is persisted in the `settings` collection under the key `github:url` so admins can change it from the UI.
* **Change detection:** Each file's SHA is stored as `github:sha:<filename>` in `settings`. Files whose SHA differs are marked *Updated*; new files are *New*. Up-to-date files are skipped.
* **Extraction pipeline (`src/lib/extractionPipeline.js`):** Calls `anthropicApi.generateContent` with a strict JSON-only system prompt. To prevent truncated responses on large files, the prompt limits extraction to **max 15 topics** and their most important relations. If the AI returns non-JSON, the file is marked `failed` in the `sources` collection.
* **Extraction pipeline (`src/lib/extractionPipeline.js`):** Calls `callLLM` with a strict JSON-only system prompt. To prevent truncated responses on large files, the prompt limits extraction to **max 15 topics** and their most important relations. If the AI returns non-JSON, the file is marked `failed` in the `sources` collection.
* **Deduplication:** A file already in `sources` with `status: completed` will throw and not be re-processed. Delete the source record first to force a re-analysis.
* **Do not increase topic cap beyond 15** without also verifying the `max_tokens: 8192` budget is sufficient for the expected file size.
@@ -109,7 +107,7 @@ The platform ships a global chatbot avatar called **R42**, rendered as the Respe
* `useChat.js` — owns the message list, persists to `chat:thread:{userId}`, calls `anthropicApi.chat()`. `buildKbContext` is async (PocketBase), so `send()` is fully async.
* `prompts.js` — system prompt, greeting, and the `propose_graph_delta` tool spec.
* `rag.js` — fetches `kb:topics` + `kb:relations` from PocketBase; lazy-loads `kb:content:{id}` only when a topic is mentioned. Returns `{ context, topics }` so `validateDelta` can reuse the fetched topics without a second round-trip.
* **Multi-turn API:** `anthropicApi.chat(systemPrompt, messages, { tools })` in `src/lib/api.js`. Returns the raw Anthropic response (`{ content: [...], stop_reason }`) so callers can read both text blocks and `tool_use` blocks. No API key header — Caddy proxy injects it server-side, matching the existing `generateContent` pattern.
* **Multi-turn API:** `callLLM({ task, system, messages, tools })` in `src/lib/llm.js`. Returns a structured response containing extracted `toolUses` and text. No API key header — Caddy proxy injects it server-side.
* **Quiz-integrity rule:** `src/pages/Testen.jsx` sets `quiz:active:{userId}=true` on start and clears it on every non-quiz phase + unmount, plus dispatches a `respellion:quiz-state` event. Never bypass this — letting users ask R42 mid-quiz would break scoring.
* **Graph refinement:** when R42's `tool_use` block proposes a `propose_graph_delta`, `rag.js` validates (no duplicate ids, no orphan relations, caps 3 topics / 5 relations) and surfaces a confirmation chip inline.
* **Admin user clicks Ja**`kbStore.applyDelta` writes to PocketBase via `db.upsertTopic` / `db.addRelation` immediately.