Merge feat/vocabulary-hint: names-and-terms decoder prompt (spec 001 workstream 2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> # Conflicts: # docs/Architecture.md # docs/Decisions.md # docs/WorkBreakdown.md # server-py/sidecar.py # src/App.tsx # src/server/live.ts # src/server/transcribe.ts # tests/fixtures/fake_sidecar.py # tests/transcribe.test.ts
This commit is contained in:
commit
2c87471fdc
26 files changed
+1177
-21
No files matched your search
@@ -26,6 +26,7 @@
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { TranscribeStreamLine } from "../lib/engine";
|
||||
import { HINT_MAX_CHARS, normalizeHint } from "../lib/hint";
|
||||
import { getFasterWhisperModel } from "../lib/models";
|
||||
import type { TranscriptChunk, WhisperTask } from "../worker/protocol";
|
||||
import { getSessionUser as appGetSessionUser, type SessionUser } from "./auth.ts";
|
||||
@@ -52,6 +53,11 @@ export interface SidecarTranscribeRequest {
|
||||
* chunks. "live": short server-live passes — sequential, no VAD (D-WS5a).
|
||||
*/
|
||||
mode?: SidecarMode;
|
||||
/**
|
||||
* Normalized "Names and terms" hint (D-WS2a); the sidecar truncates it to
|
||||
* Whisper's prompt budget with faster-whisper's own tokenizer.
|
||||
*/
|
||||
prompt?: string;
|
||||
}
|
||||
|
||||
export type SidecarMode = "file" | "live";
|
||||
@@ -273,6 +279,7 @@ export function createSidecar(command?: readonly string[]): Sidecar {
|
||||
language: request.language ?? null,
|
||||
task: request.task ?? "transcribe",
|
||||
mode: request.mode ?? "file",
|
||||
prompt: request.prompt ?? null,
|
||||
});
|
||||
} catch (error) {
|
||||
pending.delete(id);
|
||||
@@ -332,18 +339,23 @@ interface TranscribeParams {
|
||||
fasterWhisperModel: string;
|
||||
language?: string;
|
||||
task: WhisperTask;
|
||||
prompt?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse + validate the `language`/`task` query params shared by both the
|
||||
* HTTP (`POST /api/transcribe`) and WebSocket (`GET /api/transcribe/live`,
|
||||
* Parse + validate the `language`/`task`/`prompt` query params shared by both
|
||||
* the HTTP (`POST /api/transcribe`) and WebSocket (`GET /api/transcribe/live`,
|
||||
* src/server/live.ts) entry points, so a live session can't forward an
|
||||
* unvalidated string into the sidecar protocol just because it skips
|
||||
* `parseParams` below. A string return is the 400 error text.
|
||||
* `parseParams` below (D-032). A string return is the 400 error text.
|
||||
*
|
||||
* `prompt` is the raw "Names and terms" hint (D-WS2a): at most
|
||||
* HINT_MAX_CHARS (the UI's textarea limit), normalized exactly as the browser
|
||||
* worker does; empty after normalization = no prompt.
|
||||
*/
|
||||
export function parseLanguageAndTask(
|
||||
url: URL,
|
||||
): { language?: string; task: WhisperTask } | string {
|
||||
): { language?: string; task: WhisperTask; prompt?: string } | string {
|
||||
const rawLanguage = url.searchParams.get("language");
|
||||
let language: string | undefined;
|
||||
if (rawLanguage !== null && rawLanguage !== "" && rawLanguage !== "auto") {
|
||||
@@ -356,7 +368,13 @@ export function parseLanguageAndTask(
|
||||
return `invalid task: ${rawTask}`;
|
||||
}
|
||||
|
||||
return { language, task: rawTask };
|
||||
const rawPrompt = url.searchParams.get("prompt") ?? "";
|
||||
if (rawPrompt.length > HINT_MAX_CHARS) {
|
||||
return `prompt too long (max ${HINT_MAX_CHARS} characters)`;
|
||||
}
|
||||
const prompt = normalizeHint(rawPrompt);
|
||||
|
||||
return { language, task: rawTask, ...(prompt && { prompt }) };
|
||||
}
|
||||
|
||||
/** Parse + validate query params; a string is the 400 error text. */
|
||||
@@ -472,6 +490,7 @@ export function createTranscribeHandlers(deps: TranscribeDeps): TranscribeHandle
|
||||
audioPath,
|
||||
language: params.language,
|
||||
task: params.task,
|
||||
prompt: params.prompt,
|
||||
},
|
||||
(segment) => {
|
||||
chunks.push({
|
||||
|
||||
Reference in new issue
Block a user