@burdenoff/fe-libs 2026.903.4 → 2026.903.6
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 +9 -0
- package/dist/shared/assistant/index.d.ts.map +1 -1
- package/dist/shared/assistant/speech/hostedVoiceCatalogue.js +4 -1
- package/dist/shared/assistant/speech/routingSpeechEngine.js +44 -1
- package/dist/shared/assistant/voice/assistantPollSchedule.d.ts +38 -0
- package/dist/shared/assistant/voice/assistantPollSchedule.d.ts.map +1 -0
- package/dist/shared/assistant/voice/languagePair.d.ts +36 -0
- package/dist/shared/assistant/voice/languagePair.d.ts.map +1 -0
- package/dist/shared/assistant/voice/languagePair.js +15 -0
- package/dist/shared/assistant/voice/languages.d.ts +74 -0
- package/dist/shared/assistant/voice/languages.d.ts.map +1 -0
- package/dist/shared/assistant/voice/languages.js +124 -0
- package/dist/shared/assistant/voice/speakableText.d.ts +121 -0
- package/dist/shared/assistant/voice/speakableText.d.ts.map +1 -0
- package/dist/shared/assistant/voice/voicePreview.d.ts +33 -0
- package/dist/shared/assistant/voice/voicePreview.d.ts.map +1 -0
- package/dist/shared/assistant/voice/voiceTranscript.d.ts +49 -0
- package/dist/shared/assistant/voice/voiceTranscript.d.ts.map +1 -0
- package/dist/shared/assistant/voice/voiceTranscript.js +42 -0
- package/dist/shared-assistant.js +9 -2
- package/package.json +1 -1
|
@@ -21,4 +21,13 @@ export { parseConfirmationRefusal, findPendingWrite } from './pendingWrite';
|
|
|
21
21
|
export type { PendingWrite, PendingWriteValue } from './pendingWrite';
|
|
22
22
|
export { createSpeechEngines } from './speech/createSpeechEngine';
|
|
23
23
|
export type { SpeechEngine, SpeechSpeakOptions, SpeechSpeakHandlers, SpeechErrorReason } from './speech/types';
|
|
24
|
+
export { buildVoiceTranscriptMarkdown, voiceTranscriptFilename, buildContinuedFromVoiceNote } from './voice/voiceTranscript';
|
|
25
|
+
export { resolveLanguagePair, replyFollowsInput } from './voice/languagePair';
|
|
26
|
+
export { ASSISTANT_LANGUAGES, resolveAssistantLanguage, findAssistantLanguage } from './voice/languages';
|
|
27
|
+
export type { AssistantLanguage } from './voice/languages';
|
|
28
|
+
export { browserSpeechEngine } from './speech/browserSpeechEngine';
|
|
29
|
+
export { createHostedTtsEngine } from './speech/hostedTtsSpeechEngine';
|
|
30
|
+
export { createRoutingSpeechEngine } from './speech/routingSpeechEngine';
|
|
31
|
+
export { HOSTED_VOICES, HOSTED_TTS_TIER, hostedSupportsLanguage } from './speech/hostedVoiceCatalogue';
|
|
32
|
+
export type { SpeechEngineConfig } from './speech/createSpeechEngine';
|
|
24
33
|
//# 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;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,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;AAC/G,OAAO,EAAE,4BAA4B,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAC7H,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AACzG,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAK3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvG,YAAY,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC"}
|
|
@@ -49,5 +49,8 @@ function c(e) {
|
|
|
49
49
|
let r = t.toLowerCase().split("-")[0];
|
|
50
50
|
return o.get(r) ?? null;
|
|
51
51
|
}
|
|
52
|
+
function l(e) {
|
|
53
|
+
return c(e) !== null;
|
|
54
|
+
}
|
|
52
55
|
//#endregion
|
|
53
|
-
export { e as HOSTED_TTS_TIER, a as HOSTED_VOICES, c as hostedLocaleFor };
|
|
56
|
+
export { e as HOSTED_TTS_TIER, a as HOSTED_VOICES, c as hostedLocaleFor, l as hostedSupportsLanguage };
|
|
@@ -1 +1,44 @@
|
|
|
1
|
-
import "./hostedVoiceCatalogue.js";
|
|
1
|
+
import { hostedSupportsLanguage as e } from "./hostedVoiceCatalogue.js";
|
|
2
|
+
//#region src/shared/assistant/speech/routingSpeechEngine.ts
|
|
3
|
+
function t({ device: t, hosted: n }) {
|
|
4
|
+
if (!n) return t;
|
|
5
|
+
let r = new Set(n.listVoices().map((e) => e.name)), i = (i) => n.isSupported() ? i.voice ? r.has(i.voice.name) ? n : t : e(i.lang) ? n : t : t;
|
|
6
|
+
return {
|
|
7
|
+
id: `routing(${n.id}+${t.id})`,
|
|
8
|
+
isSupported() {
|
|
9
|
+
return n.isSupported() || t.isSupported();
|
|
10
|
+
},
|
|
11
|
+
listVoices() {
|
|
12
|
+
return [...n.listVoices(), ...t.listVoices()];
|
|
13
|
+
},
|
|
14
|
+
subscribeVoices(e) {
|
|
15
|
+
return t.subscribeVoices(e);
|
|
16
|
+
},
|
|
17
|
+
speak(e, r, a) {
|
|
18
|
+
if (t.cancel(), n.cancel(), i(r) !== n) {
|
|
19
|
+
t.speak(e, r, a);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
let o = !1;
|
|
23
|
+
n.speak(e, r, {
|
|
24
|
+
onStart: () => {
|
|
25
|
+
o = !0, a?.onStart?.();
|
|
26
|
+
},
|
|
27
|
+
onEnd: () => a?.onEnd?.(),
|
|
28
|
+
onChunkStart: (e) => a?.onChunkStart?.(e),
|
|
29
|
+
onError: (n) => {
|
|
30
|
+
if (n === "cancelled" || o) {
|
|
31
|
+
a?.onError?.(n);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
t.speak(e, r, a);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
cancel() {
|
|
39
|
+
t.cancel(), n.cancel();
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { t as createRoutingSpeechEngine };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How often to re-check for the assistant's reply while a turn is in flight.
|
|
3
|
+
*
|
|
4
|
+
* The assistant has no streaming transport: the prompt is a fire-and-forget POST
|
|
5
|
+
* and the reply is discovered by re-fetching the message list. So this interval
|
|
6
|
+
* is the granularity of the whole experience — it bounds how late the answer
|
|
7
|
+
* appears in chat AND how late speech can start, because auto-speak only fires
|
|
8
|
+
* once a poll observes the reply completed.
|
|
9
|
+
*
|
|
10
|
+
* A flat 1500ms was the previous behaviour, which meant up to 1.5s of dead time
|
|
11
|
+
* after the answer was already sitting on the server, and made streamed text
|
|
12
|
+
* arrive in visible jerks rather than flowing.
|
|
13
|
+
*
|
|
14
|
+
* ★ The schedule is a compromise, not free. Every poll re-fetches the newest
|
|
15
|
+
* messages in full rather than a delta, so a faster interval is a real increase
|
|
16
|
+
* in request rate. It is spent where it buys the most — the first few seconds,
|
|
17
|
+
* where most replies land — and given back afterwards, so a long-running turn
|
|
18
|
+
* costs no more than it used to.
|
|
19
|
+
*
|
|
20
|
+
* The proper fix is a delta fetch (`sinceMs`, which the shared client already
|
|
21
|
+
* supports) or a streaming transport; both are larger changes than this.
|
|
22
|
+
*/
|
|
23
|
+
/** While the answer is most likely to land. */
|
|
24
|
+
export declare const POLL_FAST_MS = 600;
|
|
25
|
+
/** Still active, but the cheap wins are gone. */
|
|
26
|
+
export declare const POLL_STEADY_MS = 1000;
|
|
27
|
+
/** Long-running turn: back to the original cadence. */
|
|
28
|
+
export declare const POLL_SLOW_MS = 1500;
|
|
29
|
+
export declare const POLL_FAST_UNTIL_MS = 6000;
|
|
30
|
+
export declare const POLL_STEADY_UNTIL_MS = 20000;
|
|
31
|
+
/**
|
|
32
|
+
* Delay before the next poll, given how long this turn has been running.
|
|
33
|
+
*
|
|
34
|
+
* `elapsedMs` is injected rather than read from a clock so the schedule is
|
|
35
|
+
* testable and cannot drift with a mocked timer.
|
|
36
|
+
*/
|
|
37
|
+
export declare function pollDelayMs(elapsedMs: number): number;
|
|
38
|
+
//# sourceMappingURL=assistantPollSchedule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistantPollSchedule.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/voice/assistantPollSchedule.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,+CAA+C;AAC/C,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,iDAAiD;AACjD,eAAO,MAAM,cAAc,OAAO,CAAC;AACnC,uDAAuD;AACvD,eAAO,MAAM,YAAY,OAAO,CAAC;AAEjC,eAAO,MAAM,kBAAkB,OAAQ,CAAC;AACxC,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAE3C;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIrD"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AssistantLanguage } from './languages';
|
|
2
|
+
export interface AssistantLanguagePair {
|
|
3
|
+
/** What the user speaks or types. Drives the recogniser. */
|
|
4
|
+
input: AssistantLanguage;
|
|
5
|
+
/** What the assistant answers in. Drives the prompt directive and the voice. */
|
|
6
|
+
reply: AssistantLanguage;
|
|
7
|
+
/**
|
|
8
|
+
* A human chose this reply language, rather than it being guessed.
|
|
9
|
+
*
|
|
10
|
+
* `buildLanguagePreamble` uses this to decide whether the language the user
|
|
11
|
+
* WROTE IN overrides their setting. For a guess it should — what they typed is
|
|
12
|
+
* better evidence than the interface language. For a choice it must not, or
|
|
13
|
+
* the preference is inert for everyone who types rather than dictates.
|
|
14
|
+
*
|
|
15
|
+
* ★ True when EITHER field was pinned, not only the reply field. Someone who
|
|
16
|
+
* picked Tamil before this setting existed has `replyLanguage` null and means
|
|
17
|
+
* "answer me in Tamil"; reading only the reply field would decide they never
|
|
18
|
+
* chose anything and hand their setting back to the mirror clause — silently
|
|
19
|
+
* re-breaking, for every existing user, the exact bug that clause was written
|
|
20
|
+
* to fix.
|
|
21
|
+
*
|
|
22
|
+
* False only when NOTHING was pinned and the language came from the interface.
|
|
23
|
+
*
|
|
24
|
+
* Resolved rather than merely non-null, so a stale persisted code from a build
|
|
25
|
+
* that offered more languages is not mistaken for a decision.
|
|
26
|
+
*/
|
|
27
|
+
replyIsExplicit: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function resolveLanguagePair(inputCode: string | null | undefined, replyCode: string | null | undefined, appLocale: string): AssistantLanguagePair;
|
|
30
|
+
/**
|
|
31
|
+
* Does the reply language follow the input, or has it been pinned?
|
|
32
|
+
*
|
|
33
|
+
* The selector shows "Match my language" for the former, and it is the default.
|
|
34
|
+
*/
|
|
35
|
+
export declare function replyFollowsInput(replyCode: string | null | undefined): boolean;
|
|
36
|
+
//# sourceMappingURL=languagePair.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"languagePair.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/voice/languagePair.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAmD,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEtG,MAAM,WAAW,qBAAqB;IACpC,4DAA4D;IAC5D,KAAK,EAAE,iBAAiB,CAAC;IACzB,gFAAgF;IAChF,KAAK,EAAE,iBAAiB,CAAC;IACzB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,SAAS,EAAE,MAAM,GAChB,qBAAqB,CAWvB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAE/E"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { findAssistantLanguage as e, resolveAssistantLanguage as t } from "./languages.js";
|
|
2
|
+
//#region src/shared/assistant/voice/languagePair.ts
|
|
3
|
+
function n(n, r, i) {
|
|
4
|
+
let a = t(n, i), o = e(r);
|
|
5
|
+
return {
|
|
6
|
+
input: a,
|
|
7
|
+
reply: o ?? a,
|
|
8
|
+
replyIsExplicit: o !== null || e(n) !== null
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function r(t) {
|
|
12
|
+
return e(t) === null;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { r as replyFollowsInput, n as resolveLanguagePair };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The language the user is TALKING TO the assistant in (BOFF-7108).
|
|
3
|
+
*
|
|
4
|
+
* Separate from the app locale on purpose. A farmer whose phone and UI are in
|
|
5
|
+
* English may well speak Tamil, and until now the only way to dictate Tamil was
|
|
6
|
+
* to translate the entire interface — the recogniser language was derived from
|
|
7
|
+
* `useI18n().locale`, so the two could not be chosen independently.
|
|
8
|
+
*
|
|
9
|
+
* One selection drives three things, which is the point:
|
|
10
|
+
* 1. `recognition.lang` — what the recogniser is listening for;
|
|
11
|
+
* 2. the reply-language directive — what the model answers in;
|
|
12
|
+
* 3. the voice for reading that reply aloud.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately NOT keyed on the app `Locale` union. That union is the set of
|
|
15
|
+
* languages the INTERFACE is translated into (9, from fe-libs); this is the set
|
|
16
|
+
* of languages a user might SPEAK, which is larger and grows for different
|
|
17
|
+
* reasons. Tying them together is what created the problem.
|
|
18
|
+
*/
|
|
19
|
+
export interface AssistantLanguage {
|
|
20
|
+
/** Primary subtag. What we persist, and what the speech-locale helpers take. */
|
|
21
|
+
code: string;
|
|
22
|
+
/** BCP-47 tag handed to the Web Speech APIs — region matters to acoustic models. */
|
|
23
|
+
tag: string;
|
|
24
|
+
/**
|
|
25
|
+
* The ENDONYM — what a speaker calls their own language. Someone who reads
|
|
26
|
+
* only Tamil cannot find "Tamil" in a list; they can find "தமிழ்".
|
|
27
|
+
*/
|
|
28
|
+
label: string;
|
|
29
|
+
/**
|
|
30
|
+
* English name, for the model-facing directive.
|
|
31
|
+
*
|
|
32
|
+
* Hardcoded rather than derived from `Intl.DisplayNames` because this string
|
|
33
|
+
* goes into a prompt: it must be stable and correct regardless of the reader's
|
|
34
|
+
* browser, and a missing `Intl` implementation must not silently degrade an
|
|
35
|
+
* instruction into a bare subtag the model may answer *about* instead of *in*.
|
|
36
|
+
*/
|
|
37
|
+
english: string;
|
|
38
|
+
/**
|
|
39
|
+
* Speaking rate for this language, 1 = the engine's default.
|
|
40
|
+
*
|
|
41
|
+
* Nothing set a rate at all until now, so every user heard the spec default of
|
|
42
|
+
* 1.0 — reported as "it reads fast", which it is: a synthesised voice at 1.0 is
|
|
43
|
+
* harder to follow than a person at the same nominal speed, and harder again
|
|
44
|
+
* when the listener is outdoors or hearing a second language.
|
|
45
|
+
*
|
|
46
|
+
* Indic and Arabic get a further reduction. Their scripts pack more syllables
|
|
47
|
+
* per character than Latin, so the same nominal rate delivers noticeably more
|
|
48
|
+
* speech per second.
|
|
49
|
+
*/
|
|
50
|
+
rate?: number;
|
|
51
|
+
}
|
|
52
|
+
/** Applied when a language does not override it. */
|
|
53
|
+
export declare const DEFAULT_SPEECH_RATE = 0.9;
|
|
54
|
+
/**
|
|
55
|
+
* India first and by speaker count, then the other app locales.
|
|
56
|
+
*
|
|
57
|
+
* Regions follow where HealthyBowl operates: `-IN` for the Indic languages,
|
|
58
|
+
* because a recogniser tuned for `ur-PK` or a voice for `bn-BD` is the wrong
|
|
59
|
+
* pronunciation model for this product's users.
|
|
60
|
+
*/
|
|
61
|
+
export declare const ASSISTANT_LANGUAGES: readonly AssistantLanguage[];
|
|
62
|
+
export declare function findAssistantLanguage(code: string | null | undefined): AssistantLanguage | null;
|
|
63
|
+
/**
|
|
64
|
+
* Which language to use: the user's explicit choice, else the app locale, else
|
|
65
|
+
* English.
|
|
66
|
+
*
|
|
67
|
+
* The app-locale step is what keeps this backwards-compatible — a user who never
|
|
68
|
+
* touches the new control behaves exactly as before, because the value derived
|
|
69
|
+
* is the one the old code derived. An app locale outside this list (or a stale
|
|
70
|
+
* persisted code from a build that offered more) degrades to English rather than
|
|
71
|
+
* handing the recogniser a tag it will reject.
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveAssistantLanguage(selectedCode: string | null | undefined, appLocale: string): AssistantLanguage;
|
|
74
|
+
//# sourceMappingURL=languages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"languages.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/voice/languages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,iBAAiB;IAChC,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,oFAAoF;IACpF,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,oDAAoD;AACpD,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAGvC;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,iBAAiB,EAkB3D,CAAC;AAOF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,iBAAiB,GAAG,IAAI,CAG/F;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACvC,SAAS,EAAE,MAAM,GAChB,iBAAiB,CAEnB"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
//#region src/shared/assistant/voice/languages.ts
|
|
2
|
+
var e = .85, t = [
|
|
3
|
+
{
|
|
4
|
+
code: "en",
|
|
5
|
+
tag: "en-IN",
|
|
6
|
+
label: "English",
|
|
7
|
+
english: "English"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
code: "hi",
|
|
11
|
+
tag: "hi-IN",
|
|
12
|
+
label: "हिन्दी",
|
|
13
|
+
english: "Hindi",
|
|
14
|
+
rate: e
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
code: "bn",
|
|
18
|
+
tag: "bn-IN",
|
|
19
|
+
label: "বাংলা",
|
|
20
|
+
english: "Bengali",
|
|
21
|
+
rate: e
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
code: "te",
|
|
25
|
+
tag: "te-IN",
|
|
26
|
+
label: "తెలుగు",
|
|
27
|
+
english: "Telugu",
|
|
28
|
+
rate: e
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
code: "mr",
|
|
32
|
+
tag: "mr-IN",
|
|
33
|
+
label: "मराठी",
|
|
34
|
+
english: "Marathi",
|
|
35
|
+
rate: e
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
code: "ta",
|
|
39
|
+
tag: "ta-IN",
|
|
40
|
+
label: "தமிழ்",
|
|
41
|
+
english: "Tamil",
|
|
42
|
+
rate: e
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
code: "gu",
|
|
46
|
+
tag: "gu-IN",
|
|
47
|
+
label: "ગુજરાતી",
|
|
48
|
+
english: "Gujarati",
|
|
49
|
+
rate: e
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
code: "kn",
|
|
53
|
+
tag: "kn-IN",
|
|
54
|
+
label: "ಕನ್ನಡ",
|
|
55
|
+
english: "Kannada",
|
|
56
|
+
rate: e
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
code: "ml",
|
|
60
|
+
tag: "ml-IN",
|
|
61
|
+
label: "മലയാളം",
|
|
62
|
+
english: "Malayalam",
|
|
63
|
+
rate: e
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
code: "pa",
|
|
67
|
+
tag: "pa-IN",
|
|
68
|
+
label: "ਪੰਜਾਬੀ",
|
|
69
|
+
english: "Punjabi",
|
|
70
|
+
rate: e
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
code: "ur",
|
|
74
|
+
tag: "ur-IN",
|
|
75
|
+
label: "اردو",
|
|
76
|
+
english: "Urdu",
|
|
77
|
+
rate: e
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
code: "ar",
|
|
81
|
+
tag: "ar-SA",
|
|
82
|
+
label: "العربية",
|
|
83
|
+
english: "Arabic",
|
|
84
|
+
rate: e
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
code: "es",
|
|
88
|
+
tag: "es-ES",
|
|
89
|
+
label: "Español",
|
|
90
|
+
english: "Spanish"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
code: "fr",
|
|
94
|
+
tag: "fr-FR",
|
|
95
|
+
label: "Français",
|
|
96
|
+
english: "French"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
code: "de",
|
|
100
|
+
tag: "de-DE",
|
|
101
|
+
label: "Deutsch",
|
|
102
|
+
english: "German"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
code: "zh",
|
|
106
|
+
tag: "zh-CN",
|
|
107
|
+
label: "中文",
|
|
108
|
+
english: "Chinese"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
code: "ja",
|
|
112
|
+
tag: "ja-JP",
|
|
113
|
+
label: "日本語",
|
|
114
|
+
english: "Japanese"
|
|
115
|
+
}
|
|
116
|
+
], n = new Map(t.map((e) => [e.code, e])), r = n.get("en");
|
|
117
|
+
function i(e) {
|
|
118
|
+
return e ? n.get(e.trim().toLowerCase().split("-")[0]) ?? null : null;
|
|
119
|
+
}
|
|
120
|
+
function a(e, t) {
|
|
121
|
+
return i(e) ?? i(t) ?? r;
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
export { t as ASSISTANT_LANGUAGES, i as findAssistantLanguage, a as resolveAssistantLanguage };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown → speakable plain text (BOFF-6290, assistant voice reply).
|
|
3
|
+
*
|
|
4
|
+
* An assistant reply is Markdown: headings, tables, fenced code, links, list
|
|
5
|
+
* markers, the odd emoji. Handing that string straight to a speech engine reads
|
|
6
|
+
* out "asterisk asterisk yield asterisk asterisk", spells URLs character by
|
|
7
|
+
* character, and grinds through a forty-row table one pipe at a time. This
|
|
8
|
+
* module flattens a reply into something a person would actually say.
|
|
9
|
+
*
|
|
10
|
+
* Design notes:
|
|
11
|
+
*
|
|
12
|
+
* - **Pure, and deliberately so.** This repo's vitest runs in the `node`
|
|
13
|
+
* environment (no jsdom), so audio and DOM behaviour are not testable here.
|
|
14
|
+
* The transformation is where the real logic lives, so it is isolated as a
|
|
15
|
+
* pure function with no imports and covered directly by `speakableText.test.ts`.
|
|
16
|
+
* - **Copy comes in through `labels`**, not `useTr`, so the function stays pure
|
|
17
|
+
* and the spoken placeholders still follow the app locale (the hook passes
|
|
18
|
+
* translated strings in). `{{rows}}` / `{{columns}}` interpolate the same way
|
|
19
|
+
* `useTr` params do.
|
|
20
|
+
* - **No lookbehind assertions.** Safari < 16.4 throws at *parse* time on a
|
|
21
|
+
* lookbehind, which would take down the whole module — the same trap the
|
|
22
|
+
* old hand-rolled markdown renderer fell into (see AssistantMarkdownRenderer).
|
|
23
|
+
* Every rule below uses a captured prefix or a lookahead instead.
|
|
24
|
+
*/
|
|
25
|
+
export interface SpeakableLabels {
|
|
26
|
+
/** Spoken in place of a fenced code block, which is unlistenable read out. */
|
|
27
|
+
codeBlock: string;
|
|
28
|
+
/** Spoken for an image with no alt text. */
|
|
29
|
+
image: string;
|
|
30
|
+
/** Spoken in place of a URL — nobody wants "h t t p s colon slash slash". */
|
|
31
|
+
link: string;
|
|
32
|
+
/** Spoken in place of a table too large to read. Params: rows, columns. */
|
|
33
|
+
largeTable: string;
|
|
34
|
+
/** Appended when a very long reply is cut short. */
|
|
35
|
+
truncated: string;
|
|
36
|
+
/** Spoken for `@` in an address. English "at" inside a Tamil reply is jarring. */
|
|
37
|
+
emailAt: string;
|
|
38
|
+
/** Spoken for `.` in an address. */
|
|
39
|
+
emailDot: string;
|
|
40
|
+
/**
|
|
41
|
+
* Prefix for a numbered item nested under an UNnumbered one. Param: ordinal.
|
|
42
|
+
*
|
|
43
|
+
* Nested ordered items normally read as a dotted path ("2.1.") which already
|
|
44
|
+
* says where the sub-steps are; that is impossible when the parent is a
|
|
45
|
+
* bullet and has no number of its own, so the level is named instead.
|
|
46
|
+
*/
|
|
47
|
+
nestedItem: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Sentence punctuation is deliberately absent: block-level labels get a full
|
|
51
|
+
* stop from `asSentence`, and the inline ones ("link", "image") have to read
|
|
52
|
+
* naturally mid-sentence — "see link for details", not "see Link. for details".
|
|
53
|
+
*/
|
|
54
|
+
export declare const DEFAULT_SPEAKABLE_LABELS: SpeakableLabels;
|
|
55
|
+
/**
|
|
56
|
+
* Tables at or below this many body rows are flattened into "header: value"
|
|
57
|
+
* sentences, because a 2×3 table is usually THE answer. Anything larger is
|
|
58
|
+
* summarised — the point of the limit is that a long table is unlistenable, not
|
|
59
|
+
* that tables are unimportant.
|
|
60
|
+
*/
|
|
61
|
+
export declare const MAX_SPOKEN_TABLE_ROWS = 3;
|
|
62
|
+
/** Roughly ten minutes of speech. Past this the reply is summarised as cut off. */
|
|
63
|
+
export declare const MAX_SPEAKABLE_CHARS = 8000;
|
|
64
|
+
/**
|
|
65
|
+
* Utterances are split at about this length before being queued.
|
|
66
|
+
*
|
|
67
|
+
* Chrome (and Edge) silently stop a single `SpeechSynthesisUtterance` after
|
|
68
|
+
* roughly fifteen seconds — a long-standing engine bug, not something we can
|
|
69
|
+
* feature-detect. Queueing sentence-sized utterances keeps every one of them
|
|
70
|
+
* well under that ceiling, so a long reply finishes instead of dying mid-word.
|
|
71
|
+
*/
|
|
72
|
+
export declare const SPEECH_CHUNK_CHARS = 180;
|
|
73
|
+
/**
|
|
74
|
+
* Chunk ceiling for Chinese and Japanese, in CHARACTERS.
|
|
75
|
+
*
|
|
76
|
+
* The limit above is sized for Latin text, where ~180 characters is ~30 words
|
|
77
|
+
* and lands around ten seconds. A CJK character is a whole syllable — a zh voice
|
|
78
|
+
* reads roughly 4–6 of them a second — so 180 of them is a minute of speech,
|
|
79
|
+
* four times past the very cut-off the chunking exists to dodge. 40 characters
|
|
80
|
+
* is ~7–10 seconds, the same target the Latin limit is aiming at.
|
|
81
|
+
*/
|
|
82
|
+
export declare const SPEECH_CHUNK_CJK_CHARS = 40;
|
|
83
|
+
export interface SpeakableOptions {
|
|
84
|
+
labels?: Partial<SpeakableLabels>;
|
|
85
|
+
maxChars?: number;
|
|
86
|
+
maxSpokenTableRows?: number;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Split a GFM table row into cells, honouring `\|` escapes.
|
|
90
|
+
*
|
|
91
|
+
* Written as a scan rather than `split(/(?<!\\)\|/)` on purpose — see the
|
|
92
|
+
* no-lookbehind note at the top of the file.
|
|
93
|
+
*/
|
|
94
|
+
export declare function splitTableRow(line: string): string[];
|
|
95
|
+
/**
|
|
96
|
+
* Say an address the way a person reads one out.
|
|
97
|
+
*
|
|
98
|
+
* "support at healthybowl dot com" — an engine handed the raw string either
|
|
99
|
+
* spells it or swallows the punctuation, and expanding the dots here also keeps
|
|
100
|
+
* the sentence splitter from mistaking `.com` for the end of a sentence.
|
|
101
|
+
*/
|
|
102
|
+
export declare function speakableEmail(address: string, at?: string, dot?: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* Convert an assistant reply (Markdown) into plain text worth speaking.
|
|
105
|
+
*
|
|
106
|
+
* Returns `''` when there is nothing speakable — the caller should treat that
|
|
107
|
+
* as "no speak control", not as an error.
|
|
108
|
+
*/
|
|
109
|
+
export declare function toSpeakableText(markdown: string, options?: SpeakableOptions): string;
|
|
110
|
+
/** Does this text need CJK treatment? True for mixed "土壤 pH 6.5" text too. */
|
|
111
|
+
export declare function containsCjk(text: string): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Split speakable text into utterance-sized chunks.
|
|
114
|
+
*
|
|
115
|
+
* See SPEECH_CHUNK_CHARS: this exists to dodge Chrome's ~15 second single
|
|
116
|
+
* utterance cut-off, so the split points are sentence boundaries (a pause the
|
|
117
|
+
* listener expects) rather than an arbitrary character offset. A CJK sentence
|
|
118
|
+
* gets the tighter CJK ceiling, because a character there is a whole syllable.
|
|
119
|
+
*/
|
|
120
|
+
export declare function chunkForSpeech(text: string, maxChars?: number, cjkMaxChars?: number): string[];
|
|
121
|
+
//# sourceMappingURL=speakableText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speakableText.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/voice/speakableText.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,eAStC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,OAAQ,CAAC;AAEzC;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AA6CD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAsBpD;AAkBD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,SAAO,EAAE,GAAG,SAAQ,GAAG,MAAM,CAE9E;AAqJD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAiJxF;AAiMD,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD;AAkFD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,MAA2B,EACrC,WAAW,GAAE,MAA+B,GAC3C,MAAM,EAAE,CAoEV"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a voice says when you audition it.
|
|
3
|
+
*
|
|
4
|
+
* Picking a voice used to be silent: you chose one, nothing happened, and the
|
|
5
|
+
* first time you heard it was after asking the assistant a real question and
|
|
6
|
+
* waiting out a round trip. Auditioning a voice should not cost a model call.
|
|
7
|
+
*
|
|
8
|
+
* ★ The sample is in the SPOKEN language, not the interface language. A voice is
|
|
9
|
+
* being judged on how it reads Tamil, so making it read English tells the
|
|
10
|
+
* listener nothing about the decision they are making — and the two are
|
|
11
|
+
* independent settings here anyway.
|
|
12
|
+
*
|
|
13
|
+
* ★ Carried as a static table rather than through `tr()`, for the same reason
|
|
14
|
+
* `speechLabels` is: the i18n provider only ever fetches the ACTIVE locale, so
|
|
15
|
+
* there is nothing to ask for a Tamil string while the interface is English.
|
|
16
|
+
* These are one short sentence each and they are needed synchronously.
|
|
17
|
+
*
|
|
18
|
+
* Deliberately short. Long enough to hear timbre and cadence, short enough that
|
|
19
|
+
* auditioning four voices is not a chore, and it names the product so the
|
|
20
|
+
* listener hears the one word this voice will say most often.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* The audition sentence for a locale.
|
|
24
|
+
*
|
|
25
|
+
* Falls back to English rather than to silence: a voice the listener can hear
|
|
26
|
+
* saying the wrong language still answers "what does this voice sound like",
|
|
27
|
+
* which is the question being asked. Region subtags are ignored — `ta-IN` and
|
|
28
|
+
* `ta-LK` want the same sentence.
|
|
29
|
+
*/
|
|
30
|
+
export declare function voicePreviewText(locale: string): string;
|
|
31
|
+
/** Exposed for tests: which languages have their own audition sentence. */
|
|
32
|
+
export declare const PREVIEW_LANGUAGES: string[];
|
|
33
|
+
//# sourceMappingURL=voicePreview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voicePreview.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/voice/voicePreview.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAsBH;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB,UAAmC,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { VoiceTranscriptTurn } from './turnVisibility';
|
|
2
|
+
/**
|
|
3
|
+
* The spoken conversation as a Markdown document.
|
|
4
|
+
*
|
|
5
|
+
* ★ Sessions become headings rather than one flat list. A transcript is read
|
|
6
|
+
* later, when "which day was that?" is the actual question — running three
|
|
7
|
+
* separate exchanges together is precisely what the session id was added to
|
|
8
|
+
* stop, and a saved file is the copy most likely to outlive anyone's memory of
|
|
9
|
+
* the order.
|
|
10
|
+
*
|
|
11
|
+
* ★ Markdown, not JSON. The audience is a person forwarding a spoken exchange to
|
|
12
|
+
* an agronomist or an insurer; the whole-conversation JSON export already exists
|
|
13
|
+
* for anything machine-shaped.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildVoiceTranscriptMarkdown(turns: readonly VoiceTranscriptTurn[], labels: {
|
|
16
|
+
title: string;
|
|
17
|
+
savedAt: string;
|
|
18
|
+
session: string;
|
|
19
|
+
earlier: string;
|
|
20
|
+
you: string;
|
|
21
|
+
assistant: string;
|
|
22
|
+
}, now: Date): string;
|
|
23
|
+
/** A filename that sorts by date and never collides across a single day. */
|
|
24
|
+
export declare function voiceTranscriptFilename(now: Date): string;
|
|
25
|
+
/**
|
|
26
|
+
* The note that "Continue in chat" leaves in the typed thread.
|
|
27
|
+
*
|
|
28
|
+
* ★ It carries the user's QUESTIONS and none of the assistant's answers, capped
|
|
29
|
+
* at three. The spec asks for "a concise summary rather than dumping every voice
|
|
30
|
+
* turn", and the answers are the long half — they are one click away in the
|
|
31
|
+
* transcript, and pasting them into the thread would be the exact leak the
|
|
32
|
+
* surfaces were split to prevent.
|
|
33
|
+
*
|
|
34
|
+
* ★ Built from the user's own words rather than by asking the model. A generated
|
|
35
|
+
* summary costs a round trip and can be wrong about what someone asked; their
|
|
36
|
+
* own sentences, clipped, cannot be. This also makes the action instant and
|
|
37
|
+
* free, which matters for a control people will press to get out of a voice
|
|
38
|
+
* session.
|
|
39
|
+
*
|
|
40
|
+
* ★ It tells the MODEL nothing, and does not need to. The client sends no
|
|
41
|
+
* history — the backend session already holds every spoken turn — so this note
|
|
42
|
+
* exists purely so the human can see, in the thread, what was discussed.
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildContinuedFromVoiceNote(turns: readonly VoiceTranscriptTurn[], labels: {
|
|
45
|
+
intro: string;
|
|
46
|
+
nothing: string;
|
|
47
|
+
more: (count: number) => string;
|
|
48
|
+
}): string;
|
|
49
|
+
//# sourceMappingURL=voiceTranscript.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voiceTranscript.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/voice/voiceTranscript.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAgCvF;;;;;;;;;;;;GAYG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,EAC5G,GAAG,EAAE,IAAI,GACR,MAAM,CA8BR;AAED,4EAA4E;AAC5E,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,IAAI,GAAG,MAAM,CAIzD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CAAE,GAC1E,MAAM,CAaR"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { groupConsecutiveBySession as e } from "./turnVisibility.js";
|
|
2
|
+
//#region src/shared/assistant/voice/voiceTranscript.ts
|
|
3
|
+
var t = 3, n = 80;
|
|
4
|
+
function r(e, t) {
|
|
5
|
+
let n = e.replace(/\s+/g, " ").trim();
|
|
6
|
+
if (n.length <= t) return n;
|
|
7
|
+
let r = n.slice(0, t), i = r.lastIndexOf(" ");
|
|
8
|
+
return `${(i > t * .6 ? r.slice(0, i) : r).trimEnd()}…`;
|
|
9
|
+
}
|
|
10
|
+
function i(e) {
|
|
11
|
+
let t = new Date(e);
|
|
12
|
+
return Number.isNaN(t.getTime()) ? "" : t.toLocaleTimeString(void 0, {
|
|
13
|
+
hour: "2-digit",
|
|
14
|
+
minute: "2-digit"
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function a(t, n, r) {
|
|
18
|
+
let a = e(t, (e) => e.sessionId), o = [
|
|
19
|
+
`# ${n.title}`,
|
|
20
|
+
"",
|
|
21
|
+
`_${n.savedAt}: ${r.toLocaleString()}_`,
|
|
22
|
+
""
|
|
23
|
+
], s = 0;
|
|
24
|
+
return a.forEach((e) => {
|
|
25
|
+
e.sessionId ? (s += 1, o.push(`## ${n.session} ${s}`, "")) : o.push(`## ${n.earlier}`, "");
|
|
26
|
+
for (let t of e.turns) {
|
|
27
|
+
let e = t.role === "user" ? n.you : n.assistant, r = i(t.timestamp);
|
|
28
|
+
o.push(r ? `**${e}** (${r})` : `**${e}**`, "", t.text.trim(), "");
|
|
29
|
+
}
|
|
30
|
+
}), `${o.join("\n").trimEnd()}\n`;
|
|
31
|
+
}
|
|
32
|
+
function o(e) {
|
|
33
|
+
return `healthybowl-voice-${`${e.getFullYear()}-${String(e.getMonth() + 1).padStart(2, "0")}-${String(e.getDate()).padStart(2, "0")}`}-${`${String(e.getHours()).padStart(2, "0")}${String(e.getMinutes()).padStart(2, "0")}`}.md`;
|
|
34
|
+
}
|
|
35
|
+
function s(e, i) {
|
|
36
|
+
let a = e.filter((e) => e.role === "user" && e.text.trim()).map((e) => e.text);
|
|
37
|
+
if (a.length === 0) return i.nothing;
|
|
38
|
+
let o = a.slice(-t), s = a.length - o.length, c = [i.intro, ...o.map((e) => `• ${r(e, n)}`)];
|
|
39
|
+
return s > 0 && c.push(i.more(s)), c.join("\n");
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { s as buildContinuedFromVoiceNote, a as buildVoiceTranscriptMarkdown, o as voiceTranscriptFilename };
|
package/dist/shared-assistant.js
CHANGED
|
@@ -8,5 +8,12 @@ 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
|
-
import {
|
|
12
|
-
|
|
11
|
+
import { browserSpeechEngine as L } from "./shared/assistant/speech/browserSpeechEngine.js";
|
|
12
|
+
import { HOSTED_TTS_TIER as R, HOSTED_VOICES as z, hostedSupportsLanguage as B } from "./shared/assistant/speech/hostedVoiceCatalogue.js";
|
|
13
|
+
import { createHostedTtsEngine as V } from "./shared/assistant/speech/hostedTtsSpeechEngine.js";
|
|
14
|
+
import { createRoutingSpeechEngine as H } from "./shared/assistant/speech/routingSpeechEngine.js";
|
|
15
|
+
import { createSpeechEngines as U } from "./shared/assistant/speech/createSpeechEngine.js";
|
|
16
|
+
import { buildContinuedFromVoiceNote as W, buildVoiceTranscriptMarkdown as G, voiceTranscriptFilename as K } from "./shared/assistant/voice/voiceTranscript.js";
|
|
17
|
+
import { ASSISTANT_LANGUAGES as q, findAssistantLanguage as J, resolveAssistantLanguage as Y } from "./shared/assistant/voice/languages.js";
|
|
18
|
+
import { replyFollowsInput as X, resolveLanguagePair as Z } from "./shared/assistant/voice/languagePair.js";
|
|
19
|
+
export { q as ASSISTANT_LANGUAGES, v as AssistantRuntimeManager, R as HOSTED_TTS_TIER, z as HOSTED_VOICES, L as browserSpeechEngine, W as buildContinuedFromVoiceNote, y as buildProgress, n as buildPromptRequestBody, G as buildVoiceTranscriptMarkdown, D as chatTurns, r as checkAssistantAiCredits, i as createAssistantSandbox, a as createAssistantSession, V as createHostedTtsEngine, H as createRoutingSpeechEngine, T as createSandboxAssistantTransport, U as createSpeechEngines, o as downloadAssistantArtifact, s as extendAssistantSandboxTTL, J as findAssistantLanguage, 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, B as hostedSupportsLanguage, t as isQuotaExhaustedError, x as isRecoverable, A as isVoiceTurn, S as mapSessionToHistory, C as mergeMessagesById, I as parseConfirmationRefusal, X as replyFollowsInput, p as reportAssistantAiTokenUsage, Y as resolveAssistantLanguage, Z as resolveLanguagePair, w as sanitize, m as sendAssistantPromptAsync, h as sendHeartbeat, P as toCaptionText, E as useSandboxAssistantTransport, e as useVoiceInput, j as voiceMarker, M as voiceSessionIdOf, K as voiceTranscriptFilename, N as voiceTurnsOnly, g as waitForAssistantServiceReady, _ as waitForSandboxRunning };
|