@burdenoff/fe-libs 2026.1029.6 → 2026.1029.7

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.
Files changed (34) hide show
  1. package/dist/shared/assistant/index.d.ts +5 -0
  2. package/dist/shared/assistant/liveDeltas.d.ts +3 -0
  3. package/dist/shared/assistant/liveDeltas.js +17 -1
  4. package/dist/shared/assistant/liveOverlay.d.ts +4 -0
  5. package/dist/shared/assistant/liveOverlay.js +10 -1
  6. package/dist/shared/assistant/speech/appendSpeechQueue.d.ts +13 -0
  7. package/dist/shared/assistant/speech/appendSpeechQueue.js +75 -0
  8. package/dist/shared/assistant/speech/browserSpeechEngine.js +93 -46
  9. package/dist/shared/assistant/speech/hostedTtsSpeechEngine.js +173 -109
  10. package/dist/shared/assistant/speech/index.d.ts +1 -1
  11. package/dist/shared/assistant/speech/notifyCancellationSpeechEngine.js +53 -0
  12. package/dist/shared/assistant/speech/routingSpeechEngine.js +107 -23
  13. package/dist/shared/assistant/speech/speechFeed.d.ts +17 -2
  14. package/dist/shared/assistant/speech/speechFeed.js +35 -12
  15. package/dist/shared/assistant/speech/types.d.ts +20 -1
  16. package/dist/shared/assistant/turnTimeline.d.ts +3 -0
  17. package/dist/shared/assistant/turnTimeline.js +4 -1
  18. package/dist/shared/assistant/ui/AssistantChatArea.js +1410 -1393
  19. package/dist/shared/assistant/ui/AssistantSpeechProvider.js +351 -291
  20. package/dist/shared/assistant/ui/useVoiceSession.js +323 -288
  21. package/dist/shared/assistant/voice/livePhraseSpeech.d.ts +14 -0
  22. package/dist/shared/assistant/voice/livePhraseSpeech.js +72 -0
  23. package/dist/shared/assistant/voice/phraseGate.d.ts +11 -0
  24. package/dist/shared/assistant/voice/phraseGate.js +74 -0
  25. package/dist/shared/assistant/voice/phrasePlayback.d.ts +9 -0
  26. package/dist/shared/assistant/voice/phrasePlayback.js +78 -0
  27. package/dist/shared/assistant/voice/phraseSpeechConfig.d.ts +5 -0
  28. package/dist/shared/assistant/voice/phraseSpeechConfig.js +10 -0
  29. package/dist/shared/assistant/voice/speakableText.d.ts +1 -0
  30. package/dist/shared/assistant/voice/speakableText.js +1 -1
  31. package/dist/shared/assistant/voice/voiceSession.d.ts +5 -0
  32. package/dist/shared/assistant/voice/voiceSession.js +17 -1
  33. package/dist/shared-assistant.js +293 -288
  34. package/package.json +1 -1
