|
1 | 1 | # Agents Guide |
2 | 2 |
|
3 | | -Dieses Repo ist ein **Browser-LLM-Chat-Scaffold** (Yesterday). Du bist hier richtig gelandet — lies diesen File, dann nach Bedarf weiter. |
| 3 | +This repo is a **browser-local LLM chat scaffold** maintained by Yesterday. You've landed in the right place — read this file first, then branch out as needed. |
4 | 4 |
|
5 | | -## Reading Order |
| 5 | +## Reading order |
6 | 6 |
|
7 | | -1. **AGENTS.md** (hier) — Regeln, Tasks, Gotchas |
8 | | -2. **CONTEXT.md** — Architektur-Big-Picture, Datenfluss, kritische Patterns |
9 | | -3. **DECISIONS.md** — Warum die wichtigen Entscheidungen so fielen (DEC-001 … DEC-009) |
10 | | -4. **ROADMAP.md** — Was kommt, mit Acceptance-Criteria |
11 | | -5. **CONTRIBUTING.md** — Dev-Workflow + PR-Regeln |
12 | | -6. Source-Code: `src/lib/` (Domain-Logik) → `src/hooks/` (Adapter) → `src/components/` (UI) |
| 7 | +1. **AGENTS.md** (here) — rules, tasks, gotchas |
| 8 | +2. **CONTEXT.md** — architecture big picture, data flow, critical patterns |
| 9 | +3. **DECISIONS.md** — why the important decisions went the way they did (DEC-001 … DEC-009) |
| 10 | +4. **ROADMAP.md** — what's coming, with acceptance criteria |
| 11 | +5. **CONTRIBUTING.md** — dev workflow + PR rules |
| 12 | +6. Source: `src/lib/` (domain logic) → `src/hooks/` (adapters) → `src/components/` (UI) |
13 | 13 |
|
14 | | -## Build & Test |
| 14 | +## Build & test |
15 | 15 |
|
16 | 16 | ```bash |
17 | | -pnpm install # einmalig |
18 | | -pnpm dev # Dev-Server, COOP/COEP-Headers gesetzt |
| 17 | +pnpm install # once |
| 18 | +pnpm dev # dev server, COOP/COEP headers set |
19 | 19 | pnpm lint # Biome |
20 | 20 | pnpm typecheck # tsc -b --noEmit |
21 | | -pnpm build # Production + PWA-SW |
| 21 | +pnpm build # production + PWA SW |
22 | 22 | ``` |
23 | 23 |
|
24 | | -Vor jedem PR: `pnpm lint && pnpm typecheck && pnpm build` — alle drei gruen. |
| 24 | +Before every PR: `pnpm lint && pnpm typecheck && pnpm build` — all three green. |
25 | 25 |
|
26 | | -## Regeln |
| 26 | +## Rules |
27 | 27 |
|
28 | | -- **Verifikation vor Claim**: nach jeder relevanten Aenderung `pnpm typecheck` UND `pnpm build`, nicht nur typecheck. |
29 | | -- **Keine Modelle committen**: OPFS-Cache bleibt im Browser, nie nach Disk exportieren. `public/wasm/` ist `.gitignore`d. |
30 | | -- **Keine destruktiven Datei-Operationen** — siehe globale CLAUDE.md. |
31 | | -- **Biome fuer Formatting + Lint** — nicht ESLint/Prettier dazumixen. |
32 | | -- **Async-Generator Bridge in `mediapipe-llm.ts`** nicht anfassen ohne Verstaendnis — MediaPipe-Callback und AbortSignal-Handling sind zusammen fragil. |
33 | | -- **Stream tee() im OPFS-Cache** nicht umbauen — beide Branches muessen parallel konsumiert werden, sonst backpressure-Deadlock. |
34 | | -- **External-Store-Runtime, nicht LocalRuntime** — wir halten Messages selbst in Dexie + Zustand. |
| 28 | +- **Verify before claiming**: after any relevant change run `pnpm typecheck` AND `pnpm build`, not just typecheck. |
| 29 | +- **Never commit models**: the OPFS cache stays in the browser, never export to disk. `public/wasm/` is gitignored. |
| 30 | +- **No destructive file operations** — see the global CLAUDE.md. |
| 31 | +- **Biome for formatting + lint** — don't mix in ESLint/Prettier. |
| 32 | +- **Don't touch the async-generator bridge in `mediapipe-llm.ts`** without understanding it — MediaPipe's callback API and the AbortSignal handling are fragile together. |
| 33 | +- **Don't restructure the stream `tee()` in the OPFS cache** — both branches must be consumed in parallel or backpressure deadlocks. |
| 34 | +- **External-store runtime, not LocalRuntime** — we hold messages ourselves in Dexie + Zustand. |
35 | 35 |
|
36 | | -## Typische Tasks |
| 36 | +## Typical tasks |
37 | 37 |
|
38 | | -| Task | Pfad | |
| 38 | +| Task | Where | |
39 | 39 | |---|---| |
40 | | -| Neues Modell hinzufuegen | `src/lib/model-catalog.ts` erweitern | |
41 | | -| UI-Komponente hinzufuegen | `src/components/` + assistant-ui Primitives | |
42 | | -| Persistenz-Schema erweitern | `src/lib/db.ts` Version bumpen (Dexie migration) | |
43 | | -| Branding anpassen | `src/lib/project.ts`, `public/icons/icon.svg`, `index.html` | |
44 | | -| Neue Sprache hinzufuegen | neue `src/lib/i18n/<locale>.ts` + `LOCALES` + `LOCALE_LABELS` + `DICTS` in `index.ts` + `detectLocale` | |
45 | | -| Neue Tagline dazu | `src/lib/taglines.ts` pro Sprache | |
46 | | -| Default-Sampling aendern | `src/lib/mediapipe-llm.ts#DEFAULT_OPTIONS` | |
47 | | -| Deploy-Workflow | `.github/workflows/deploy.yml`; Base-Path via `PROJECT.basePath` | |
48 | | -| Prompt-Format fuer anderes Modell | `src/lib/prompt-template.ts` | |
49 | | -| PWA-Caching anpassen | `vite.config.ts#VitePWA.workbox.runtimeCaching` | |
50 | | - |
51 | | -## Nicht tun |
52 | | - |
53 | | -- Kein HTTP-Backend-Layer — der Point ist Serverless-Browser. |
54 | | -- Keine OpenAI-SDK-Stubs — wir nutzen ausschliesslich `@mediapipe/tasks-genai`. |
55 | | -- Keine `alert()` / `confirm()` — UI via React-Components. |
56 | | -- Keine Model-Files in `public/` oder Git-LFS — nur WASM (via `copy-wasm`). |
57 | | -- Keine harten `localhost`-URLs — Modelle kommen aus HF Hub (direkt) oder zukuenftig R2-Mirror (via Env). |
| 40 | +| Add a new model | extend `src/lib/model-catalog.ts` | |
| 41 | +| Add a UI component | `src/components/` + assistant-ui primitives | |
| 42 | +| Extend the persistence schema | bump `src/lib/db.ts` version (Dexie migration) | |
| 43 | +| Change branding | `src/lib/project.ts`, `public/icons/icon.svg`, `index.html` | |
| 44 | +| Add a language | new `src/lib/i18n/<locale>.ts` + `LOCALES` + `LOCALE_LABELS` + `DICTS` in `index.ts` + `detectLocale` | |
| 45 | +| Add a tagline | `src/lib/taglines.ts` per language | |
| 46 | +| Change default sampling | `src/lib/mediapipe-llm.ts#DEFAULT_OPTIONS` | |
| 47 | +| Deploy pipeline | `.github/workflows/deploy.yml`; base path via `PROJECT.basePath` | |
| 48 | +| Prompt format for a different model | `src/lib/prompt-template.ts` | |
| 49 | +| Adjust PWA caching | `src/sw.ts` (routes, plugins, COI header injection) | |
| 50 | + |
| 51 | +## Don't do |
| 52 | + |
| 53 | +- No HTTP backend layer — the point is serverless-in-browser. |
| 54 | +- No OpenAI-SDK stubs — we use `@mediapipe/tasks-genai` exclusively. |
| 55 | +- No `alert()` / `confirm()` — UI through React components only. |
| 56 | +- No model files in `public/` or Git-LFS — only WASM (via `copy-wasm`). |
| 57 | +- No hardcoded `localhost` URLs — models come from HF Hub directly or (future) an R2 mirror. |
58 | 58 |
|
59 | 59 | ## Debugging |
60 | 60 |
|
61 | | -- Chrome DevTools → Application → Storage → OPFS: Model-Files sichtbar |
62 | | -- Application → IndexedDB → `browser-llm-demo`: Conversations + Messages |
63 | | -- Application → Service Workers: SW Status (nur in Production-Build aktiv) |
64 | | -- Network → Filter `wasm`: sieht WASM-CacheFirst-Hits |
65 | | -- `navigator.storage.estimate()` in Console: Quota-Status |
| 61 | +- Chrome DevTools → Application → Storage → OPFS: model files visible |
| 62 | +- Application → IndexedDB → `browser-llm-demo`: conversations + messages |
| 63 | +- Application → Service Workers: SW status (only active in production builds) |
| 64 | +- Network → filter `wasm`: WASM CacheFirst hits |
| 65 | +- `navigator.storage.estimate()` in console: quota status |
| 66 | +- `window.crossOriginIsolated` in console: must be `true` for threaded WASM |
66 | 67 |
|
67 | | -## Upstream-Referenz |
| 68 | +## Upstream reference |
68 | 69 |
|
69 | | -Port-Quellen (Apache-2.0): |
| 70 | +Port sources (Apache-2.0): |
70 | 71 |
|
71 | 72 | - `https://github.com/google-ai-edge/mediapipe-samples/tree/main/examples/llm_inference/llm_chat_ts` |
72 | | -- `opfs_cache.ts` und `llm_service.ts` sind die Kern-Referenzen fuer OPFS- und MediaPipe-Interop. |
| 73 | +- `opfs_cache.ts` and `llm_service.ts` are the core references for OPFS + MediaPipe interop. |
73 | 74 |
|
74 | 75 | ## Gotchas |
75 | 76 |
|
76 | | -- **iOS Safari < 18.4**: keine WebGPU → CPU-Fallback, E2B zaeh, E4B nicht sinnvoll |
77 | | -- **OPFS-Quota unter iOS**: ~1 GB default, via `persist()` erweiterbar |
78 | | -- **PWA Install-Prompt**: Android-Chrome automatisch, iOS manuell "Zum Home-Bildschirm" |
79 | | -- **SharedArrayBuffer**: braucht COOP/COEP — bei GitHub Pages via `coi-serviceworker` (siehe DEC-008) automatisch |
80 | | -- **HF-Rate-Limits**: unwahrscheinlich bei Direct-Downloads, aber bei vielen Users evtl. Mirror noetig |
81 | | -- **Shader-Compile-Latenz**: 10–25 s beim ersten Load pro Session, danach Browser-WebGPU-Cache greift |
82 | | -- **Firefox ~30–50 % langsamer** als Chrome (wgpu vs Dawn + OPFS SQLite). Feature, nicht Bug. |
83 | | -- **Stop-Token-Leak**: Gemma emittet manchmal `<end_of_turn>` literal. Regex in `mediapipe-llm.ts#GEMMA_STOP_PATTERN` + `cancelProcessing()` fangen das |
84 | | -- **Doppel-BOS vermeiden**: MediaPipes Tokenizer prepended BOS automatisch — nicht manuell in `renderGemmaPrompt` einfuegen |
| 77 | +- **iOS Safari < 18.4**: no WebGPU → CPU fallback, E2B is slow, E4B not viable |
| 78 | +- **OPFS quota on iOS**: ~1 GB default, extendable via `persist()` |
| 79 | +- **PWA install prompt**: automatic on Android Chrome, manual "Add to Home Screen" on iOS |
| 80 | +- **SharedArrayBuffer**: needs COOP/COEP — on GitHub Pages we inject them via `src/sw.ts` (see DEC-008) |
| 81 | +- **HF rate limits**: unlikely for direct downloads, but may need a mirror at scale |
| 82 | +- **Shader compile latency**: 10–25 s on first session load, then the browser WebGPU cache kicks in |
| 83 | +- **Firefox ~30–50 % slower** than Chrome (wgpu vs Dawn + SQLite-backed OPFS). Feature, not bug. |
| 84 | +- **Stop-token leak**: Gemma sometimes emits `<end_of_turn>` as a literal string. The regex in `mediapipe-llm.ts#GEMMA_STOP_PATTERN` + `cancelProcessing()` catch it. |
| 85 | +- **Avoid double BOS**: MediaPipe's tokenizer prepends BOS automatically — don't add one manually in `renderGemmaPrompt`. |
| 86 | +- **Two SWs collide**: never register multiple service workers for the same scope. All SW logic lives in `src/sw.ts`. |
0 commit comments