diff --git a/docs/plans/001-2-vocabulary-hint.md b/docs/plans/001-2-vocabulary-hint.md index 74cd37d..9805c05 100644 --- a/docs/plans/001-2-vocabulary-hint.md +++ b/docs/plans/001-2-vocabulary-hint.md @@ -81,16 +81,16 @@ sidecar applies the same truncation rule (Python twin, parity-tested) and passes - `fitHintToBudget(hint: string, encode: (text: string) => number[], budget = PROMPT_TOKEN_BUDGET): number[]` (normalizes internally; `[]` for empty) -- [ ] Step 1: failing tests — `normalizeHint(" Maxwell\n\n Voices of the Void \n")` === +- [x] Step 1: failing tests — `normalizeHint(" Maxwell\n\n Voices of the Void \n")` === `"Maxwell, Voices of the Void"`; control chars removed; `"a, b"` unchanged; whitespace-only → `""`. `loadHint`/`saveHint` round-trip with a Map-backed storage, throwing storage → `""`/no throw. `fitHintToBudget` with fake `encode = (t) => [...t].map(c => c.charCodeAt(0))`: short hint → encode(" Maxwell, Voices of the Void"); budget 10 on `"alpha, beta gamma"` → ids of `" alpha"` (trailing comma stripped, whole words only); single 50-char word with budget 10 → first 10 ids; 5000-word hint with budget 223 → length ≤ 223 and decodes to a word prefix of the hint. -- [ ] Step 2: `bun test tests/hint.test.ts` → FAIL (module missing). -- [ ] Step 3: implement; binary search over word count (encode is monotone in prefix length). -- [ ] Step 4: tests PASS. Commit. +- [x] Step 2: `bun test tests/hint.test.ts` → FAIL (module missing). +- [x] Step 3: implement; binary search over word count (encode is monotone in prefix length). +- [x] Step 4: tests PASS. Commit. ### Task 2: `src/worker/prompt.ts` — decoder prompt through the pipeline @@ -106,14 +106,14 @@ sidecar applies the same truncation rule (Python twin, parity-tested) and passes min(rest.max_new_tokens ?? 440, 448 − decoder_input_ids.length)`, and returns the output Tensor sliced `[:, prefix.length:]`. -- [ ] Step 1: failing tests with a fake model (records kwargs, returns a real `Tensor` int64 of +- [x] Step 1: failing tests with a fake model (records kwargs, returns a real `Tensor` int64 of `[decoder_input_ids..., generated...]`): no kwarg → kwargs passed through untouched and no `decoder_input_ids`; with prefix `[50362, 1, 2]` and init `[50258, 50259, 50360]` → decoder_input_ids equal concatenation, private kwarg absent, output tokens start at 50258; `.en`-style init `[50257]` works; 224-token prefix + 440 requested → max_new_tokens = 448 − 227; installing twice wraps once. `buildPromptPrefix` with a fake tokenizer: `""` → `[]`; `"Maxwell"` → `[prevId, ...encode(" Maxwell")]`. -- [ ] Step 2: FAIL. Step 3: implement. Step 4: PASS. Commit. +- [x] Step 2: FAIL. Step 3: implement. Step 4: PASS. Commit. ### Task 3: protocol + worker + engine + live plumbing @@ -124,10 +124,10 @@ Tests `tests/protocol.test.ts`, `tests/engine.test.ts` string`; BrowserEngine forwards non-empty `prompt`; ServerEngine adds `prompt` query param when non-empty. -- [ ] Step 1: failing tests — guard accepts string `prompt`, rejects `prompt: 5`; BrowserEngine +- [x] Step 1: failing tests — guard accepts string `prompt`, rejects `prompt: 5`; BrowserEngine posts `prompt` (and omits it when `""`/undefined); ServerEngine URL carries `prompt=Maxwell%2C+Voices…` (URLSearchParams encoding) and none when empty. -- [ ] Step 2: FAIL. Step 3: implement; worker: `installPromptSupport(asr.model)` once after +- [x] Step 2: FAIL. Step 3: implement; worker: `installPromptSupport(asr.model)` once after pipeline creation (and lazily in transcribe), `options[PROMPT_PREFIX_KWARG] = buildPromptPrefix(asr.tokenizer, message.prompt ?? "")` when non-empty; cite D-WS2a. Step 4: PASS; typecheck. Commit. @@ -144,14 +144,14 @@ characters)"`); `SidecarTranscribeRequest.prompt?: string`; sidecar JSON `"promp `LiveSessionData.prompt?: string`; Python `fit_prompt_ids(hint, encode, budget=223) -> list[int]` (+ `normalize_hint`). -- [ ] Step 1: failing tests — parse: `?prompt=Maxwell%0AVoid` → `"Maxwell, Void"`; empty/whitespace +- [x] Step 1: failing tests — parse: `?prompt=Maxwell%0AVoid` → `"Maxwell, Void"`; empty/whitespace → no prompt; 1001 chars → error string; POST with `prompt=Århus, Ærø` reaches sidecar request as that string (Review Focus 4); live session passes `prompt` to the sidecar; createSidecar pass-through via fake sidecar echo (`prompt=` appended only when present); parity: Python `fit_prompt_ids` == TS `fitHintToBudget` with the char-code fake encoder over a table of cases (spawn `python3 -I -c` importing `server-py/sidecar.py`, which imports faster-whisper only in `main()`). -- [ ] Step 2: FAIL. Step 3: implement; sidecar encodes with +- [x] Step 2: FAIL. Step 3: implement; sidecar encodes with `cached_model.hf_tokenizer.encode(text, add_special_tokens=False).ids`, passes `initial_prompt=ids` (hotwords decided in Task 6). Step 4: PASS. Commit. @@ -160,21 +160,21 @@ characters)"`); `SidecarTranscribeRequest.prompt?: string`; sidecar JSON `"promp **Files:** Create `src/components/VocabularyHint.tsx`; Modify `src/App.tsx`, `src/index.css`, `DESIGN.md` -- [ ] Step 1: render below the language/task options for **every** model: label "Names and terms +- [x] Step 1: render below the language/task options for **every** model: label "Names and terms (optional)", 2-row textarea (`maxLength={HINT_MAX_CHARS}`), placeholder with an example, one line of help (what it does, that it is remembered in this browser, long lists are cut at the end). Disabled while busy/live. State initialized from `loadHint()`, saved on change. -- [ ] Step 2: pass `prompt: hint` in the file run, `transcribeLiveSegment` options, and the server +- [x] Step 2: pass `prompt: hint` in the file run, `transcribeLiveSegment` options, and the server live WS URL (`prompt` param). Typecheck + fast suite. -- [ ] Step 3: impeccable polish/harden on the component; tokens only; DESIGN.md component line. -- [ ] Step 4: build, serve `PORT=3120 NODE_ENV=production bun src/index.ts`, headless Chrome +- [x] Step 3: impeccable polish/harden on the component; tokens only; DESIGN.md component line. +- [x] Step 4: build, serve `PORT=3120 NODE_ENV=production bun src/index.ts`, headless Chrome screenshots light/dark × 390 px/1280 px; fix what looks wrong. Commit. ### Task 6: measurements (under the lock) → D-WS2a Scratch scripts in `/tmp/ws2/` (not committed), importing the worktree's modules. -- [ ] Browser path (onnxruntime-node, catalog dtypes, `wasm` defaults): en_alien whole file, turbo +- [x] Browser path (onnxruntime-node, catalog dtypes, `wasm` defaults): en_alien whole file, turbo q4 and one small model (base.en or tiny.en, downloaded under the lock), without hint vs hint `"Maxwell, Voices of the Void"`: WER (test normalizer), count of `maxwell` and `voices of the void` vs reference counts, wall-clock, punctuation count (a prompt without a final full stop may @@ -204,3 +204,13 @@ Scratch scripts in `/tmp/ws2/` (not committed), importing the worktree's modules - [ ] `bun run typecheck`, fast suite, `bun run build` green; `git status` clean. - [ ] `git rm docs/plans/001-2-vocabulary-hint.md`, commit last. + +## Resume notes (after the pause) + +- Tasks 1–5 done in commits 942799d…f7d0525 (UI screenshots taken at 3120 before the pause). +- Task 6: browser-path runs done (`/tmp/ws2/run2.jsonl`, `run3.jsonl`, out-*.txt); they showed a + near-full-budget prompt (≈220 tokens) hurting base.en badly (WER 0.339 → 0.476; a 300-term + synthetic list → 1.05), so the budget is being re-measured before D-WS2a is written. + faster-whisper none/initial/hotwords/both measured (`/tmp/ws2/fw.jsonl`); outcome not yet applied. +- Task 7: tests written (WIP commit 826bcd2); the three hint tests passed once under the lock; the + full file (whole-file gate) not yet run.