@burdenoff/fe-libs 2026.903.10 → 2026.904.2

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.
@@ -0,0 +1,62 @@
1
+ import { ReactNode } from 'react';
2
+ /**
3
+ * Assistant copy, namespaced by product.
4
+ *
5
+ * The assistant UI is shared but its translations are not: every product seeds
6
+ * its own keys, and HealthyBowl already has hundreds of `healthybowl.assistant.*`
7
+ * keys live in production. So a shared component cannot hardcode a key prefix —
8
+ * it must ask for a RELATIVE key and let the product decide the namespace.
9
+ *
10
+ * ★★ The shape below is chosen to be byte-compatible with what HealthyBowl
11
+ * already ships. `assistantTranslationKey('healthybowl', 'header.title')` is
12
+ * exactly `'healthybowl.assistant.header.title'`, which is the key already
13
+ * seeded and already translated. Any other shape would silently drop every
14
+ * non-English user back to the English fallback while every test still passed —
15
+ * the migration would look clean and quietly un-translate the product.
16
+ *
17
+ * Products other than HealthyBowl simply have no keys yet, so they get the
18
+ * English fallback until they seed theirs. That is a known, visible gap rather
19
+ * than a regression.
20
+ */
21
+ /**
22
+ * How a relative key becomes a full translation key.
23
+ *
24
+ * Pure and exported precisely so the compatibility claim above is testable
25
+ * without rendering anything.
26
+ */
27
+ export declare function assistantTranslationKey(namespace: string, key: string): string;
28
+ export interface AssistantI18nValue {
29
+ /** Product id, e.g. `healthybowl`. Matches the id in shared/config/products. */
30
+ namespace: string;
31
+ }
32
+ export interface AssistantI18nProviderProps {
33
+ namespace: string;
34
+ children: ReactNode;
35
+ }
36
+ /**
37
+ * Supplies the product namespace to every assistant component beneath it.
38
+ *
39
+ * Mount it once, as high as the assistant goes — the same product id the
40
+ * transport already needs for `AI_ASSISTANT_PRODUCT`, so there is one answer to
41
+ * "which product is this?" rather than two that can disagree.
42
+ */
43
+ export declare function AssistantI18nProvider({ namespace, children, }: AssistantI18nProviderProps): import("react/jsx-runtime").JSX.Element;
44
+ /** The namespace in force, or null when no provider is mounted. */
45
+ export declare function useAssistantI18nNamespace(): string | null;
46
+ export type AssistantTr = (key: string, fallback: string, params?: Record<string, string | number>) => string;
47
+ /**
48
+ * Translate a RELATIVE assistant key, e.g. `tr('header.title', 'Assistant')`.
49
+ *
50
+ * Fallback semantics match the products' existing `useTr`: the I18n provider
51
+ * returns the key itself when it has no translation, and the caller's English
52
+ * string is used instead so a raw key never reaches the UI.
53
+ *
54
+ * ★ With no provider mounted this returns the English fallback rather than
55
+ * throwing. A missing provider is a configuration mistake, but a thrown error
56
+ * inside render is a white screen, and English copy is strictly better than
57
+ * that. It complains once to the console so the mistake is still findable — the
58
+ * failure this guards against is the assistant silently rendering English in
59
+ * every locale, which is exactly what happened to the fe-libs shell.
60
+ */
61
+ export declare function useAssistantTr(): AssistantTr;
62
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/i18n.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAIf;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,MAAM,CAER;AAED,MAAM,WAAW,kBAAkB;IACjC,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,QAAQ,GACT,EAAE,0BAA0B,2CAO5B;AAED,mEAAmE;AACnE,wBAAgB,yBAAyB,IAAI,MAAM,GAAG,IAAI,CAEzD;AAED,MAAM,MAAM,WAAW,GAAG,CACxB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KACrC,MAAM,CAAC;AAIZ;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,IAAI,WAAW,CA4B5C"}
@@ -0,0 +1,32 @@
1
+ import { useI18n as e } from "../providers/shell/I18nProvider.js";
2
+ import { createContext as t, useCallback as n, useContext as r, useMemo as i } from "react";
3
+ import { jsx as a } from "react/jsx-runtime";
4
+ //#region src/shared/assistant/i18n.tsx
5
+ function o(e, t) {
6
+ return `${e}.assistant.${t}`;
7
+ }
8
+ var s = t(null);
9
+ function c({ namespace: e, children: t }) {
10
+ let n = i(() => ({ namespace: e }), [e]);
11
+ return /* @__PURE__ */ a(s.Provider, {
12
+ value: n,
13
+ children: t
14
+ });
15
+ }
16
+ function l() {
17
+ return r(s)?.namespace ?? null;
18
+ }
19
+ var u = !1;
20
+ function d() {
21
+ let { t } = e(), r = l();
22
+ return r === null && !u && (u = !0, console.error("[assistant] No AssistantI18nProvider mounted — assistant copy will render in English for every locale. Wrap the assistant in <AssistantI18nProvider namespace=\"<product>\">.")), n((e, n, i) => {
23
+ if (r === null) return f(n, i);
24
+ let a = o(r, e), s = t(a);
25
+ return f(s === a ? n : s, i);
26
+ }, [t, r]);
27
+ }
28
+ function f(e, t) {
29
+ return t ? Object.entries(t).reduce((e, [t, n]) => e.replace(RegExp(`\\{\\{${t}\\}\\}`, "g"), String(n)), e) : e;
30
+ }
31
+ //#endregion
32
+ export { c as AssistantI18nProvider, o as assistantTranslationKey, l as useAssistantI18nNamespace, d as useAssistantTr };
@@ -59,4 +59,8 @@ export * from './images';
59
59
  export * from './operations';