@@ -0,0 +1,14 @@
1
+ import { LiveDeltaState } from '../liveDeltas';
2
+ export declare const NARRATION_HOLD_MS = 700;
3
+ export interface LivePhraseSpeech {
4
+ update(visible: string, live: LiveDeltaState, floor: number): void;
5
+ invalidate(live: LiveDeltaState, floor: number): void;
6
+ close(): void;
7
+ }
8
+ /** One turn's narrator. Its caller runs only on an opted-in voice/SSE overlay. */
9
+ export declare function createLivePhraseSpeech(options: {
10
+ append(phrases: readonly string[], visible: string): void;
11
+ retract(): void;
12
+ setTimer?: (run: () => void, ms: number) => unknown;
13
+ clearTimer?: (timer: unknown) => void;
14
+ }): LivePhraseSpeech;
@@ -0,0 +1,72 @@
1
+ import { toSpeakableText as e } from "./speakableText.js";
2
+ import { phraseCandidates as t } from "./phraseGate.js";
3
+ //#region src/shared/assistant/voice/livePhraseSpeech.ts
4
+ var n = 700;
5
+ function r(n) {
6
+ let r = n.setTimer ?? ((e, t) => setTimeout(e, t)), i = n.clearTimer ?? ((e) => {
7
+ clearTimeout(e);
8
+ }), a = null, o = "", s = [], c = [], l = "", u = !1, d = !1, f = () => {
9
+ a !== null && i(a), a = null;
10
+ }, p = () => {
11
+ f(), n.retract(), u = !0;
12
+ }, m = (e) => {
13
+ if (d || u || e <= s.length) return;
14
+ let t = c.slice(s.length, e);
15
+ s = c.slice(0, e), n.append(t, l);
16
+ }, h = (e, t) => {
17
+ let n = [...e.messages].filter(([, e]) => (e.created ?? 0) > t);
18
+ n.sort((e, t) => (e[1].created ?? 0) - (t[1].created ?? 0));
19
+ let r = n.at(-1)?.[0];
20
+ return [...e.parts].filter(([, e]) => e.messageId === r).at(-1);
21
+ };
22
+ return {
23
+ invalidate(e, t) {
24
+ if (d || !o) return;
25
+ let n = h(e, t);
26
+ if (!n) {
27
+ p();
28
+ return;
29
+ }
30
+ let [r, i] = n;
31
+ (`${i.messageId}/${r}/${i.generation}` !== o || !i.live || i.gapped || i.frozen || i.toolAfter || i.generation !== e.generation) && p();
32
+ },
33
+ update(i, g, _) {
34
+ if (d) return;
35
+ let v = h(g, _);
36
+ if (!v) return;
37
+ let [y, b] = v, x = `${b.messageId}/${y}/${b.generation}`;
38
+ if (x !== o && (f(), o && n.retract(), o = x, s = [], u = !1), !b.live || b.gapped || b.frozen || b.toolAfter || b.generation !== g.generation) {
39
+ p();
40
+ return;
41
+ }
42
+ if (u) return;
43
+ let S = b.ended && g.messages.get(b.messageId)?.completed !== void 0;
44
+ if (c = t(b.text, S), s.some((e, t) => c[t] !== e)) {
45
+ p();
46
+ return;
47
+ }
48
+ l = i;
49
+ let C = e(i).replace(/\s+/g, " ");
50
+ if (c.some((e) => !C.includes(e.replace(/\s+/g, " ")))) {
51
+ p();
52
+ return;
53
+ }
54
+ if (S) {
55
+ f(), m(c.length);
56
+ return;
57
+ }
58
+ if (m(Math.max(0, c.length - 1)), s.length === c.length) {
59
+ f();
60
+ return;
61
+ }
62
+ a === null && (a = r(() => {
63
+ a = null, m(c.length);
64
+ }, 700));
65
+ },
66
+ close() {
67
+ d = !0, f();
68
+ }
69
+ };
70
+ }
71
+ //#endregion
72
+ export { n as NARRATION_HOLD_MS, r as createLivePhraseSpeech };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Re-scan a bounded, append-only source part. Callers keep their released prefix
3
+ * and freeze if it ever changes; they do not feed arbitrary poll snapshots.
4
+ * Unfinished paragraphs contribute only sentences with a confirmed ending.
5
+ */
6
+ export declare function phraseCandidates(markdown: string, final?: boolean): string[];
7
+ /** Reconcile F against accepted/unwithdrawn S without reading the final twice. */
8
+ export declare function unspokenFinal(final: string, segments: readonly string[]): {
9
+ text: string;
10
+ diverged: boolean;
11
+ };
@@ -0,0 +1,74 @@
1
+ import { MAX_SPEAKABLE_CHARS as e, toSpeakableText as t } from "./speakableText.js";
2
+ //#region src/shared/assistant/voice/phraseGate.ts
3
+ var n = /(?:\b(?:mr|mrs|ms|dr|prof|sr|jr|st|vs|etc|e\.g|i\.e)|\b[A-Z])\.$/i, r = /^(?:thinking|working|one moment|just a moment|sure|certainly|let me (?:check|think|look)|सोच रहा हूँ|சிந்திக்கிறேன்|أفكر|思考中)[\s.!…।॥。]*$/iu, i = /^\s*(?:\||[-*+]\s|\d+[.)]\s|[[{])/, a = {
4
+ codeBlock: "",
5
+ image: "",
6
+ link: "",
7
+ largeTable: "",
8
+ truncated: "",
9
+ emailAt: "",
10
+ emailDot: "",
11
+ nestedItem: ""
12
+ };
13
+ function o(e, i) {
14
+ let o = [], s = 0, c = (n) => {
15
+ let i = t(e.slice(s, n), { labels: a }).trim();
16
+ s = n, i && !r.test(i) && o.push(i);
17
+ };
18
+ for (let t = 0; t < e.length; t++) {
19
+ let r = e[t];
20
+ if (!".!?…。!?।॥؟".includes(r)) continue;
21
+ let i = e[t + 1] ?? "";
22
+ r === "." && (/\d/.test(e[t - 1] ?? "") && (i === "" || /\d/.test(i)) || n.test(e.slice(s, t + 1)) || i === ".") || ".!?…".includes(r) && i !== "" && !/\s/.test(i) || i !== r && c(t + 1);
23
+ }
24
+ return i && s < e.length && c(e.length), o;
25
+ }
26
+ function s(t, n = !1) {
27
+ let r = t.slice(0, e).split("\n"), a = [], s = [], c = null, l = !1, u = (e) => {
28
+ l || a.push(...o(s.join("\n"), e)), s = [], l = !1;
29
+ };
30
+ for (let e = 0; e < r.length; e++) {
31
+ let t = r[e], a = /^\s*(`{3,}|~{3,})/.exec(t)?.[1];
32
+ if (c) {
33
+ a && a[0] === c[0] && a.length >= c.length && (c = null);
34
+ continue;
35
+ }
36
+ if (a) {
37
+ u(!0), c = a;
38
+ continue;
39
+ }
40
+ if (!t.trim()) {
41
+ u(!0);
42
+ continue;
43
+ }
44
+ (i.test(t) || t.includes("|") || /[[{]\s*"(?:tool|arguments|name)"\s*:/.test(t)) && (l = !0), s.push(t), e === r.length - 1 && u(n);
45
+ }
46
+ return a;
47
+ }
48
+ function c(e, t) {
49
+ let n = (e) => e.replace(/\s+/g, " ").trim(), r = n(e), i = n(t.filter(Boolean).join(" "));
50
+ if (!i) return {
51
+ text: r,
52
+ diverged: !1
53
+ };
54
+ if (r.startsWith(i)) return {
55
+ text: r.slice(i.length).trim(),
56
+ diverged: !1
57
+ };
58
+ for (let e = t.length - 1; e >= 0; e--) {
59
+ let i = n(t[e] ?? "");
60
+ if (!i) continue;
61
+ let a = r.lastIndexOf(i);
62
+ if (a >= 0) return {
63
+ text: r.slice(a + i.length).trim(),
64
+ diverged: !0
65
+ };
66
+ }
67
+ let a = new Set(t.map(n)), o = s(r, !0);
68
+ return {
69
+ text: o.length ? o.filter((e) => !a.has(n(e))).join(" ") : r,
70
+ diverged: !0
71
+ };
72
+ }
73
+ //#endregion
74
+ export { s as phraseCandidates, c as unspokenFinal };
@@ -0,0 +1,9 @@
1
+ import { SpeechQueue, SpeechSpeakHandlers } from '../speech/types';
2
+ export interface PhrasePlayback {
3
+ append(text: string): void;
4
+ finish(final: string): void;
5
+ retract(): void;
6
+ cancel(): void;
7
+ }
8
+ /** One reply, one engine request. Buffer text only; synthesis stays one ahead. */
9
+ export declare function createPhrasePlayback(open: (handlers: SpeechSpeakHandlers) => SpeechQueue, handlers: SpeechSpeakHandlers, changed: (chunks: readonly string[]) => void, divergence: () => void): PhrasePlayback;
@@ -0,0 +1,78 @@
1
+ import { MAX_SPEAKABLE_CHARS as e, chunkForSpeech as t } from "./speakableText.js";
2
+ import { unspokenFinal as n } from "./phraseGate.js";
3
+ //#region src/shared/assistant/voice/phrasePlayback.ts
4
+ function r(r, i, a, o) {
5
+ let s = [], c = [], l = 0, u = !1, d = !1, f = !1, p = !1, m = !1, h = r({
6
+ onStart: () => {
7
+ p || i.onStart?.();
8
+ },
9
+ onChunkStart: (e) => {
10
+ p || i.onChunkStart?.(e);
11
+ },
12
+ onPause: () => {
13
+ p || i.onPause?.();
14
+ },
15
+ onDeviceFallback: () => {
16
+ p || i.onDeviceFallback?.();
17
+ },
18
+ onChunkEnd(e) {
19
+ p || (g(), i.onChunkEnd?.(e));
20
+ },
21
+ onRetract(e) {
22
+ if (!p) {
23
+ for (let t of e) s[t] = "";
24
+ a([...s]), i.onRetract?.(e);
25
+ }
26
+ },
27
+ onEnd() {
28
+ p || (p = !0, i.onEnd?.());
29
+ },
30
+ onError(e) {
31
+ p || (p = !0, c = [], i.onError?.(e));
32
+ }
33
+ });
34
+ function g() {
35
+ if (!(m || p)) {
36
+ m = !0;
37
+ try {
38
+ for (; c.length && !p && (d || !s.some(Boolean) || c.length > 1);) {
39
+ let e = c[0];
40
+ if (s.push(e), a([...s]), !h.append([e])) {
41
+ s.pop(), a([...s]);
42
+ break;
43
+ }
44
+ c.shift();
45
+ }
46
+ d && !c.length && !p && !f && (f = !0, h.end());
47
+ } finally {
48
+ m = !1;
49
+ }
50
+ }
51
+ }
52
+ let _ = (e) => t(e, void 0, void 0, { ...s.some(Boolean) ? { packFirstChunk: !0 } : { firstChunkMaxChars: 110 } });
53
+ return {
54
+ append(e) {
55
+ if (!(d || p || u)) {
56
+ if (l + e.length > 8e3) {
57
+ u = !0;
58
+ return;
59
+ }
60
+ l += e.length, c = _([...c, e].filter(Boolean).join(" ")), g();
61
+ }
62
+ },
63
+ finish(t) {
64
+ if (d || p) return;
65
+ d = !0;
66
+ let r = n(t.slice(0, e), [...s, ...c]);
67
+ r.diverged && o(), c = _([...c, r.text].filter(Boolean).join(" ")), g();
68
+ },
69
+ retract() {
70
+ d || p || (c = [], h.retract());
71
+ },
72
+ cancel() {
73
+ p = !0, c = [], h.cancel();
74
+ }
75
+ };
76
+ }
77
+ //#endregion
78
+ export { r as createPhrasePlayback };
@@ -0,0 +1,5 @@
1
+ export declare function configurePhraseSpeech(options: {
2
+ enabled: boolean;
3
+ environment: string;
4
+ } | null): void;
5
+ export declare function phraseSpeechEnabled(): boolean;
@@ -0,0 +1,10 @@
1
+ //#region src/shared/assistant/voice/phraseSpeechConfig.ts
2
+ var e = !1;
3
+ function t(t) {
4
+ e = t?.enabled === !0 && t.environment === "alpha";
5
+ }
6
+ function n() {
7
+ return e;
8
+ }
9
+ //#endregion
10
+ export { t as configurePhraseSpeech, n as phraseSpeechEnabled };
@@ -132,4 +132,5 @@ export declare function containsCjk(text: string): boolean;
132
132
  */
133
133
  export declare function chunkForSpeech(text: string, maxChars?: number, cjkMaxChars?: number, options?: {
134
134
  firstChunkMaxChars?: number;
135
+ packFirstChunk?: boolean;
135
136
  }): string[];
@@ -302,7 +302,7 @@ function ee(e, t = 180, n = 40, r = {}) {
302
302
  let n = X(s, t), r = Math.min(c, e);
303
303
  n.length <= r ? (s = n, c = r) : (o.push(s), s = t, c = e);
304
304
  }
305
- if (s && o.push(s), o.length > 1) {
305
+ if (s && o.push(s), o.length > 1 && !r.packFirstChunk) {
306
306
  let e = W(o[0]);
307
307
  if (e.length > 1) {
308
308
  let [t, ...n] = e;
@@ -125,6 +125,11 @@ export type VoiceSessionEvent =
125
125
  | {
126
126
  type: 'reply-audio-started';
127
127
  replyId: string;
128
+ partial?: boolean;
129
+ } | {
130
+ type: 'reply-audio-paused';
131
+ } | {
132
+ type: 'reply-audio-ended';
128
133
  }
129
134
  /**
130
135
  * ★ BOFF-9207: the turn ended WITHOUT an answer - the assistant refused it
@@ -158,13 +158,29 @@ function v(t, r, i = null) {
158
158
  pending: "",
159
159
  interim: ""
160
160
  } : t;
161
- case "reply-audio-started": return t.phase === "thinking" ? {
161
+ case "reply-audio-started": return r.partial && t.phase === "thinking" ? {
162
+ ...t,
163
+ phase: "speaking",
164
+ pending: "",
165
+ interim: ""
166
+ } : t.phase === "thinking" ? {
162
167
  ...t,
163
168
  ...g,
164
169
  phase: "speaking",
165
170
  pending: "",
166
171
  interim: ""
167
172
  } : t;
173
+ case "reply-audio-paused": return t.phase === "speaking" ? {
174
+ ...t,
175
+ phase: "thinking"
176
+ } : t;
177
+ case "reply-audio-ended": return t.phase === "thinking" || t.phase === "speaking" ? {
178
+ ...t,
179
+ ...g,
180
+ phase: "listening",
181
+ pending: "",
182
+ interim: ""
183
+ } : t;
168
184
  case "turn-refused": return t.phase === "thinking" ? {
169
185
  ...t,
170
186
  ...g,