@burdenoff/fe-libs 2026.903.3 → 2026.903.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/shared/assistant/index.d.ts +2 -0
- package/dist/shared/assistant/index.d.ts.map +1 -1
- package/dist/shared/assistant/speech/browserSpeechEngine.d.ts +3 -0
- package/dist/shared/assistant/speech/browserSpeechEngine.d.ts.map +1 -0
- package/dist/shared/assistant/speech/browserSpeechEngine.js +89 -0
- package/dist/shared/assistant/speech/createSpeechEngine.d.ts +50 -0
- package/dist/shared/assistant/speech/createSpeechEngine.d.ts.map +1 -0
- package/dist/shared/assistant/speech/createSpeechEngine.js +31 -0
- package/dist/shared/assistant/speech/hostedTtsProtocol.d.ts +118 -0
- package/dist/shared/assistant/speech/hostedTtsProtocol.d.ts.map +1 -0
- package/dist/shared/assistant/speech/hostedTtsProtocol.js +42 -0
- package/dist/shared/assistant/speech/hostedTtsSpeechEngine.d.ts +10 -0
- package/dist/shared/assistant/speech/hostedTtsSpeechEngine.d.ts.map +1 -0
- package/dist/shared/assistant/speech/hostedTtsSpeechEngine.js +121 -0
- package/dist/shared/assistant/speech/hostedVoiceCatalogue.d.ts +35 -0
- package/dist/shared/assistant/speech/hostedVoiceCatalogue.d.ts.map +1 -0
- package/dist/shared/assistant/speech/hostedVoiceCatalogue.js +53 -0
- package/dist/shared/assistant/speech/index.d.ts +8 -0
- package/dist/shared/assistant/speech/index.d.ts.map +1 -0
- package/dist/shared/assistant/speech/routingSpeechEngine.d.ts +8 -0
- package/dist/shared/assistant/speech/routingSpeechEngine.d.ts.map +1 -0
- package/dist/shared/assistant/speech/routingSpeechEngine.js +1 -0
- package/dist/shared/assistant/speech/types.d.ts +65 -0
- package/dist/shared/assistant/speech/types.d.ts.map +1 -0
- package/dist/shared-assistant.js +2 -1
- package/package.json +1 -1
|
@@ -19,4 +19,6 @@ export type { UseVoiceInputOptions, UseVoiceInputResult, VoiceInputError } from
|
|
|
19
19
|
export { toCaptionText } from './voice/captionText';
|
|
20
20
|
export { parseConfirmationRefusal, findPendingWrite } from './pendingWrite';
|
|
21
21
|
export type { PendingWrite, PendingWriteValue } from './pendingWrite';
|
|
22
|
+
export { createSpeechEngines } from './speech/createSpeechEngine';
|
|
23
|
+
export type { SpeechEngine, SpeechSpeakOptions, SpeechSpeakHandlers, SpeechErrorReason } from './speech/types';
|
|
22
24
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,QAAQ,EACR,aAAa,EACb,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAKpB,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,4BAA4B,EAC5B,sBAAsB,EACtB,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,EACzB,aAAa,EACb,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,OAAO,CAAC;AAEf,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,WAAW,EACX,sBAAsB,EACtB,yBAAyB,EACzB,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,iBAAiB,EACjB,2BAA2B,EAC3B,oBAAoB,EACpB,2BAA2B,EAC3B,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,cAAc,EACd,cAAc,EACd,yBAAyB,GAC1B,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxG,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,QAAQ,EACR,aAAa,EACb,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAKpB,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,4BAA4B,EAC5B,sBAAsB,EACtB,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,EACzB,aAAa,EACb,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,OAAO,CAAC;AAEf,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,WAAW,EACX,sBAAsB,EACtB,yBAAyB,EACzB,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,iBAAiB,EACjB,2BAA2B,EAC3B,oBAAoB,EACpB,2BAA2B,EAC3B,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,cAAc,EACd,cAAc,EACd,yBAAyB,GAC1B,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxG,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browserSpeechEngine.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/speech/browserSpeechEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EACV,YAAY,EAKb,MAAM,SAAS,CAAC;AAsCjB,eAAO,MAAM,mBAAmB,EAAE,YAsGjC,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
//#region src/shared/assistant/speech/browserSpeechEngine.ts
|
|
2
|
+
function e() {
|
|
3
|
+
if (typeof window > "u") return null;
|
|
4
|
+
try {
|
|
5
|
+
return window.speechSynthesis ?? null;
|
|
6
|
+
} catch {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function t() {
|
|
11
|
+
return typeof window < "u" && typeof window.SpeechSynthesisUtterance == "function";
|
|
12
|
+
}
|
|
13
|
+
function n(e) {
|
|
14
|
+
switch (e) {
|
|
15
|
+
case "canceled":
|
|
16
|
+
case "cancelled":
|
|
17
|
+
case "interrupted": return "cancelled";
|
|
18
|
+
case "not-allowed": return "not-allowed";
|
|
19
|
+
case "synthesis-unavailable":
|
|
20
|
+
case "language-unavailable":
|
|
21
|
+
case "voice-unavailable": return "unsupported";
|
|
22
|
+
default: return "unknown";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
var r = 0, i = {
|
|
26
|
+
id: "browser-speech-synthesis",
|
|
27
|
+
isSupported() {
|
|
28
|
+
return e() !== null && t();
|
|
29
|
+
},
|
|
30
|
+
listVoices() {
|
|
31
|
+
try {
|
|
32
|
+
return e()?.getVoices() ?? [];
|
|
33
|
+
} catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
subscribeVoices(t) {
|
|
38
|
+
let n = e();
|
|
39
|
+
if (!n?.addEventListener) return () => {};
|
|
40
|
+
let r = () => t();
|
|
41
|
+
return n.addEventListener("voiceschanged", r), () => n.removeEventListener("voiceschanged", r);
|
|
42
|
+
},
|
|
43
|
+
speak(i, a, o = {}) {
|
|
44
|
+
let s = e();
|
|
45
|
+
if (!s || !t()) {
|
|
46
|
+
o.onError?.("unsupported");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
let c = i.filter((e) => e.trim().length > 0);
|
|
50
|
+
if (c.length === 0) {
|
|
51
|
+
o.onEnd?.();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
r += 1;
|
|
55
|
+
let l = r;
|
|
56
|
+
try {
|
|
57
|
+
s.cancel();
|
|
58
|
+
} catch {}
|
|
59
|
+
c.forEach((e, t) => {
|
|
60
|
+
let i = new window.SpeechSynthesisUtterance(e);
|
|
61
|
+
i.lang = a.lang, a.voice && (i.voice = a.voice), a.rate !== void 0 && (i.rate = a.rate), i.onstart = () => {
|
|
62
|
+
l === r && o.onChunkStart?.(t);
|
|
63
|
+
}, i.onerror = (e) => {
|
|
64
|
+
if (l === r) {
|
|
65
|
+
r += 1;
|
|
66
|
+
try {
|
|
67
|
+
s.cancel();
|
|
68
|
+
} catch {}
|
|
69
|
+
o.onError?.(n(e.error));
|
|
70
|
+
}
|
|
71
|
+
}, t === c.length - 1 && (i.onend = () => {
|
|
72
|
+
l === r && o.onEnd?.();
|
|
73
|
+
});
|
|
74
|
+
try {
|
|
75
|
+
s.speak(i);
|
|
76
|
+
} catch {
|
|
77
|
+
l === r && o.onError?.("unknown");
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
cancel() {
|
|
82
|
+
r += 1;
|
|
83
|
+
try {
|
|
84
|
+
e()?.cancel();
|
|
85
|
+
} catch {}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
//#endregion
|
|
89
|
+
export { i as browserSpeechEngine };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { SpeechEngine } from './types';
|
|
2
|
+
export interface SpeechEngineConfig {
|
|
3
|
+
/** Our proxy's URL. Empty/undefined keeps the device engine. */
|
|
4
|
+
endpoint?: string | null;
|
|
5
|
+
getHeaders?: () => Record<string, string>;
|
|
6
|
+
fetchImpl?: typeof fetch;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Point the assistant at a hosted TTS proxy.
|
|
10
|
+
*
|
|
11
|
+
* Called by the host shell's provider, exactly as `configureGeocoder` is. This
|
|
12
|
+
* is the supported way to turn hosted speech on; the environment variable below
|
|
13
|
+
* only ever works when this package is run directly (dev / standalone).
|
|
14
|
+
*
|
|
15
|
+
* ★ `getHeaders` is not optional in practice, only in the type. The proxy turns
|
|
16
|
+
* text into audio the org is BILLED for, so an endpoint that accepts anonymous
|
|
17
|
+
* POSTs is a metered API open to anyone who reads the bundle. The engine has
|
|
18
|
+
* always supported per-request headers; until now nothing could reach that hook
|
|
19
|
+
* from the host, which meant the only configuration this function could express
|
|
20
|
+
* was an unauthenticated one. It is a function rather than a value because
|
|
21
|
+
* access tokens expire and the engine outlives any one of them — reading a
|
|
22
|
+
* token once at configure time would work until it rotated and then fail
|
|
23
|
+
* exactly where it is hardest to notice, mid-reply.
|
|
24
|
+
*/
|
|
25
|
+
export declare function configureSpeechEngine(options: {
|
|
26
|
+
ttsEndpoint?: string | null;
|
|
27
|
+
getHeaders?: () => Record<string, string>;
|
|
28
|
+
}): void;
|
|
29
|
+
/**
|
|
30
|
+
* Read the rollout config.
|
|
31
|
+
*
|
|
32
|
+
* Order matters: explicit runtime configuration, then a global the host can set
|
|
33
|
+
* before the bundle loads, and only then the build-time variable — which is
|
|
34
|
+
* eliminated in the published package and therefore useful for local dev only.
|
|
35
|
+
*/
|
|
36
|
+
export declare function readSpeechEngineConfig(): SpeechEngineConfig;
|
|
37
|
+
/**
|
|
38
|
+
* The engine to use, or the device engine when hosted TTS is not configured.
|
|
39
|
+
*
|
|
40
|
+
* Returns `null` for `hosted` when off, so callers can tell "hosted is not
|
|
41
|
+
* configured" from "hosted is configured but cannot speak this language".
|
|
42
|
+
*/
|
|
43
|
+
export declare function createSpeechEngines(config?: SpeechEngineConfig): {
|
|
44
|
+
device: SpeechEngine;
|
|
45
|
+
hosted: SpeechEngine | null;
|
|
46
|
+
};
|
|
47
|
+
export declare function getSpeechEngine(): SpeechEngine;
|
|
48
|
+
/** Test seam: drop the singleton so the next call re-reads the config. */
|
|
49
|
+
export declare function resetSpeechEngine(): void;
|
|
50
|
+
//# sourceMappingURL=createSpeechEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createSpeechEngine.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/speech/createSpeechEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,MAAM,WAAW,kBAAkB;IACjC,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE;IAC7C,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C,GAAG,IAAI,CAmBP;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,kBAAkB,CAY3D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,GAAE,kBAA6C,GAAG;IAC1F,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;CAC7B,CASA;AAeD,wBAAgB,eAAe,IAAI,YAAY,CAG9C;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { browserSpeechEngine as e } from "./browserSpeechEngine.js";
|
|
2
|
+
import { createHostedTtsEngine as t } from "./hostedTtsSpeechEngine.js";
|
|
3
|
+
import "./routingSpeechEngine.js";
|
|
4
|
+
//#region src/shared/assistant/speech/createSpeechEngine.ts
|
|
5
|
+
var n, r;
|
|
6
|
+
function i() {
|
|
7
|
+
if (n) return {
|
|
8
|
+
endpoint: n,
|
|
9
|
+
getHeaders: r
|
|
10
|
+
};
|
|
11
|
+
if (typeof window < "u") {
|
|
12
|
+
let e = window.__HB_TTS_ENDPOINT__;
|
|
13
|
+
if (e) return {
|
|
14
|
+
endpoint: e,
|
|
15
|
+
getHeaders: r
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return { endpoint: (void 0)?.trim() || null };
|
|
19
|
+
}
|
|
20
|
+
function a(n = i()) {
|
|
21
|
+
return {
|
|
22
|
+
device: e,
|
|
23
|
+
hosted: n.endpoint ? t({
|
|
24
|
+
endpoint: n.endpoint,
|
|
25
|
+
getHeaders: n.getHeaders,
|
|
26
|
+
fetchImpl: n.fetchImpl
|
|
27
|
+
}) : null
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { a as createSpeechEngines };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { SpeechErrorReason } from './types';
|
|
2
|
+
/** What the client sends per chunk. */
|
|
3
|
+
export interface HostedTtsRequest {
|
|
4
|
+
text: string;
|
|
5
|
+
/** BCP-47, e.g. `ta-IN`. The vendor needs the region, not a bare subtag. */
|
|
6
|
+
languageCode: string;
|
|
7
|
+
/** Vendor voice id, e.g. `ta-IN-Chirp3-HD-Kore`. Omitted = server default. */
|
|
8
|
+
voiceName?: string;
|
|
9
|
+
/** Vendor voice tier, so the backend need not infer it from the name. */
|
|
10
|
+
tier: string;
|
|
11
|
+
/**
|
|
12
|
+
* The same knob the device engine calls `rate`.
|
|
13
|
+
*
|
|
14
|
+
* ★ The accepted range depends on which vendor ENDPOINT the proxy calls, not
|
|
15
|
+
* on the voice tier — see `SPEAKING_RATE_MAX_SYNC` below. Values in use are
|
|
16
|
+
* 0.9, and 0.85 for Indic scripts and Arabic, comfortably inside every
|
|
17
|
+
* variant, so this has never affected playback.
|
|
18
|
+
*/
|
|
19
|
+
speakingRate?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Google's synthesize response is `{ audioContent: <base64 mp3> }`. We accept
|
|
23
|
+
* that shape directly so the proxy can be a thin pass-through, but the proxy is
|
|
24
|
+
* also free to return raw audio bytes — see `decodeAudioResponse`.
|
|
25
|
+
*/
|
|
26
|
+
export interface HostedTtsJsonResponse {
|
|
27
|
+
audioContent?: string;
|
|
28
|
+
error?: {
|
|
29
|
+
message?: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The accepted speaking-rate bounds, for any caller that clamps.
|
|
34
|
+
*
|
|
35
|
+
* ★ The ceiling is a property of the ENDPOINT, not of the voice tier, and the
|
|
36
|
+
* vendor's own rejection message says both numbers out loud:
|
|
37
|
+
*
|
|
38
|
+
* "Unable to adjust speaking rate. Please ensure that speaking_rate is in the
|
|
39
|
+
* range [0.25, 4.0] for sync synthesis and in the range [0.25, 2.0] for
|
|
40
|
+
* streaming synthesis."
|
|
41
|
+
*
|
|
42
|
+
* Measured against the live API on 2026-08-31 with `en-IN-Chirp3-HD-Puck` and a
|
|
43
|
+
* fixed sentence, reading the returned LINEAR16 duration rather than trusting
|
|
44
|
+
* the HTTP status — 4.0 synthesises and is genuinely ~4x faster (21.93s at 0.25
|
|
45
|
+
* down to 1.37s at 4.0); 4.1, 5.0 and 0.2 are all rejected with the message
|
|
46
|
+
* above.
|
|
47
|
+
*
|
|
48
|
+
* This corrects a previous edit of this file which narrowed the ceiling to 2.0
|
|
49
|
+
* and attributed 4.0 to "the older Standard/WaveNet tiers". That reading was
|
|
50
|
+
* wrong: 2.0 is the STREAMING limit, and the tier has nothing to do with it.
|
|
51
|
+
*
|
|
52
|
+
* `SPEAKING_RATE_MAX` is the sync ceiling because the proxy calls
|
|
53
|
+
* `v1/text:synthesize`. Moving it to `v1/text:streamingSynthesize` — tempting,
|
|
54
|
+
* since that is how you cut time-to-first-audio — halves the ceiling, and rates
|
|
55
|
+
* the client had treated as valid would start returning 400. That switch has to
|
|
56
|
+
* change this alias in the same commit, which is why both bounds are named
|
|
57
|
+
* rather than one being a magic number.
|
|
58
|
+
*/
|
|
59
|
+
export declare const SPEAKING_RATE_MIN = 0.25;
|
|
60
|
+
export declare const SPEAKING_RATE_MAX_SYNC = 4;
|
|
61
|
+
export declare const SPEAKING_RATE_MAX_STREAMING = 2;
|
|
62
|
+
export declare const SPEAKING_RATE_MAX = 4;
|
|
63
|
+
/**
|
|
64
|
+
* Should a failure stop the engine claiming support for the rest of the page?
|
|
65
|
+
*
|
|
66
|
+
* ★ `not-allowed` has TWO producers and only one of them is terminal:
|
|
67
|
+
*
|
|
68
|
+
* - `classifyHttpFailure` maps HTTP 401/403 to it — the proxy refuses us, and
|
|
69
|
+
* every later request will be refused identically. Latching is right: it
|
|
70
|
+
* makes `routingSpeechEngine` fall back to device voices instead of
|
|
71
|
+
* repeating a request that cannot succeed.
|
|
72
|
+
* - the engine maps the browser's `NotAllowedError` to it — the autoplay
|
|
73
|
+
* policy declining to play audio that was not started by a user gesture.
|
|
74
|
+
* That is PER-GESTURE and entirely transient; the very next tap works.
|
|
75
|
+
*
|
|
76
|
+
* Latching on the second silently deleted the hosted voice for the whole page
|
|
77
|
+
* after a single refusal — and auto-speak is precisely the path that triggers
|
|
78
|
+
* it, because an arriving reply is not a gesture. The user would hear Chirp 3
|
|
79
|
+
* once, be refused once, and get device voices for the rest of the session with
|
|
80
|
+
* nothing explaining why.
|
|
81
|
+
*
|
|
82
|
+
* Pure and exported because the engine itself cannot be unit-tested here (no
|
|
83
|
+
* DOM, no Audio), so the decision is kept where it CAN be.
|
|
84
|
+
*/
|
|
85
|
+
export declare function shouldDisableEngine(reason: string | undefined, transient: boolean): boolean;
|
|
86
|
+
/** Vendor limit is 5000 bytes per request; stay well inside it. */
|
|
87
|
+
export declare const HOSTED_TTS_MAX_CHARS = 2000;
|
|
88
|
+
export declare function buildRequest(text: string, languageCode: string, tier: string, voiceName?: string | null, speakingRate?: number): HostedTtsRequest;
|
|
89
|
+
/**
|
|
90
|
+
* Map a transport failure onto the reasons the UI already knows how to explain.
|
|
91
|
+
*
|
|
92
|
+
* `cancelled` is deliberately NOT produced here — an aborted request is the
|
|
93
|
+
* caller's own doing and the engine reports it, exactly as the browser engine
|
|
94
|
+
* swallows its own `canceled` utterance errors.
|
|
95
|
+
*/
|
|
96
|
+
export declare function classifyHttpFailure(status: number): SpeechErrorReason;
|
|
97
|
+
/** Base64 → bytes, without assuming a DOM (`atob` is not in node). */
|
|
98
|
+
export declare function base64ToBytes(base64: string): Uint8Array;
|
|
99
|
+
/**
|
|
100
|
+
* Accept either shape the proxy may return: a JSON body carrying base64
|
|
101
|
+
* (the vendor's own shape, so the proxy can pass it straight through) or raw
|
|
102
|
+
* audio bytes.
|
|
103
|
+
*
|
|
104
|
+
* Returns null when the payload carries no audio at all — which must be treated
|
|
105
|
+
* as a failure, not as silence. A zero-byte "success" that plays nothing is the
|
|
106
|
+
* dead-button bug this feature already fixed once.
|
|
107
|
+
*/
|
|
108
|
+
export declare function decodeAudioResponse(contentType: string | null, body: ArrayBuffer | HostedTtsJsonResponse): Uint8Array | null;
|
|
109
|
+
/**
|
|
110
|
+
* Split text the hosted vendor would reject for length.
|
|
111
|
+
*
|
|
112
|
+
* `chunkForSpeech` already splits for the browser engine's ~15s utterance
|
|
113
|
+
* ceiling, and those chunks are normally far under the vendor limit — this is a
|
|
114
|
+
* backstop for a single pathological sentence, and it splits on whitespace so a
|
|
115
|
+
* word is never cut in half.
|
|
116
|
+
*/
|
|
117
|
+
export declare function splitForVendor(text: string, limit?: number): string[];
|
|
118
|
+
//# sourceMappingURL=hostedTtsProtocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hostedTtsProtocol.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/speech/hostedTtsProtocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,uCAAuC;AACvC,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,sBAAsB,IAAM,CAAC;AAC1C,eAAO,MAAM,2BAA2B,IAAM,CAAC;AAC/C,eAAO,MAAM,iBAAiB,IAAyB,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,CAG3F;AAED,mEAAmE;AACnE,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,EACzB,YAAY,CAAC,EAAE,MAAM,GACpB,gBAAgB,CAUlB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAOrE;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAaxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,IAAI,EAAE,WAAW,GAAG,qBAAqB,GACxC,UAAU,GAAG,IAAI,CAWnB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAuB,GAAG,MAAM,EAAE,CAenF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//#region src/shared/assistant/speech/hostedTtsProtocol.ts
|
|
2
|
+
function e(e, t) {
|
|
3
|
+
return t ? !1 : e === "not-allowed" || e === "unsupported";
|
|
4
|
+
}
|
|
5
|
+
var t = 2e3;
|
|
6
|
+
function n(e, t, n, r, i) {
|
|
7
|
+
let a = {
|
|
8
|
+
text: e,
|
|
9
|
+
languageCode: t,
|
|
10
|
+
tier: n
|
|
11
|
+
};
|
|
12
|
+
return r && r.toLowerCase().startsWith(t.toLowerCase()) && (a.voiceName = r), i !== void 0 && (a.speakingRate = i), a;
|
|
13
|
+
}
|
|
14
|
+
function r(e) {
|
|
15
|
+
return e === 401 || e === 403 ? "not-allowed" : e === 404 || e === 501 ? "unsupported" : "unknown";
|
|
16
|
+
}
|
|
17
|
+
function i(e) {
|
|
18
|
+
let t = globalThis.Buffer, n = typeof atob == "function" ? atob(e) : t?.from(e, "base64").toString("binary") ?? "", r = new Uint8Array(n.length);
|
|
19
|
+
for (let e = 0; e < n.length; e += 1) r[e] = n.charCodeAt(e);
|
|
20
|
+
return r;
|
|
21
|
+
}
|
|
22
|
+
function a(e, t) {
|
|
23
|
+
if (t instanceof ArrayBuffer) return t.byteLength > 0 ? new Uint8Array(t) : null;
|
|
24
|
+
if (e?.includes("json") || typeof t == "object") {
|
|
25
|
+
let e = t.audioContent;
|
|
26
|
+
if (!e) return null;
|
|
27
|
+
let n = i(e);
|
|
28
|
+
return n.length > 0 ? n : null;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
function o(e, n = t) {
|
|
33
|
+
if (e.length <= n) return [e];
|
|
34
|
+
let r = [], i = e;
|
|
35
|
+
for (; i.length > n;) {
|
|
36
|
+
let e = i.slice(0, n).lastIndexOf(" "), t = e > n * .5 ? e : n;
|
|
37
|
+
r.push(i.slice(0, t).trim()), i = i.slice(t).trim();
|
|
38
|
+
}
|
|
39
|
+
return i && r.push(i), r.filter((e) => e.length > 0);
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { n as buildRequest, r as classifyHttpFailure, a as decodeAudioResponse, e as shouldDisableEngine, o as splitForVendor };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SpeechEngine } from './types';
|
|
2
|
+
export interface HostedTtsOptions {
|
|
3
|
+
/** Absolute or same-origin path of OUR proxy. Never the vendor directly. */
|
|
4
|
+
endpoint: string;
|
|
5
|
+
/** Extra headers per request (auth). Called fresh each time so a rotated token is picked up. */
|
|
6
|
+
getHeaders?: () => Record<string, string>;
|
|
7
|
+
fetchImpl?: typeof fetch;
|
|
8
|
+
}
|
|
9
|
+
export declare function createHostedTtsEngine(options: HostedTtsOptions): SpeechEngine;
|
|
10
|
+
//# sourceMappingURL=hostedTtsSpeechEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hostedTtsSpeechEngine.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/speech/hostedTtsSpeechEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAUH,OAAO,KAAK,EAAE,YAAY,EAA4D,MAAM,SAAS,CAAC;AAEtG,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,gGAAgG;IAChG,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,CAkP7E"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { HOSTED_TTS_TIER as e, HOSTED_VOICES as t, hostedLocaleFor as n } from "./hostedVoiceCatalogue.js";
|
|
2
|
+
import { buildRequest as r, classifyHttpFailure as i, decodeAudioResponse as a, shouldDisableEngine as o, splitForVendor as s } from "./hostedTtsProtocol.js";
|
|
3
|
+
//#region src/shared/assistant/speech/hostedTtsSpeechEngine.ts
|
|
4
|
+
function c(c) {
|
|
5
|
+
let l = c.fetchImpl ?? ((...e) => fetch(...e)), u = null, d = null, f = 0, p = null, m = !1, h = () => {
|
|
6
|
+
d &&= (URL.revokeObjectURL(d), null);
|
|
7
|
+
}, g = () => {
|
|
8
|
+
if (u) {
|
|
9
|
+
u.onended = null, u.onerror = null;
|
|
10
|
+
try {
|
|
11
|
+
u.pause();
|
|
12
|
+
} catch {}
|
|
13
|
+
u = null;
|
|
14
|
+
}
|
|
15
|
+
h();
|
|
16
|
+
};
|
|
17
|
+
async function _(t, o, s) {
|
|
18
|
+
let u = r(t, n(o.lang) ?? o.lang, e, o.voice?.name ?? null, o.rate), d = await l(c.endpoint, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
signal: s,
|
|
21
|
+
headers: {
|
|
22
|
+
"content-type": "application/json",
|
|
23
|
+
...c.getHeaders?.() ?? {}
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify(u)
|
|
26
|
+
});
|
|
27
|
+
if (!d.ok) {
|
|
28
|
+
let e = /* @__PURE__ */ Error(`hosted tts ${d.status}`);
|
|
29
|
+
throw e.reason = i(d.status), e;
|
|
30
|
+
}
|
|
31
|
+
let f = d.headers.get("content-type"), p = a(f, f?.includes("json") ? await d.json() : await d.arrayBuffer());
|
|
32
|
+
if (!p) {
|
|
33
|
+
let e = /* @__PURE__ */ Error("hosted tts returned no audio");
|
|
34
|
+
throw e.reason = "unknown", e;
|
|
35
|
+
}
|
|
36
|
+
return p;
|
|
37
|
+
}
|
|
38
|
+
function v(e, t, n) {
|
|
39
|
+
return new Promise((r, i) => {
|
|
40
|
+
if (t !== f) {
|
|
41
|
+
r();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let a = new Blob([e.slice().buffer], { type: "audio/mpeg" });
|
|
45
|
+
h(), d = URL.createObjectURL(a);
|
|
46
|
+
let o = new Audio(d);
|
|
47
|
+
u = o, o.onended = () => {
|
|
48
|
+
t === f && r();
|
|
49
|
+
}, o.onerror = () => {
|
|
50
|
+
if (t !== f) {
|
|
51
|
+
r();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
let e = /* @__PURE__ */ Error("audio playback failed");
|
|
55
|
+
e.reason = "unknown", i(e);
|
|
56
|
+
}, o.play().then(() => {
|
|
57
|
+
t === f && n?.();
|
|
58
|
+
}, (e) => {
|
|
59
|
+
if (t !== f) {
|
|
60
|
+
r();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
let n = /* @__PURE__ */ Error("audio play rejected"), a = e?.name === "NotAllowedError";
|
|
64
|
+
n.reason = a ? "not-allowed" : "unknown", n.transient = a, i(n);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
id: "hosted-tts",
|
|
70
|
+
isSupported() {
|
|
71
|
+
return m ? !1 : typeof window < "u" && typeof window.Audio == "function" && typeof fetch == "function";
|
|
72
|
+
},
|
|
73
|
+
listVoices() {
|
|
74
|
+
return [...t];
|
|
75
|
+
},
|
|
76
|
+
subscribeVoices() {
|
|
77
|
+
return () => {};
|
|
78
|
+
},
|
|
79
|
+
speak(e, t, n = {}) {
|
|
80
|
+
let r = e.filter((e) => e.trim().length > 0).flatMap((e) => s(e));
|
|
81
|
+
if (r.length === 0) {
|
|
82
|
+
n.onEnd?.();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
f += 1;
|
|
86
|
+
let i = f;
|
|
87
|
+
g();
|
|
88
|
+
let a = new AbortController();
|
|
89
|
+
p = a;
|
|
90
|
+
let c = !1, l = () => {
|
|
91
|
+
c || (c = !0, n.onStart?.());
|
|
92
|
+
}, u = (e) => {
|
|
93
|
+
let n = _(e, t, a.signal);
|
|
94
|
+
return n.catch(() => {}), n;
|
|
95
|
+
};
|
|
96
|
+
(async () => {
|
|
97
|
+
try {
|
|
98
|
+
let e = u(r[0]);
|
|
99
|
+
for (let t = 0; t < r.length; t += 1) {
|
|
100
|
+
if (i !== f) return;
|
|
101
|
+
let a = await e;
|
|
102
|
+
if (i !== f) return;
|
|
103
|
+
let o = r[t + 1];
|
|
104
|
+
o !== void 0 && (e = u(o)), n.onChunkStart?.(t), await v(a, i, l);
|
|
105
|
+
}
|
|
106
|
+
i === f && n.onEnd?.();
|
|
107
|
+
} catch (e) {
|
|
108
|
+
if (a.abort(), i !== f) return;
|
|
109
|
+
f += 1, g();
|
|
110
|
+
let t = e?.reason;
|
|
111
|
+
o(t, e?.transient === !0) && (m = !0), n.onError?.(t === "not-allowed" || t === "unsupported" || t === "cancelled" ? t : "unknown");
|
|
112
|
+
}
|
|
113
|
+
})();
|
|
114
|
+
},
|
|
115
|
+
cancel() {
|
|
116
|
+
f += 1, p?.abort(), p = null, g();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
export { c as createHostedTtsEngine };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { SpeechVoiceLike } from '../voice/speechVoices';
|
|
2
|
+
/** Vendor tier this catalogue describes; sent to the backend with each request. */
|
|
3
|
+
export declare const HOSTED_TTS_TIER = "chirp3-hd";
|
|
4
|
+
/**
|
|
5
|
+
* Every hosted voice, as `SpeechVoiceLike` so it is interchangeable with a
|
|
6
|
+
* device voice everywhere downstream.
|
|
7
|
+
*
|
|
8
|
+
* The vendor id form is `<vendor-locale>-Chirp3-HD-<Speaker>`, e.g.
|
|
9
|
+
* `ta-IN-Chirp3-HD-Kore`. It is used as BOTH the display name and the id the
|
|
10
|
+
* backend receives, because the persisted preference is keyed by name — a
|
|
11
|
+
* separate display label would break a saved pick the first time it changed.
|
|
12
|
+
*
|
|
13
|
+
* `name` is therefore always the VENDOR's spelling (it is sent on the wire and
|
|
14
|
+
* must exist), while `lang` is always the APP's (it is matched against
|
|
15
|
+
* `SPEECH_LANG`). For every locale but Mandarin these are the same string.
|
|
16
|
+
*/
|
|
17
|
+
export declare const HOSTED_VOICES: readonly SpeechVoiceLike[];
|
|
18
|
+
/**
|
|
19
|
+
* The exact vendor locale to request for an app tag, or null when the vendor
|
|
20
|
+
* cannot speak the language at all.
|
|
21
|
+
*
|
|
22
|
+
* Prefers an exact match (`en-GB` stays `en-GB`, `zh-CN` becomes `cmn-CN`) and
|
|
23
|
+
* otherwise falls back to the primary subtag's canonical vendor locale
|
|
24
|
+
* (`ar-SA` -> `ar-XA`, `zh` -> `cmn-CN`).
|
|
25
|
+
*/
|
|
26
|
+
export declare function hostedLocaleFor(tag: string): string | null;
|
|
27
|
+
/**
|
|
28
|
+
* True when the hosted engine can actually speak this language.
|
|
29
|
+
*
|
|
30
|
+
* The caller needs this to decide whether to fall back to the device engine
|
|
31
|
+
* for a reply in a language the vendor does not cover, rather than sending a
|
|
32
|
+
* request that can only fail.
|
|
33
|
+
*/
|
|
34
|
+
export declare function hostedSupportsLanguage(tag: string): boolean;
|
|
35
|
+
//# sourceMappingURL=hostedVoiceCatalogue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hostedVoiceCatalogue.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/speech/hostedVoiceCatalogue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,mFAAmF;AACnF,eAAO,MAAM,eAAe,cAAc,CAAC;AAyE3C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,eAAe,EAQnD,CAAC;AAiCF;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO1D;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE3D"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//#region src/shared/assistant/speech/hostedVoiceCatalogue.ts
|
|
2
|
+
var e = "chirp3-hd", t = [
|
|
3
|
+
"Achernar",
|
|
4
|
+
"Algenib",
|
|
5
|
+
"Charon",
|
|
6
|
+
"Kore",
|
|
7
|
+
"Puck",
|
|
8
|
+
"Zephyr"
|
|
9
|
+
], n = [
|
|
10
|
+
"en-US",
|
|
11
|
+
"en-GB",
|
|
12
|
+
"en-IN",
|
|
13
|
+
"ta-IN",
|
|
14
|
+
"hi-IN",
|
|
15
|
+
"bn-IN",
|
|
16
|
+
"te-IN",
|
|
17
|
+
"mr-IN",
|
|
18
|
+
"gu-IN",
|
|
19
|
+
"kn-IN",
|
|
20
|
+
"ml-IN",
|
|
21
|
+
"pa-IN",
|
|
22
|
+
"ur-IN",
|
|
23
|
+
"ar-XA",
|
|
24
|
+
"es-ES",
|
|
25
|
+
"cmn-CN",
|
|
26
|
+
"ja-JP",
|
|
27
|
+
"fr-FR",
|
|
28
|
+
"de-DE"
|
|
29
|
+
], r = { "cmn-CN": "zh-CN" };
|
|
30
|
+
function i(e) {
|
|
31
|
+
return r[e] ?? e;
|
|
32
|
+
}
|
|
33
|
+
var a = n.flatMap((e) => t.map((t) => ({
|
|
34
|
+
name: `${e}-Chirp3-HD-${t}`,
|
|
35
|
+
lang: i(e),
|
|
36
|
+
localService: !1
|
|
37
|
+
}))), o = /* @__PURE__ */ new Map();
|
|
38
|
+
for (let e of n) for (let t of new Set([i(e), e])) {
|
|
39
|
+
let n = t.toLowerCase().split("-")[0];
|
|
40
|
+
o.has(n) || o.set(n, e);
|
|
41
|
+
}
|
|
42
|
+
var s = /* @__PURE__ */ new Map();
|
|
43
|
+
for (let e of n) s.set(e.toLowerCase(), e), s.set(i(e).toLowerCase(), e);
|
|
44
|
+
function c(e) {
|
|
45
|
+
let t = e.trim().replace("_", "-");
|
|
46
|
+
if (!t) return null;
|
|
47
|
+
let n = s.get(t.toLowerCase());
|
|
48
|
+
if (n) return n;
|
|
49
|
+
let r = t.toLowerCase().split("-")[0];
|
|
50
|
+
return o.get(r) ?? null;
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { e as HOSTED_TTS_TIER, a as HOSTED_VOICES, c as hostedLocaleFor };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { browserSpeechEngine } from './browserSpeechEngine';
|
|
2
|
+
export { createHostedTtsEngine } from './hostedTtsSpeechEngine';
|
|
3
|
+
export { configureSpeechEngine, createSpeechEngines, getSpeechEngine, readSpeechEngineConfig, resetSpeechEngine, } from './createSpeechEngine';
|
|
4
|
+
export { createRoutingSpeechEngine } from './routingSpeechEngine';
|
|
5
|
+
export type { SpeechEngineConfig } from './createSpeechEngine';
|
|
6
|
+
export { HOSTED_VOICES, HOSTED_TTS_TIER, hostedSupportsLanguage } from './hostedVoiceCatalogue';
|
|
7
|
+
export type { SpeechEngine, SpeechErrorReason, SpeechSpeakHandlers, SpeechSpeakOptions, SpeechVoiceLike, } from './types';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/speech/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,EACf,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChG,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,GAChB,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SpeechEngine } from './types';
|
|
2
|
+
export interface RoutingEngines {
|
|
3
|
+
device: SpeechEngine;
|
|
4
|
+
/** Null when hosted TTS is not configured — then this is a pass-through. */
|
|
5
|
+
hosted: SpeechEngine | null;
|
|
6
|
+
}
|
|
7
|
+
export declare function createRoutingSpeechEngine({ device, hosted }: RoutingEngines): SpeechEngine;
|
|
8
|
+
//# sourceMappingURL=routingSpeechEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routingSpeechEngine.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/speech/routingSpeechEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAA4D,MAAM,SAAS,CAAC;AAEtG,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,YAAY,CAAC;IACrB,4EAA4E;IAC5E,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;CAC7B;AAED,wBAAgB,yBAAyB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,cAAc,GAAG,YAAY,CAoG1F"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "./hostedVoiceCatalogue.js";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { SpeechVoiceLike } from '../voice/speechVoices';
|
|
2
|
+
export type { SpeechVoiceLike };
|
|
3
|
+
export interface SpeechSpeakOptions {
|
|
4
|
+
/** Chosen by `selectVoiceForLocale`; `null` lets the engine resolve `lang`. */
|
|
5
|
+
voice: SpeechVoiceLike | null;
|
|
6
|
+
/** BCP-47 tag for the app locale, used when `voice` is null. */
|
|
7
|
+
lang: string;
|
|
8
|
+
/** 0.1–10, browser default 1. */
|
|
9
|
+
rate?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Why playback stopped. `cancelled` is not a failure: it is what every queued
|
|
13
|
+
* utterance reports when we deliberately cancel (new playback, panel close,
|
|
14
|
+
* conversation switch), and the UI must stay silent about it.
|
|
15
|
+
*/
|
|
16
|
+
export type SpeechErrorReason = 'cancelled' | 'not-allowed' | 'unsupported' | 'unknown';
|
|
17
|
+
export interface SpeechSpeakHandlers {
|
|
18
|
+
/**
|
|
19
|
+
* Audio has actually begun playing — not merely been requested.
|
|
20
|
+
*
|
|
21
|
+
* Exists so a caller can tell "this engine failed before the user heard
|
|
22
|
+
* anything" from "it failed part-way through". `routingSpeechEngine` uses it
|
|
23
|
+
* to decide whether retrying on the other engine is safe: retrying after
|
|
24
|
+
* audio has been heard would repeat words the user already got.
|
|
25
|
+
*
|
|
26
|
+
* Optional, so an engine that cannot report it simply never calls it, and a
|
|
27
|
+
* caller that does not care ignores it.
|
|
28
|
+
*/
|
|
29
|
+
onStart?: () => void;
|
|
30
|
+
/**
|
|
31
|
+
* Chunk `index` has begun playing — the sync signal for live captions.
|
|
32
|
+
*
|
|
33
|
+
* ★ Per CHUNK, not per word. `onboundary` would give word granularity but
|
|
34
|
+
* exists only on the browser engine; the hosted engine plays one audio element
|
|
35
|
+
* per chunk and has no equivalent. A chunk start is the finest granularity
|
|
36
|
+
* BOTH engines can honestly report, so the caption never has to know which one
|
|
37
|
+
* is speaking.
|
|
38
|
+
*
|
|
39
|
+
* Without it the whole reply is captioned the instant playback begins — which
|
|
40
|
+
* is what "captions appear multiple sentences ahead of the audio" means in
|
|
41
|
+
* practice: the user reads the answer, then waits for the voice to catch up.
|
|
42
|
+
*
|
|
43
|
+
* Optional. An engine that cannot report it never calls it and the caption
|
|
44
|
+
* falls back to showing the reply whole: degrades, never breaks.
|
|
45
|
+
*/
|
|
46
|
+
onChunkStart?: (index: number) => void;
|
|
47
|
+
/** The whole queue finished on its own. */
|
|
48
|
+
onEnd?: () => void;
|
|
49
|
+
onError?: (reason: SpeechErrorReason) => void;
|
|
50
|
+
}
|
|
51
|
+
export interface SpeechEngine {
|
|
52
|
+
/** Stable id, for logging and for telling engines apart in a future swap. */
|
|
53
|
+
readonly id: string;
|
|
54
|
+
/** False when the platform has no usable speech output at all. */
|
|
55
|
+
isSupported(): boolean;
|
|
56
|
+
/** May be empty before the engine has loaded its voice list — see `voiceAvailability`. */
|
|
57
|
+
listVoices(): SpeechVoiceLike[];
|
|
58
|
+
/** Subscribe to voice-list changes; returns an unsubscribe function. */
|
|
59
|
+
subscribeVoices(listener: () => void): () => void;
|
|
60
|
+
/** Speak the chunks in order. Implementations MUST cancel anything in flight first. */
|
|
61
|
+
speak(chunks: readonly string[], options: SpeechSpeakOptions, handlers?: SpeechSpeakHandlers): void;
|
|
62
|
+
/** Stop immediately and drop anything queued. Safe to call when idle. */
|
|
63
|
+
cancel(): void;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/speech/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAC9B,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,aAAa,GAAG,aAAa,GAAG,SAAS,CAAC;AAExF,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC/C;AAED,MAAM,WAAW,YAAY;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,WAAW,IAAI,OAAO,CAAC;IACvB,0FAA0F;IAC1F,UAAU,IAAI,eAAe,EAAE,CAAC;IAChC,wEAAwE;IACxE,eAAe,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAClD,uFAAuF;IACvF,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,kBAAkB,EAAE,QAAQ,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACpG,yEAAyE;IACzE,MAAM,IAAI,IAAI,CAAC;CAChB"}
|
package/dist/shared-assistant.js
CHANGED
|
@@ -8,4 +8,5 @@ import { useSandboxAssistantTransport as E } from "./shared/assistant/useSandbox
|
|
|
8
8
|
import { chatTurns as D, groupBySession as O, groupConsecutiveBySession as k, isVoiceTurn as A, voiceMarker as j, voiceSessionIdOf as M, voiceTurnsOnly as N } from "./shared/assistant/voice/turnVisibility.js";
|
|
9
9
|
import { toCaptionText as P } from "./shared/assistant/voice/captionText.js";
|
|
10
10
|
import { findPendingWrite as F, parseConfirmationRefusal as I } from "./shared/assistant/pendingWrite.js";
|
|
11
|
-
|
|
11
|
+
import { createSpeechEngines as L } from "./shared/assistant/speech/createSpeechEngine.js";
|
|
12
|
+
export { v as AssistantRuntimeManager, y as buildProgress, n as buildPromptRequestBody, D as chatTurns, r as checkAssistantAiCredits, i as createAssistantSandbox, a as createAssistantSession, T as createSandboxAssistantTransport, L as createSpeechEngines, o as downloadAssistantArtifact, s as extendAssistantSandboxTTL, c as findExistingSandbox, F as findPendingWrite, l as getAssistantArtifacts, b as getAssistantMedia, u as getAssistantMessages, d as getAssistantSession, f as getAssistantStreamAccess, O as groupBySession, k as groupConsecutiveBySession, t as isQuotaExhaustedError, x as isRecoverable, A as isVoiceTurn, S as mapSessionToHistory, C as mergeMessagesById, I as parseConfirmationRefusal, p as reportAssistantAiTokenUsage, w as sanitize, m as sendAssistantPromptAsync, h as sendHeartbeat, P as toCaptionText, E as useSandboxAssistantTransport, e as useVoiceInput, j as voiceMarker, M as voiceSessionIdOf, N as voiceTurnsOnly, g as waitForAssistantServiceReady, _ as waitForSandboxRunning };
|