60
60
  export * from './capabilities';
61
61
  export * from './speech/hostedTtsProtocol';
62
+ export * from './i18n';
63
+ export * from './voice/speechLabels';
64
+ export * from './voice/replyLanguage';
65
+ export * from './voice/speechPlayback';
62
66
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AAGxB,cAAc,wBAAwB,CAAC;AAKvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,gBAAgB,CAAC;AAK/B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AAGxB,cAAc,wBAAwB,CAAC;AAKvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,gBAAgB,CAAC;AAK/B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAG3C,cAAc,QAAQ,CAAC;AAEvB,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Which language is this reply actually in? (BOFF-7107)
3
+ *
4
+ * The assistant reads replies aloud by choosing a voice for `useI18n().locale` —
5
+ * the UI language. That is the wrong signal the moment the model answers in the
6
+ * language the USER wrote in rather than the one the chrome is set to, which is
7
+ * exactly what the language directive in `assistantPrompt` now asks it to do.
8
+ * A Tamil reply read by an English voice is confident nonsense, and it is the
9
+ * one failure the speech module is built to make impossible.
10
+ *
11
+ * So playback keys on the reply text instead. Deliberately by SCRIPT, not by a
12
+ * statistical language detector:
13
+ *
14
+ * - it is deterministic, dependency-free and costs no bundle;
15
+ * - for the languages HealthyBowl actually serves beyond English — Tamil,
16
+ * Hindi, Arabic, Chinese — the script IS the language, unambiguously;
17
+ * - a detector that can separate en/es/fr/de (`franc` and friends) is ~40KB and
18
+ * still guesses on short strings.
19
+ *
20
+ * The honest limitation: **Latin-script languages cannot be told apart this
21
+ * way.** A Spanish reply in an English UI still gets an English voice. That is a
22
+ * far smaller failure than the cross-script one (an English voice reading
23
+ * Spanish is accented but intelligible; an English voice reading Tamil is
24
+ * noise), and closing it needs a real detector — a deliberate later choice, not
25
+ * an oversight.
26
+ */
27
+ /**
28
+ * The locale whose voice should read `text`, or `null` when the script does not
29
+ * decide it (empty, no letters, or Latin — see the module note).
30
+ */
31
+ export declare function detectScriptLocale(text: string): string | null;
32
+ /**
33
+ * The locale to speak `text` in: the script when it decides, else the app locale.
34
+ *
35
+ * Falling back to the app locale (rather than refusing) keeps every Latin-script
36
+ * reply behaving exactly as it did before this existed.
37
+ */
38
+ /**
39
+ * ⚠️ `appLocale` is the caller's best available signal for "which language did
40
+ * the user mean", and it is only as good as that signal.
41
+ *
42
+ * With the interface language alone it is a weak proxy: an English-UI user who
43
+ * writes in Urdu produces an Urdu reply, but `appLocale` says `en`, so a shared
44
+ * Arabic script falls back to the family default and picks an Arabic voice.
45
+ * BOFF-7108 is what fixes this properly — it lets the user state the language
46
+ * they are speaking, independently of the interface, and passes THAT here.
47
+ *
48
+ * Callers should therefore hand in the most specific language they know: the
49
+ * user's declared conversation language if there is one, the UI locale only as a
50
+ * last resort.
51
+ */
52
+ export declare function resolveSpeechLocale(text: string, appLocale: string): string;
53
+ //# sourceMappingURL=replyLanguage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replyLanguage.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/voice/replyLanguage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AA2EH;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgD9D;AAED;;;;;GAKG;AACH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAU3E"}
@@ -0,0 +1,112 @@
1
+ //#region src/shared/assistant/voice/replyLanguage.ts
2
+ var e = [
3
+ {
4
+ locale: "ja",
5
+ pattern: /[\p{Script=Hiragana}\p{Script=Katakana}]/u
6
+ },
7
+ {
8
+ locale: "ko",
9
+ pattern: /\p{Script=Hangul}/u
10
+ },
11
+ {
12
+ locale: "zh",
13
+ pattern: /\p{Script=Han}/u
14
+ },
15
+ {
16
+ locale: "ta",
17
+ pattern: /\p{Script=Tamil}/u
18
+ },
19
+ {
20
+ locale: "hi",
21
+ pattern: /\p{Script=Devanagari}/u
22
+ },
23
+ {
24
+ locale: "bn",
25
+ pattern: /\p{Script=Bengali}/u
26
+ },
27
+ {
28
+ locale: "te",
29
+ pattern: /\p{Script=Telugu}/u
30
+ },
31
+ {
32
+ locale: "kn",
33
+ pattern: /\p{Script=Kannada}/u
34
+ },
35
+ {
36
+ locale: "ml",
37
+ pattern: /\p{Script=Malayalam}/u
38
+ },
39
+ {
40
+ locale: "gu",
41
+ pattern: /\p{Script=Gujarati}/u
42
+ },
43
+ {
44
+ locale: "pa",
45
+ pattern: /\p{Script=Gurmukhi}/u
46
+ },
47
+ {
48
+ locale: "ar",
49
+ pattern: /\p{Script=Arabic}/u
50
+ },
51
+ {
52
+ locale: "he",
53
+ pattern: /\p{Script=Hebrew}/u
54
+ },
55
+ {
56
+ locale: "ru",
57
+ pattern: /\p{Script=Cyrillic}/u
58
+ },
59
+ {
60
+ locale: "el",
61
+ pattern: /\p{Script=Greek}/u
62
+ }
63
+ ], t = {
64
+ zh: ["zh", "ja"],
65
+ ar: [
66
+ "ar",
67
+ "ur",
68
+ "fa",
69
+ "ps",
70
+ "sd"
71
+ ],
72
+ hi: [
73
+ "hi",
74
+ "mr",
75
+ "ne",
76
+ "sa",
77
+ "kok"
78
+ ],
79
+ ru: [
80
+ "ru",
81
+ "uk",
82
+ "bg",
83
+ "sr",
84
+ "mk",
85
+ "be"
86
+ ]
87
+ }, n = .3, r = .2, i = /\p{L}/u;
88
+ function a(t) {
89
+ if (!t) return null;
90
+ let a = 0, o = /* @__PURE__ */ new Map();
91
+ for (let n of t) if (i.test(n)) {
92
+ a += 1;
93
+ for (let { locale: t, pattern: r } of e) if (r.test(n)) {
94
+ o.set(t, (o.get(t) ?? 0) + 1);
95
+ break;
96
+ }
97
+ }
98
+ if (a === 0) return null;
99
+ let s = o.get("ja") ?? 0, c = o.get("zh") ?? 0;
100
+ s > 0 && s >= (s + c) * r && (o.set("ja", s + c), o.delete("zh"));
101
+ let l = null, u = 0;
102
+ for (let [e, t] of o) t > u && (l = e, u = t);
103
+ return l !== null && u / a >= n ? l : null;
104
+ }
105
+ function o(e, n) {
106
+ let r = a(e);
107
+ if (r === null) return n;
108
+ let i = t[r];
109
+ return i && i.includes(n) ? n : r;
110
+ }
111
+ //#endregion
112
+ export { a as detectScriptLocale, o as resolveSpeechLocale };
@@ -0,0 +1,12 @@
1
+ import { SpeakableLabels } from './speakableText';
2
+ /**
3
+ * Labels to read a reply in `locale` with.
4
+ *
5
+ * `uiLabels` is the interface-language set, used both as the base and as the
6
+ * fallback for any language not carried here — so English replies, and any
7
+ * language we have no table for, behave exactly as before.
8
+ */
9
+ export declare function speechLabelsFor(locale: string, uiLabels: SpeakableLabels): SpeakableLabels;
10
+ /** Exposed for tests: which languages have their own spoken labels. */
11
+ export declare const SPOKEN_LABEL_LANGUAGES: string[];
12
+ //# sourceMappingURL=speechLabels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speechLabels.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/voice/speechLabels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAyGvD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,eAAe,GACxB,eAAe,CAKjB;AAED,uEAAuE;AACvE,eAAO,MAAM,sBAAsB,UAAkC,CAAC"}
@@ -0,0 +1,75 @@
1
+ //#region src/shared/assistant/voice/speechLabels.ts
2
+ var e = {
3
+ en: {
4
+ codeBlock: "Code block",
5
+ image: "image",
6
+ link: "link",
7
+ largeTable: "Table with {{rows}} rows and {{columns}} columns",
8
+ truncated: "The rest of this message is not read aloud",
9
+ nestedItem: "Sub-item {{ordinal}}",
10
+ emailAt: "at",
11
+ emailDot: "dot"
12
+ },
13
+ ta: {
14
+ codeBlock: "நிரல் தொகுதி",
15
+ image: "படம்",
16
+ link: "இணைப்பு",
17
+ largeTable: "{{rows}} வரிசைகள் மற்றும் {{columns}} நெடுவரிசைகள் கொண்ட அட்டவணை",
18
+ truncated: "இந்தச் செய்தியின் மீதமுள்ள பகுதி வாசிக்கப்படவில்லை",
19
+ nestedItem: "துணை உருப்படி {{ordinal}}",
20
+ emailAt: "அட்",
21
+ emailDot: "புள்ளி"
22
+ },
23
+ hi: {
24
+ codeBlock: "कोड ब्लॉक",
25
+ image: "छवि",
26
+ link: "लिंक",
27
+ largeTable: "{{rows}} पंक्तियों और {{columns}} स्तंभों वाली तालिका",
28
+ truncated: "इस संदेश का शेष भाग नहीं पढ़ा जाता",
29
+ nestedItem: "उप-आइटम {{ordinal}}",
30
+ emailAt: "ऐट",
31
+ emailDot: "डॉट"
32
+ },
33
+ ar: {
34
+ codeBlock: "كتلة برمجية",
35
+ image: "صورة",
36
+ link: "رابط",
37
+ largeTable: "جدول من {{rows}} صفوف و {{columns}} أعمدة",
38
+ truncated: "لا تتم قراءة بقية هذه الرسالة بصوت عالٍ",
39
+ nestedItem: "عنصر فرعي {{ordinal}}",
40
+ emailAt: "أت",
41
+ emailDot: "نقطة"
42
+ },
43
+ es: {
44
+ codeBlock: "Bloque de código",
45
+ image: "imagen",
46
+ link: "enlace",
47
+ largeTable: "Tabla con {{rows}} filas y {{columns}} columnas",
48
+ truncated: "El resto de este mensaje no se lee en voz alta",
49
+ nestedItem: "Subelemento {{ordinal}}",
50
+ emailAt: "arroba",
51
+ emailDot: "punto"
52
+ },
53
+ zh: {
54
+ codeBlock: "代码块",
55
+ image: "图片",
56
+ link: "链接",
57
+ largeTable: "包含 {{rows}} 行 {{columns}} 列的表格",
58
+ truncated: "此消息的其余部分不会朗读",
59
+ nestedItem: "子项 {{ordinal}}",
60
+ emailAt: "at",
61
+ emailDot: "点"
62
+ }
63
+ };
64
+ function t(t, n) {
65
+ let r = t.trim().toLowerCase().split(/[-_]/)[0];
66
+ if (!r) return n;
67
+ let i = e[r];
68
+ return i ? {
69
+ ...n,
70
+ ...i
71
+ } : n;
72
+ }
73
+ var n = Object.keys(e);
74
+ //#endregion
75
+ export { n as SPOKEN_LABEL_LANGUAGES, t as speechLabelsFor };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Playback state for the assistant's spoken reply (BOFF-6290).
3
+ *
4
+ * A reducer rather than a pile of `useState` calls, because the awkward part of
5
+ * speech playback is not "is it speaking" — it is that the engine's `end` and
6
+ * `error` events arrive AFTER the state has already moved on:
7
+ *
8
+ * - tapping speak on message B while A is talking cancels A, and A's `end`
9
+ * event lands a tick later. Without a guard it would clear B's speaking state
10
+ * and leave a button that says "stop" next to silence — or worse, silence
11
+ * next to a button that says "speak" while the voice keeps going;
12
+ * - closing the panel cancels playback, and the cancellation itself fires
13
+ * `error` on every queued utterance.
14
+ *
15
+ * Every transition therefore carries a monotonically increasing `requestId`, and
16
+ * a terminal event is only honoured when it belongs to the CURRENT request. That
17
+ * is the whole trick, and it is pure, so it is tested directly (the audio around
18
+ * it is not testable in this repo's node-environment vitest).
19
+ */
20
+ export type SpeechPlaybackState = {
21
+ readonly status: "idle";
22
+ } | {
23
+ readonly status: "speaking";
24
+ readonly messageId: string;
25
+ readonly requestId: number;
26
+ };
27
+ export type SpeechPlaybackAction =
28
+ /** User pressed speak on a message (or auto-speak fired). */
29
+ {
30
+ readonly type: "start";
31
+ readonly messageId: string;
32
+ readonly requestId: number;
33
+ }
34
+ /** User pressed the control on `messageId`: stop if it is the one talking. */
35
+ | {
36
+ readonly type: "toggle";
37
+ readonly messageId: string;
38
+ readonly requestId: number;
39
+ }
40
+ /** Explicit stop: the stop button, panel close, conversation switch, unmount. */
41
+ | {
42
+ readonly type: "stop";
43
+ }
44
+ /** The engine finished the queue for `requestId`. */
45
+ | {
46
+ readonly type: "finished";
47
+ readonly requestId: number;
48
+ }
49
+ /** The engine failed (or was cancelled) on `requestId`. */
50
+ | {
51
+ readonly type: "failed";
52
+ readonly requestId: number;
53
+ };
54
+ export declare const IDLE_PLAYBACK: SpeechPlaybackState;
55
+ export declare function speechPlaybackReducer(state: SpeechPlaybackState, action: SpeechPlaybackAction): SpeechPlaybackState;
56
+ /** Is THIS message the one currently being spoken? Drives one button's state. */
57
+ export declare function isSpeakingMessage(state: SpeechPlaybackState, messageId: string): boolean;
58
+ /**
59
+ * Does acting on `messageId` require cancelling what the engine is doing now?
60
+ *
61
+ * True whenever anything is playing — including the same message, where the tap
62
+ * means "stop". The caller cancels first, then speaks; that ordering is what
63
+ * makes "starting a new playback cancels the previous one" hold with a queue of
64
+ * utterances rather than a single one.
65
+ */
66
+ export declare function requiresCancel(state: SpeechPlaybackState): boolean;
67
+ //# sourceMappingURL=speechPlayback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speechPlayback.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/voice/speechPlayback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3B;IACE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEN,MAAM,MAAM,oBAAoB;AAC9B,6DAA6D;AAC3D;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AACH,8EAA8E;GAC5E;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AACH,iFAAiF;GAC/E;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAC3B,qDAAqD;GACnD;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE;AAC3D,2DAA2D;GACzD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D,eAAO,MAAM,aAAa,EAAE,mBAAwC,CAAC;AAErE,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,mBAAmB,EAC1B,MAAM,EAAE,oBAAoB,GAC3B,mBAAmB,CAgCrB;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,mBAAmB,EAC1B,SAAS,EAAE,MAAM,GAChB,OAAO,CAET;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAElE"}
@@ -0,0 +1,28 @@
1
+ //#region src/shared/assistant/voice/speechPlayback.ts
2
+ var e = { status: "idle" };
3
+ function t(t, n) {
4
+ switch (n.type) {
5
+ case "start": return {
6
+ status: "speaking",
7
+ messageId: n.messageId,
8
+ requestId: n.requestId
9
+ };
10
+ case "toggle": return t.status === "speaking" && t.messageId === n.messageId ? e : {
11
+ status: "speaking",
12
+ messageId: n.messageId,
13
+ requestId: n.requestId
14
+ };
15
+ case "stop": return t.status === "idle" ? t : e;
16
+ case "finished":
17
+ case "failed": return t.status !== "speaking" || t.requestId !== n.requestId ? t : e;
18
+ default: return t;
19
+ }
20
+ }
21
+ function n(e, t) {
22
+ return e.status === "speaking" && e.messageId === t;
23
+ }
24
+ function r(e) {
25
+ return e.status === "speaking";
26
+ }
27
+ //#endregion
28
+ export { e as IDLE_PLAYBACK, n as isSpeakingMessage, r as requiresCancel, t as speechPlaybackReducer };
@@ -26,4 +26,8 @@ import { toCaptionText as kt } from "./shared/assistant/voice/captionText.js";
26
26
  import { ASSISTANT_LANGUAGES as At, DEFAULT_SPEECH_RATE as jt, findAssistantLanguage as Mt, resolveAssistantLanguage as Nt } from "./shared/assistant/voice/languages.js";
27
27
  import { replyFollowsInput as Pt, resolveLanguagePair as Ft } from "./shared/assistant/voice/languagePair.js";
28
28
  import { findPendingWrite as It, parseConfirmationRefusal as Lt } from "./shared/assistant/pendingWrite.js";
29
- export { p as ASSISTANT_API_AUTH_STRATEGY, At as ASSISTANT_LANGUAGES, U as AssistantRuntimeManager, i as CHECK_AI_CREDITS, a as CREATE_SANDBOX, dt as DEFAULT_SPEAKABLE_LABELS, jt as DEFAULT_SPEECH_RATE, Re as ENDPOINT_GRACE_MS, o as EXTEND_SANDBOX_TTL, s as GET_ACTIVE_SANDBOXES, c as GET_SANDBOX, me as HOSTED_TTS_MAX_CHARS, ue as HOSTED_TTS_TIER, de as HOSTED_VOICES, ze as INITIAL_SESSION, Be as INTERRUPT_MAX_MS, Ve as INTERRUPT_MIN_MS, ft as MAX_SPEAKABLE_CHARS, pt as MAX_SPOKEN_TABLE_ROWS, Ct as POLL_FAST_MS, wt as POLL_FAST_UNTIL_MS, Tt as POLL_SLOW_MS, Et as POLL_STEADY_MS, Dt as POLL_STEADY_UNTIL_MS, xt as PREVIEW_LANGUAGES, l as PROXY_SANDBOX_REQUEST, u as REPORT_AI_TOKEN_USAGE, He as SILENCE_SUBMIT_MS, he as SPEAKING_RATE_MAX, ge as SPEAKING_RATE_MAX_STREAMING, _e as SPEAKING_RATE_MAX_SYNC, ve as SPEAKING_RATE_MIN, mt as SPEECH_CHUNK_CHARS, ht as SPEECH_CHUNK_CJK_CHARS, Xe as SPEECH_LANG, d as STOP_ASSISTANT_SANDBOX, Ze as VOICE_LIST_TIMEOUT_MS, f as WAIT_FOR_SANDBOX_RUNNING, ye as base64ToBytes, le as browserSpeechEngine, C as buildAssistantSandboxEnv, ct as buildContinuedFromVoiceNote, W as buildProgress, w as buildPromptRequestBody, be as buildRequest, t as buildSandboxContextInput, lt as buildVoiceTranscriptMarkdown, ne as chatTurns, T as checkAssistantAiCredits, gt as chunkForSpeech, xe as classifyHttpFailure, De as configureSpeechEngine, _t as containsCjk, E as createAssistantSandbox, D as createAssistantSession, Te as createHostedTtsEngine, Ee as createRoutingSpeechEngine, ee as createSandboxAssistantTransport, Oe as createSpeechEngines, Se as decodeAudioResponse, O as downloadAssistantArtifact, n as executeSandboxGraphQL, k as extendAssistantSandboxTTL, Mt as findAssistantLanguage, A as findExistingSandbox, It as findPendingWrite, j as getAssistantArtifacts, G as getAssistantMedia, M as getAssistantMessages, N as getAssistantSession, P as getAssistantStreamAccess, K as getAssistantText, y as getCapability, m as getImageForMode, h as getImageTagForMode, q as getRawMessageCreatedAt, g as getResourcesForMode, _ as getSandboxImageRegistry, ke as getSpeechEngine, J as getToolProgress, re as groupBySession, ie as groupConsecutiveBySession, fe as hostedLocaleFor, pe as hostedSupportsLanguage, Ue as isCapturing, We as isEcho, r as isQuotaExhaustedError, Y as isReapedSandboxConnectionError, X as isRecoverable, Qe as isSpeechBlocked, Ge as isSubmittable, b as isUnknownFieldError, ae as isVoiceTurn, $e as languageDisplayName, Z as mapSessionToHistory, x as markSupported, S as markUnsupported, Q as mergeMessagesById, Me as micIsInterrupt, F as modeRequiresConfirmWrites, et as normalizeLangTag, Ke as orbMode, Lt as parseConfirmationRefusal, qe as planReplyConsumption, Ot as pollDelayMs, tt as primaryLanguageSubtag, Ae as readSpeechEngineConfig, Pt as replyFollowsInput, I as reportAssistantAiTokenUsage, je as resetSpeechEngine, Nt as resolveAssistantLanguage, Ft as resolveLanguagePair, v as resolveSandboxAssistantMode, $ as sanitize, L as selectExistingAssistantSandbox, nt as selectVoiceForLocale, R as sendAssistantPromptAsync, z as sendHeartbeat, Ce as shouldDisableEngine, Ne as showsAssistantCaption, Pe as showsUserCaption, vt as speakableEmail, rt as speechGate, it as speechLangForLocale, we as splitForVendor, yt as splitTableRow, Je as spokenSoFar, B as stopReapedAssistantSandbox, kt as toCaptionText, bt as toSpeakableText, te as useSandboxAssistantTransport, e as useVoiceInput, at as voiceAvailability, oe as voiceMarker, St as voicePreviewText, ot as voiceQualityRank, se as voiceSessionIdOf, Ye as voiceSessionReducer, Fe as voiceStageState, ut as voiceTranscriptFilename, ce as voiceTurnsOnly, st as voicesForLocale, V as waitForAssistantServiceReady, H as waitForSandboxRunning, Ie as waveformIsActive, Le as waveformIsSettled };
29
+ import { AssistantI18nProvider as Rt, assistantTranslationKey as zt, useAssistantI18nNamespace as Bt, useAssistantTr as Vt } from "./shared/assistant/i18n.js";
30
+ import { SPOKEN_LABEL_LANGUAGES as Ht, speechLabelsFor as Ut } from "./shared/assistant/voice/speechLabels.js";
31
+ import { detectScriptLocale as Wt, resolveSpeechLocale as Gt } from "./shared/assistant/voice/replyLanguage.js";
32
+ import { IDLE_PLAYBACK as Kt, isSpeakingMessage as qt, requiresCancel as Jt, speechPlaybackReducer as Yt } from "./shared/assistant/voice/speechPlayback.js";
33
+ export { p as ASSISTANT_API_AUTH_STRATEGY, At as ASSISTANT_LANGUAGES, Rt as AssistantI18nProvider, U as AssistantRuntimeManager, i as CHECK_AI_CREDITS, a as CREATE_SANDBOX, dt as DEFAULT_SPEAKABLE_LABELS, jt as DEFAULT_SPEECH_RATE, Re as ENDPOINT_GRACE_MS, o as EXTEND_SANDBOX_TTL, s as GET_ACTIVE_SANDBOXES, c as GET_SANDBOX, me as HOSTED_TTS_MAX_CHARS, ue as HOSTED_TTS_TIER, de as HOSTED_VOICES, Kt as IDLE_PLAYBACK, ze as INITIAL_SESSION, Be as INTERRUPT_MAX_MS, Ve as INTERRUPT_MIN_MS, ft as MAX_SPEAKABLE_CHARS, pt as MAX_SPOKEN_TABLE_ROWS, Ct as POLL_FAST_MS, wt as POLL_FAST_UNTIL_MS, Tt as POLL_SLOW_MS, Et as POLL_STEADY_MS, Dt as POLL_STEADY_UNTIL_MS, xt as PREVIEW_LANGUAGES, l as PROXY_SANDBOX_REQUEST, u as REPORT_AI_TOKEN_USAGE, He as SILENCE_SUBMIT_MS, he as SPEAKING_RATE_MAX, ge as SPEAKING_RATE_MAX_STREAMING, _e as SPEAKING_RATE_MAX_SYNC, ve as SPEAKING_RATE_MIN, mt as SPEECH_CHUNK_CHARS, ht as SPEECH_CHUNK_CJK_CHARS, Xe as SPEECH_LANG, Ht as SPOKEN_LABEL_LANGUAGES, d as STOP_ASSISTANT_SANDBOX, Ze as VOICE_LIST_TIMEOUT_MS, f as WAIT_FOR_SANDBOX_RUNNING, zt as assistantTranslationKey, ye as base64ToBytes, le as browserSpeechEngine, C as buildAssistantSandboxEnv, ct as buildContinuedFromVoiceNote, W as buildProgress, w as buildPromptRequestBody, be as buildRequest, t as buildSandboxContextInput, lt as buildVoiceTranscriptMarkdown, ne as chatTurns, T as checkAssistantAiCredits, gt as chunkForSpeech, xe as classifyHttpFailure, De as configureSpeechEngine, _t as containsCjk, E as createAssistantSandbox, D as createAssistantSession, Te as createHostedTtsEngine, Ee as createRoutingSpeechEngine, ee as createSandboxAssistantTransport, Oe as createSpeechEngines, Se as decodeAudioResponse, Wt as detectScriptLocale, O as downloadAssistantArtifact, n as executeSandboxGraphQL, k as extendAssistantSandboxTTL, Mt as findAssistantLanguage, A as findExistingSandbox, It as findPendingWrite, j as getAssistantArtifacts, G as getAssistantMedia, M as getAssistantMessages, N as getAssistantSession, P as getAssistantStreamAccess, K as getAssistantText, y as getCapability, m as getImageForMode, h as getImageTagForMode, q as getRawMessageCreatedAt, g as getResourcesForMode, _ as getSandboxImageRegistry, ke as getSpeechEngine, J as getToolProgress, re as groupBySession, ie as groupConsecutiveBySession, fe as hostedLocaleFor, pe as hostedSupportsLanguage, Ue as isCapturing, We as isEcho, r as isQuotaExhaustedError, Y as isReapedSandboxConnectionError, X as isRecoverable, qt as isSpeakingMessage, Qe as isSpeechBlocked, Ge as isSubmittable, b as isUnknownFieldError, ae as isVoiceTurn, $e as languageDisplayName, Z as mapSessionToHistory, x as markSupported, S as markUnsupported, Q as mergeMessagesById, Me as micIsInterrupt, F as modeRequiresConfirmWrites, et as normalizeLangTag, Ke as orbMode, Lt as parseConfirmationRefusal, qe as planReplyConsumption, Ot as pollDelayMs, tt as primaryLanguageSubtag, Ae as readSpeechEngineConfig, Pt as replyFollowsInput, I as reportAssistantAiTokenUsage, Jt as requiresCancel, je as resetSpeechEngine, Nt as resolveAssistantLanguage, Ft as resolveLanguagePair, v as resolveSandboxAssistantMode, Gt as resolveSpeechLocale, $ as sanitize, L as selectExistingAssistantSandbox, nt as selectVoiceForLocale, R as sendAssistantPromptAsync, z as sendHeartbeat, Ce as shouldDisableEngine, Ne as showsAssistantCaption, Pe as showsUserCaption, vt as speakableEmail, rt as speechGate, Ut as speechLabelsFor, it as speechLangForLocale, Yt as speechPlaybackReducer, we as splitForVendor, yt as splitTableRow, Je as spokenSoFar, B as stopReapedAssistantSandbox, kt as toCaptionText, bt as toSpeakableText, Bt as useAssistantI18nNamespace, Vt as useAssistantTr, te as useSandboxAssistantTransport, e as useVoiceInput, at as voiceAvailability, oe as voiceMarker, St as voicePreviewText, ot as voiceQualityRank, se as voiceSessionIdOf, Ye as voiceSessionReducer, Fe as voiceStageState, ut as voiceTranscriptFilename, ce as voiceTurnsOnly, st as voicesForLocale, V as waitForAssistantServiceReady, H as waitForSandboxRunning, Ie as waveformIsActive, Le as waveformIsSettled };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/fe-libs",
3
- "version": "2026.903.10",
3
+ "version": "2026.904.2",
4
4
  "description": "Burdenoff frontend primitives and domain libraries",
5
5
  "type": "module",
6
6
  "bin": {