Skip to content
Source
Sign inSign up
Open History to see the last commit

speech.test.ts

152 lines4.9 KB

1import { describe, expect, it } from "vite-plus/test";
2import { DEFAULT_SPEECH_TRANSCRIPTION_OPTIONS, SPEECH_MAX_OPTIONS_BYTES } from "@t3tools/contracts";
3​
4import {
5 chooseSetupSpeechModel,
6 decodeSpeechPcmRequest,
7 encodeSpeechPcmRequest,
8 resolveSpeechVocabulary,
9} from "./speech.ts";
10​
11describe("speech PCM request framing", () => {
12 it("round-trips options and unaligned PCM without changing audio bytes", () => {
13 const pcm = new Uint8Array(new Float32Array([0.25, -0.5]).buffer);
14 const options = { ...DEFAULT_SPEECH_TRANSCRIPTION_OPTIONS, speechLanguage: "fr" };
15 const body = encodeSpeechPcmRequest(pcm, options);
16 const pool = new Uint8Array(body.length + 3);
17 pool.set(body, 3);
18 expect(decodeSpeechPcmRequest(pool.subarray(3))).toEqual({ pcm, options });
19 });
20​
21 it("accepts recordings from clients that predate silence filtering", () => {
22 const metadata = new TextEncoder().encode(
23 JSON.stringify({
24 speechLanguage: "fr",
25 speechTranslateToEnglish: false,
26 speechCustomWords: [],
27 speechRemoveFillerWords: false,
28 }),
29 );
30 const body = new Uint8Array(4 + metadata.length + 4);
31 new DataView(body.buffer).setUint32(0, metadata.length, true);
32 body.set(metadata, 4);
33 expect(decodeSpeechPcmRequest(body).options).toEqual({
34 ...DEFAULT_SPEECH_TRANSCRIPTION_OPTIONS,
35 speechLanguage: "fr",
36 speechRemoveFillerWords: false,
37 speechSilenceFiltering: true,
38 });
39 });
40​
41 it("rejects truncated frames, oversized option lengths and missing audio", () => {
42 expect(() => decodeSpeechPcmRequest(new Uint8Array(3))).toThrow();
43 const oversized = new Uint8Array(5);
44 new DataView(oversized.buffer).setUint32(0, SPEECH_MAX_OPTIONS_BYTES + 1, true);
45 expect(() => decodeSpeechPcmRequest(oversized)).toThrow();
46 expect(() =>
47 decodeSpeechPcmRequest(
48 encodeSpeechPcmRequest(new Uint8Array(), DEFAULT_SPEECH_TRANSCRIPTION_OPTIONS),
49 ),
50 ).toThrow();
51 });
52});
53​
54describe("resolveSpeechVocabulary", () => {
55 it("prefers project spellings, keeps hints under the cap, and drops duplicates", () => {
56 const personal = Array.from({ length: 100 }, (_, index) => ({
57 term: `word${index}`,
58 aliases: index === 0 ? ["Effect"] : [],
59 }));
60 const resolved = resolveSpeechVocabulary({
61 personal,
62 project: [{ term: "effect", aliases: ["a fact"] }],
63 hints: [" T3 Code ", "word1", "", "x".repeat(51)],
64 });
65 expect(resolved).toHaveLength(100);
66 expect(resolved.slice(0, 3)).toEqual([
67 { term: "T3 Code", aliases: [] },
68 { term: "effect", aliases: ["a fact"] },
69 { term: "word0", aliases: [] },
70 ]);
71 expect(resolved.filter(({ term }) => term === "word1")).toHaveLength(1);
72 expect(resolved.at(-1)?.term).toBe("word97");
73 });
74});
75​
76describe("chooseSetupSpeechModel", () => {
77 const model = (
78 id: string,
79 languages: string[],
80 supportsLanguageDetection: boolean,
81 recommended: boolean,
82 ) => ({ id, languages, supportsLanguageDetection, recommended });
83 const catalog = [
84 model("unrecommended", ["en", "de", "es"], true, false),
85 model("english", ["en"], false, true),
86 model("fixed", ["en", "de"], false, true),
87 model("detecting", ["en", "de", "fr"], true, true),
88 ];
89​
90 it.each([
91 [
92 "keeps the selected English model for an English speaker",
93 "english",
94 "auto",
95 ["en"],
96 { id: "english", language: "en" },
97 ],
98 [
99 "skips models that would transcribe English instead of German",
100 "english",
101 "auto",
102 ["de", "en"],
103 { id: "detecting", language: "de" },
104 ],
105 [
106 "accepts a model without detection for a chosen language",
107 "english",
108 "de",
109 ["en"],
110 { id: "fixed", language: "de" },
111 ],
112 [
113 "keeps a selected model that transcribes the language",
114 "unrecommended",
115 "auto",
116 ["de"],
117 { id: "unrecommended", language: "de" },
118 ],
119 [
120 "moves on to the next preferred language a model transcribes",
121 "english",
122 "auto",
123 ["ja", "fr"],
124 { id: "detecting", language: "fr" },
125 ],
126 [
127 "falls back to a model that is not recommended",
128 "english",
129 "auto",
130 ["es"],
131 { id: "unrecommended", language: "es" },
132 ],
133 ["offers nothing when no model transcribes the language", "english", "auto", ["ja"], undefined],
134 [
135 "offers the selected model without a language recommendation when Auto has no preferences",
136 "fixed",
137 "auto",
138 [],
139 { id: "fixed", language: null },
140 ],
141 [
142 "does not guess another model when Auto has no preferences or selection",
143 "gone",
144 "auto",
145 [],
146 undefined,
147 ],
148 ] as const)("%s", (_, selectedModelId, intent, preferred, expected) => {
149 const choice = chooseSetupSpeechModel(catalog, { selectedModelId, intent, preferred });
150 expect(choice && { id: choice.model.id, language: choice.language }).toEqual(expected);
151 });
152});