@burdenoff/fe-libs 2026.1014.5 → 2026.1015.1

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 (39) hide show
  1. package/dist/shared/assistant/avatar/avatarPreferences.d.ts +61 -14
  2. package/dist/shared/assistant/avatar/avatarPreferences.js +54 -21
  3. package/dist/shared/assistant/avatar/live/LiveAvatarTabView.d.ts +33 -0
  4. package/dist/shared/assistant/avatar/live/LiveAvatarTabView.js +278 -0
  5. package/dist/shared/assistant/avatar/live/avatarSessionClient.d.ts +5 -0
  6. package/dist/shared/assistant/avatar/live/avatarSessionClient.js +25 -6
  7. package/dist/shared/assistant/avatar/live/avatarVoice.d.ts +43 -0
  8. package/dist/shared/assistant/avatar/live/avatarVoice.js +45 -0
  9. package/dist/shared/assistant/avatar/live/createLiveAvatarSeam.d.ts +21 -1
  10. package/dist/shared/assistant/avatar/live/createLiveAvatarSeam.js +74 -24
  11. package/dist/shared/assistant/avatar/live/liveAvatarController.d.ts +47 -8
  12. package/dist/shared/assistant/avatar/live/liveAvatarController.js +244 -161
  13. package/dist/shared/assistant/avatar/live/liveAvatarStatus.d.ts +2 -2
  14. package/dist/shared/assistant/avatar/live/liveAvatarStatus.js +11 -4
  15. package/dist/shared/assistant/avatar/useAvatarCopy.d.ts +50 -21
  16. package/dist/shared/assistant/avatar/useAvatarCopy.js +93 -49
  17. package/dist/shared/assistant/i18nManifest.d.ts +23 -2
  18. package/dist/shared/assistant/i18nManifest.js +23 -2
  19. package/dist/shared/assistant/index.d.ts +5 -0
  20. package/dist/shared/assistant/speech/hostedVoiceCatalogue.d.ts +19 -0
  21. package/dist/shared/assistant/speech/hostedVoiceCatalogue.js +25 -18
  22. package/dist/shared/assistant/ui/AIAssistantPanel.js +134 -101
  23. package/dist/shared/assistant/ui/AssistantAvatarTabs.d.ts +10 -0
  24. package/dist/shared/assistant/ui/AssistantAvatarTabs.js +48 -0
  25. package/dist/shared/assistant/ui/AssistantChatArea.js +1260 -1199
  26. package/dist/shared/assistant/ui/assistantLiveAvatarSeam.d.ts +137 -5
  27. package/dist/shared/assistant/ui/assistantLiveAvatarSeam.js +114 -55
  28. package/dist/shared/assistant/ui/avatar/AssistantAvatarTab.d.ts +13 -0
  29. package/dist/shared/assistant/ui/avatar/AssistantAvatarTab.js +70 -0
  30. package/dist/shared/assistant/ui/composer/AssistantComposer.d.ts +18 -0
  31. package/dist/shared/assistant/ui/composer/AssistantComposer.js +121 -102
  32. package/dist/shared/assistant/ui/panelOverlays.d.ts +24 -3
  33. package/dist/shared/assistant/ui/panelOverlays.js +11 -10
  34. package/dist/shared/assistant/ui/voice/AssistantVoiceSession.d.ts +6 -0
  35. package/dist/shared/assistant/ui/voice/AssistantVoiceSession.js +172 -166
  36. package/dist/shared/assistant/ui/voice/voiceStartGate.d.ts +57 -0
  37. package/dist/shared/assistant/ui/voice/voiceStartGate.js +9 -0
  38. package/dist/shared-assistant.js +259 -254
  39. package/package.json +1 -1
@@ -1,290 +1,296 @@
1
1
  import { useReturnFocus as e } from "../../../hooks/useReturnFocus.js";
2
2
  import { useAssistantControl as t } from "../../appControls/useAssistantControl.js";
3
3
  import { useAssistantTr as n } from "../../i18n.js";
4
- import { micIsInterrupt as r, showsAssistantCaption as ee, voiceStageState as i } from "../../voice/voiceStageState.js";
5
- import { VOICE_THINKING_LABEL as a, composeVoiceStatus as te, voiceStatusSuffix as ne } from "../../voice/voiceStatus.js";
6
- import { spokenSoFar as re } from "../../voice/voiceSession.js";
7
- import { useAvatarCopy as ie } from "../../avatar/useAvatarCopy.js";
8
- import { ASSISTANT_PANEL_ATTR as ae, ASSISTANT_VOICE_SESSION_ATTR as oe } from "../panelOverlays.js";
9
- import { useLiveAvatarHandsFreeGuard as se, useLiveAvatarVoiceView as ce } from "../assistantLiveAvatarSeam.js";
10
- import { AssistantAttachmentButton as le } from "../composer/AssistantAttachmentButton.js";
11
- import { AssistantAttachmentChips as ue } from "../composer/AssistantAttachmentChips.js";
12
- import { AssistantVoiceOrb as de } from "./AssistantVoiceOrb.js";
13
- import { VoiceMiniController as fe } from "./VoiceMiniController.js";
14
- import { VoiceStage as pe } from "./VoiceStage.js";
15
- import { VoiceTranscriptDrawer as me } from "./VoiceTranscriptDrawer.js";
16
- import { escapeBelongsToLayerAbove as he, voiceSessionEscapeAction as ge } from "./voiceSessionEscape.js";
17
- import { HandsFreePrivacyNotice as _e } from "./HandsFreePrivacyNotice.js";
18
- import { useCallback as o, useEffect as s, useMemo as ve, useRef as c, useState as l } from "react";
19
- import { Maximize2 as ye, Minimize2 as be, ScrollText as xe, X as u } from "lucide-react";
20
- import { jsx as d, jsxs as f } from "react/jsx-runtime";
4
+ import { micIsInterrupt as r, showsAssistantCaption as ee, voiceStageState as te } from "../../voice/voiceStageState.js";
5
+ import { VOICE_THINKING_LABEL as i, composeVoiceStatus as ne, voiceStatusSuffix as re } from "../../voice/voiceStatus.js";
6
+ import { spokenSoFar as ie } from "../../voice/voiceSession.js";
7
+ import { useAvatarCopy as ae } from "../../avatar/useAvatarCopy.js";
8
+ import { ASSISTANT_PANEL_ATTR as oe, ASSISTANT_VOICE_SESSION_ATTR as se } from "../panelOverlays.js";
9
+ import { useLiveAvatarHandsFreeGuard as ce, useLiveAvatarTab as le, useLiveAvatarVoiceView as ue } from "../assistantLiveAvatarSeam.js";
10
+ import { AssistantAttachmentButton as de } from "../composer/AssistantAttachmentButton.js";
11
+ import { AssistantAttachmentChips as fe } from "../composer/AssistantAttachmentChips.js";
12
+ import { AssistantVoiceOrb as pe } from "./AssistantVoiceOrb.js";
13
+ import { VoiceMiniController as me } from "./VoiceMiniController.js";
14
+ import { VoiceStage as he } from "./VoiceStage.js";
15
+ import { VoiceTranscriptDrawer as ge } from "./VoiceTranscriptDrawer.js";
16
+ import { escapeBelongsToLayerAbove as _e, voiceSessionEscapeAction as ve } from "./voiceSessionEscape.js";
17
+ import { HandsFreePrivacyNotice as ye } from "./HandsFreePrivacyNotice.js";
18
+ import { useCallback as a, useEffect as o, useMemo as be, useRef as s, useState as c } from "react";
19
+ import { Maximize2 as xe, Minimize2 as Se, ScrollText as Ce, X as l } from "lucide-react";
20
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
21
21
  //#region src/shared/assistant/ui/voice/AssistantVoiceSession.tsx
22
- var p = ({ session: p, turns: we = [], onSaveTranscript: Te, onContinueInChat: Ee, notice: m = null, privacyNotice: h = null, spoken: g, onInterrupt: De, onToggleMute: _, attachments: v = [], onAttachFiles: y, onRemoveAttachment: Oe, onRetryAttachment: ke, canAttachMore: Ae = !0, preparingAttachments: b = 0, attachmentsLocked: x = !1, attachmentNotice: S = null, spokenChunks: C = null, onEnd: w, capturing: je = !0, interrupting: Me = !1, awaitingEndpoint: Ne = !1, holding: T = !1, correcting: Pe = !1, onLetMeFinish: E, onImDone: D, proposalChip: O = null, proposalTray: k = null, renderAfterTurn: Fe }) => {
23
- let A = n(), { muted: j, error: M } = p, N = c(null), P = c(null), [Ie, F] = l("mini"), I = ce(Ie, p.phase === "ended"), Le = se(), L = ie(), R = I === "full", z = I === "mini", B = I === "mini" ? "panel" : I === "panel" ? "full" : "mini", [V, H] = l(!1);
24
- t("assistant.transcript", ve(() => ({ run: () => V ? { status: "already" } : (H(!0), { status: "done" }) }), [V]));
25
- let U = k !== null && !z, W = c(!1), G = c(_);
26
- G.current = _;
27
- let K = o(() => {
28
- p.phase !== "listening" && p.phase !== "thinking" || j || W.current || re(p) || (W.current = !0, G.current());
29
- }, [j, p]), q = o(() => {
30
- W.current && (W.current = !1, G.current());
31
- }, []), Re = K;
32
- s(() => {
22
+ var f = ({ session: f, turns: Te = [], onSaveTranscript: Ee, onContinueInChat: De, notice: m = null, privacyNotice: h = null, spoken: g, onInterrupt: Oe, onToggleMute: _, attachments: v = [], onAttachFiles: y, onRemoveAttachment: ke, onRetryAttachment: Ae, canAttachMore: je = !0, preparingAttachments: Me = 0, attachmentsLocked: b = !1, attachmentNotice: x = null, spokenChunks: S = null, onEnd: C, capturing: Ne = !0, interrupting: Pe = !1, awaitingEndpoint: Fe = !1, holding: w = !1, correcting: Ie = !1, onLetMeFinish: T, onImDone: E, proposalChip: D = null, proposalTray: O = null, renderAfterTurn: Le, onViewChange: k }) => {
23
+ let A = n(), { muted: j, error: M } = f, N = s(null), P = s(null), [F, I] = c("mini"), L = le().open, [Re, ze] = c(L);
24
+ Re !== L && (ze(L), L && F !== "mini" && I("mini"));
25
+ let R = ue(F, f.phase === "ended"), Be = ce();
26
+ o(() => {
27
+ k?.(R);
28
+ }, [k, R]);
29
+ let Ve = ae(), z = R === "full", B = R === "mini", V = R === "mini" ? "panel" : R === "panel" ? "full" : "mini", [H, U] = c(!1);
30
+ t("assistant.transcript", be(() => ({ run: () => H ? { status: "already" } : (U(!0), { status: "done" }) }), [H]));
31
+ let W = O !== null && !B, G = s(!1), K = s(_);
32
+ K.current = _;
33
+ let q = a(() => {
34
+ f.phase !== "listening" && f.phase !== "thinking" || j || G.current || ie(f) || (G.current = !0, K.current());
35
+ }, [j, f]), J = a(() => {
36
+ G.current && (G.current = !1, K.current());
37
+ }, []), He = q;
38
+ o(() => {
33
39
  let e = () => {
34
- document.visibilityState === "hidden" ? K() : q();
40
+ document.visibilityState === "hidden" ? q() : J();
35
41
  };
36
42
  return document.addEventListener("visibilitychange", e), () => document.removeEventListener("visibilitychange", e);
37
- }, [K, q]), s(() => {
38
- let e = q;
43
+ }, [q, J]), o(() => {
44
+ let e = J;
39
45
  return window.addEventListener("focus", e), () => {
40
- window.removeEventListener("focus", e), W.current = !1;
46
+ window.removeEventListener("focus", e), G.current = !1;
41
47
  };
42
- }, []), s(() => {
48
+ }, []), o(() => {
43
49
  document.activeElement?.closest("[data-hands-free-notice]") || N.current?.focus();
44
- }, []), e(P, { fallbackSelector: `[${ae}]` }), s(() => {
50
+ }, []), e(P, { fallbackSelector: `[${oe}]` }), o(() => {
45
51
  let e = (e) => {
46
52
  if (e.key === "Escape") {
47
- let t = ge({
53
+ let t = ve({
48
54
  defaultPrevented: e.defaultPrevented,
49
- layerAbove: he({
55
+ layerAbove: _e({
50
56
  target: e.target,
51
57
  session: P.current,
52
58
  doc: document
53
59
  }),
54
- transcriptOpen: V
60
+ transcriptOpen: H
55
61
  });
56
62
  if (t === "ignore") return;
57
- e.preventDefault(), t === "close-transcript" ? H(!1) : h ? h.decline() : U ? k?.onClose() : w();
63
+ e.preventDefault(), t === "close-transcript" ? U(!1) : h ? h.decline() : W ? O?.onClose() : C();
58
64
  return;
59
65
  }
60
- if (e.key !== "Tab" || !R) return;
61
- let t = (V ? P.current?.querySelector("[data-testid=\"assistant-voice-transcript\"]") : P.current)?.querySelectorAll("button:not([disabled])");
66
+ if (e.key !== "Tab" || !z) return;
67
+ let t = (H ? P.current?.querySelector("[data-testid=\"assistant-voice-transcript\"]") : P.current)?.querySelectorAll("button:not([disabled])");
62
68
  if (!t || t.length === 0) return;
63
69
  let n = t[0], r = t[t.length - 1];
64
70
  !n || !r || (e.shiftKey && document.activeElement === n ? (e.preventDefault(), r.focus()) : !e.shiftKey && document.activeElement === r && (e.preventDefault(), n.focus()));
65
71
  };
66
72
  return window.addEventListener("keydown", e), () => window.removeEventListener("keydown", e);
67
73
  }, [
68
- w,
69
- R,
70
- V,
71
- U,
72
- k,
74
+ C,
75
+ z,
76
+ H,
77
+ W,
78
+ O,
73
79
  h
74
80
  ]);
75
- let J = m !== null && M === null, Y = Le && m === null && M === null, ze = re(p), X = E && D && p.phase === "listening" && !j && M === null ? {
76
- letMeFinish: E,
77
- imDone: D
78
- } : null, Be = X !== null;
79
- s(() => {
80
- if (Be) return;
81
+ let Y = m !== null && M === null, X = Be && m === null && M === null, Ue = ie(f), Z = T && E && f.phase === "listening" && !j && M === null ? {
82
+ letMeFinish: T,
83
+ imDone: E
84
+ } : null, We = Z !== null;
85
+ o(() => {
86
+ if (We) return;
81
87
  let e = document.activeElement;
82
88
  (!e || e === document.body) && N.current?.focus();
83
- }, [Be]);
84
- let Z = i({
85
- snapshot: p,
86
- capturing: je,
87
- interrupting: Me,
88
- awaitingEndpoint: Ne
89
- }), Ve = (() => {
90
- switch (Z) {
89
+ }, [We]);
90
+ let Q = te({
91
+ snapshot: f,
92
+ capturing: Ne,
93
+ interrupting: Pe,
94
+ awaitingEndpoint: Fe
95
+ }), Ge = (() => {
96
+ switch (Q) {
91
97
  case "connecting": return A("voiceSession.connecting", "Starting the microphone…");
92
98
  case "endpoint-grace": return A("voiceSession.endpointGrace", "Still listening — take your time");
93
99
  case "interrupting": return A("voiceSession.interrupting", "Stopped — go ahead");
94
- case "thinking": return A(a.key, a.fallback);
100
+ case "thinking": return A(i.key, i.fallback);
95
101
  case "speaking": return A("voiceSession.speaking", "Speaking — tap to stop");
96
102
  case "mic-error":
97
103
  case "ended": return "";
98
104
  case "listening": return j ? A("voiceSession.muted", "Microphone off") : A("voiceSession.listening", "Listening…");
99
- default: return Z;
105
+ default: return Q;
100
106
  }
101
- })(), He = !j && (Z === "listening" || Z === "endpoint-grace"), Ue = He && T ? A("voiceSession.holding", "Take your time — tap “I’m done” when you finish") : He && Pe ? A("voiceSession.correcting", "Stopped that answer — listening to your correction") : Ve, Q = ne({
107
+ })(), Ke = !j && (Q === "listening" || Q === "endpoint-grace"), qe = Ke && w ? A("voiceSession.holding", "Take your time — tap “I’m done” when you finish") : Ke && Ie ? A("voiceSession.correcting", "Stopped that answer — listening to your correction") : Ge, $ = re({
102
108
  tr: A,
103
- stage: Z,
109
+ stage: Q,
104
110
  muted: j,
105
- label: Ue
106
- }), We = te(Ue, Q), $ = Z === "thinking";
107
- return /* @__PURE__ */ f("div", {
111
+ label: qe
112
+ }), Je = ne(qe, $), Ye = Q === "thinking";
113
+ return /* @__PURE__ */ d("div", {
108
114
  ref: P,
109
- className: `flex flex-col items-center justify-between bg-bg-sunken ${I === "full" ? "fixed inset-x-0 bottom-0 top-14 z-[60] px-6 py-8" : I === "panel" ? "absolute inset-0 z-20 px-4 py-6" : "absolute inset-x-0 bottom-0 z-20 min-h-[11.25rem] border-t border-border-subtle px-3 py-3 shadow-lg"}`,
115
+ className: `flex flex-col items-center justify-between bg-bg-sunken ${R === "full" ? "fixed inset-x-0 bottom-0 top-14 z-[60] px-6 py-8" : R === "panel" ? "absolute inset-0 z-20 px-4 py-6" : "absolute inset-x-0 bottom-0 z-20 min-h-[11.25rem] border-t border-border-subtle px-3 py-3 shadow-lg"}`,
110
116
  role: "dialog",
111
- "aria-modal": R || void 0,
117
+ "aria-modal": z || void 0,
112
118
  "aria-label": A("voiceSession.title", "Voice conversation"),
113
119
  "data-testid": "assistant-voice-session",
114
- [oe]: "",
120
+ [se]: "",
115
121
  children: [
116
- /* @__PURE__ */ f("button", {
122
+ /* @__PURE__ */ d("button", {
117
123
  ref: N,
118
124
  type: "button",
119
- onClick: w,
120
- className: `absolute ${R ? "right-6 top-6 h-14 gap-2 px-5 text-base" : z ? "right-12 top-2 h-8 gap-1.5 px-2.5 text-xs" : "right-3 top-3 h-11 gap-2 px-4 text-sm"} flex items-center justify-center rounded-full border border-border-default bg-bg-elevated text-text-secondary hover:bg-bg-sunken transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong`,
125
+ onClick: C,
126
+ className: `absolute ${z ? "right-6 top-6 h-14 gap-2 px-5 text-base" : B ? "right-12 top-2 h-8 gap-1.5 px-2.5 text-xs" : "right-3 top-3 h-11 gap-2 px-4 text-sm"} flex items-center justify-center rounded-full border border-border-default bg-bg-elevated text-text-secondary hover:bg-bg-sunken transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong`,
121
127
  title: A("voiceSession.end", "End the voice conversation"),
122
128
  "data-testid": "assistant-voice-end",
123
- children: [/* @__PURE__ */ d(u, {
124
- className: R ? "size-6" : z ? "size-4" : "size-5",
129
+ children: [/* @__PURE__ */ u(l, {
130
+ className: z ? "size-6" : B ? "size-4" : "size-5",
125
131
  "aria-hidden": "true"
126
- }), /* @__PURE__ */ d("span", {
127
- className: `truncate whitespace-nowrap font-medium ${z ? "max-w-[3.5rem] text-xs" : "max-w-[7rem]"}`,
132
+ }), /* @__PURE__ */ u("span", {
133
+ className: `truncate whitespace-nowrap font-medium ${B ? "max-w-[3.5rem] text-xs" : "max-w-[7rem]"}`,
128
134
  children: A("voiceSession.endShort", "End")
129
135
  })]
130
136
  }),
131
- /* @__PURE__ */ f("div", {
132
- className: `flex flex-col items-center ${z ? `px-3 ${J || Y || S ? "pt-9 pb-2" : ""}` : `min-h-[1.5rem] ${J || Y || S ? R ? "pt-14" : "pt-10" : ""}`}`,
137
+ /* @__PURE__ */ d("div", {
138
+ className: `flex flex-col items-center ${B ? `px-3 ${Y || X || x ? "pt-9 pb-2" : ""}` : `min-h-[1.5rem] ${Y || X || x ? z ? "pt-14" : "pt-10" : ""}`}`,
133
139
  children: [
134
- J ? /* @__PURE__ */ d("p", {
140
+ Y ? /* @__PURE__ */ u("p", {
135
141
  className: "max-w-xs text-center text-xs text-status-warning",
136
142
  "data-testid": "assistant-voice-notice",
137
- children: Se(m, A)
143
+ children: p(m, A)
138
144
  }) : null,
139
- Y ? /* @__PURE__ */ d("p", {
145
+ X ? /* @__PURE__ */ u("p", {
140
146
  className: "max-w-xs text-center text-xs text-text-secondary",
141
147
  "data-testid": "assistant-voice-avatar-hands-free",
142
- children: L.handsFreeOff
148
+ children: Ve.handsFreeOff
143
149
  }) : null,
144
- /* @__PURE__ */ d("span", {
150
+ /* @__PURE__ */ u("span", {
145
151
  className: "sr-only start-0 top-0",
146
152
  role: "status",
147
153
  "aria-live": "polite",
148
154
  "aria-atomic": "true",
149
155
  "data-testid": "assistant-voice-notice-announced",
150
- children: J && m === "reply-not-heard" ? Se(m, A) : Y ? L.handsFreeOff : null
156
+ children: Y && m === "reply-not-heard" ? p(m, A) : X ? Ve.handsFreeOff : null
151
157
  }),
152
- S ? /* @__PURE__ */ d("p", {
158
+ x ? /* @__PURE__ */ u("p", {
153
159
  className: "max-w-xs text-center text-xs text-status-warning",
154
160
  role: "status",
155
161
  "aria-live": "polite",
156
162
  "data-testid": "assistant-voice-attachment-notice",
157
- children: S
163
+ children: x
158
164
  }) : null
159
165
  ]
160
166
  }),
161
167
  (() => {
162
- let e = /* @__PURE__ */ d(de, {
163
- session: p,
164
- compact: z,
165
- connecting: Z === "connecting",
166
- onPress: r(Z) ? De : _,
167
- label: r(Z) ? A("voiceSession.interrupt", "Stop the assistant and speak") : j ? A("voiceSession.unmute", "Turn the microphone on") : A("voiceSession.mute", "Turn the microphone off")
168
- }), t = X ? /* @__PURE__ */ d("button", {
168
+ let e = /* @__PURE__ */ u(pe, {
169
+ session: f,
170
+ compact: B,
171
+ connecting: Q === "connecting",
172
+ onPress: r(Q) ? Oe : _,
173
+ label: r(Q) ? A("voiceSession.interrupt", "Stop the assistant and speak") : j ? A("voiceSession.unmute", "Turn the microphone on") : A("voiceSession.mute", "Turn the microphone off")
174
+ }), t = Z ? /* @__PURE__ */ u("button", {
169
175
  type: "button",
170
- onClick: T ? X.imDone : X.letMeFinish,
171
- className: `flex items-center justify-center rounded-full border font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong ${z ? "h-7 px-3 text-xs" : R ? "h-11 px-6 text-base" : "h-9 px-4 text-sm"} ${T ? "border-transparent bg-action-primary-bg text-action-primary-text hover:opacity-90" : "border-border-default bg-bg-elevated text-text-secondary hover:bg-bg-sunken"}`,
172
- title: T ? A("voiceSession.imDoneHint", "Send what you said now") : A("voiceSession.letMeFinishHint", "Pause sending until you tap “I’m done”"),
176
+ onClick: w ? Z.imDone : Z.letMeFinish,
177
+ className: `flex items-center justify-center rounded-full border font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong ${B ? "h-7 px-3 text-xs" : z ? "h-11 px-6 text-base" : "h-9 px-4 text-sm"} ${w ? "border-transparent bg-action-primary-bg text-action-primary-text hover:opacity-90" : "border-border-default bg-bg-elevated text-text-secondary hover:bg-bg-sunken"}`,
178
+ title: w ? A("voiceSession.imDoneHint", "Send what you said now") : A("voiceSession.letMeFinishHint", "Pause sending until you tap “I’m done”"),
173
179
  "data-testid": "assistant-voice-hold",
174
- "data-holding": T ? "true" : "false",
175
- children: T ? A("voiceSession.imDone", "I’m done") : A("voiceSession.letMeFinish", "Let me finish")
176
- }) : null, n = t ? /* @__PURE__ */ f("div", {
180
+ "data-holding": w ? "true" : "false",
181
+ children: w ? A("voiceSession.imDone", "I’m done") : A("voiceSession.letMeFinish", "Let me finish")
182
+ }) : null, n = t ? /* @__PURE__ */ d("div", {
177
183
  className: "flex flex-col items-center gap-2",
178
184
  children: [e, t]
179
- }) : e, i = ee(Z) ? g : ze || null, a = M === null ? null : Ce(M, A);
180
- return z ? /* @__PURE__ */ d(fe, {
181
- spokenChunks: C,
182
- state: Z,
183
- caption: i,
184
- statusText: We,
185
- statusSuffix: Q,
186
- rotateStatus: $,
187
- errorText: a,
185
+ }) : e, te = ee(Q) ? g : Ue || null, i = M === null ? null : we(M, A);
186
+ return B ? /* @__PURE__ */ u(me, {
187
+ spokenChunks: S,
188
+ state: Q,
189
+ caption: te,
190
+ statusText: Je,
191
+ statusSuffix: $,
192
+ rotateStatus: Ye,
193
+ errorText: i,
188
194
  control: n
189
- }) : /* @__PURE__ */ d(pe, {
190
- state: Z,
191
- userCaption: ze || null,
192
- spokenChunks: C,
195
+ }) : /* @__PURE__ */ u(he, {
196
+ state: Q,
197
+ userCaption: Ue || null,
198
+ spokenChunks: S,
193
199
  assistantCaption: g,
194
- expanded: R,
195
- statusText: We,
196
- statusSuffix: Q,
197
- rotateStatus: $,
198
- errorText: a,
200
+ expanded: z,
201
+ statusText: Je,
202
+ statusSuffix: $,
203
+ rotateStatus: Ye,
204
+ errorText: i,
199
205
  control: n
200
206
  });
201
207
  })(),
202
- O && !U ? /* @__PURE__ */ d("div", {
203
- className: z ? "absolute left-20 right-36 top-2 flex justify-center" : `flex w-full justify-center ${R ? "max-w-[calc(100%-12rem)]" : "max-w-[calc(100%-8rem)]"}`,
204
- children: /* @__PURE__ */ d("button", {
208
+ D && !W ? /* @__PURE__ */ u("div", {
209
+ className: B ? "absolute left-20 right-36 top-2 flex justify-center" : `flex w-full justify-center ${z ? "max-w-[calc(100%-12rem)]" : "max-w-[calc(100%-8rem)]"}`,
210
+ children: /* @__PURE__ */ u("button", {
205
211
  type: "button",
206
212
  onClick: () => {
207
- F("panel"), O.onOpen();
213
+ I("panel"), D.onOpen();
208
214
  },
209
- className: `max-w-full truncate rounded-full border border-border-default bg-bg-elevated font-medium text-text-primary transition-colors hover:bg-bg-raised focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong ${z ? "h-8 px-3 text-xs" : "h-10 px-4 text-sm"}`,
215
+ className: `max-w-full truncate rounded-full border border-border-default bg-bg-elevated font-medium text-text-primary transition-colors hover:bg-bg-raised focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong ${B ? "h-8 px-3 text-xs" : "h-10 px-4 text-sm"}`,
210
216
  "data-testid": "assistant-voice-proposal-chip",
211
- children: O.label
217
+ children: D.label
212
218
  })
213
219
  }) : null,
214
- k && U ? /* @__PURE__ */ f("div", {
215
- className: `absolute z-10 flex flex-col gap-2 overflow-y-auto rounded-lg border border-border-subtle bg-bg-elevated shadow-lg ${R ? "inset-x-6 bottom-24 top-24 mx-auto max-w-xl p-4" : "inset-x-3 bottom-16 top-16 p-3"}`,
220
+ O && W ? /* @__PURE__ */ d("div", {
221
+ className: `absolute z-10 flex flex-col gap-2 overflow-y-auto rounded-lg border border-border-subtle bg-bg-elevated shadow-lg ${z ? "inset-x-6 bottom-24 top-24 mx-auto max-w-xl p-4" : "inset-x-3 bottom-16 top-16 p-3"}`,
216
222
  "data-testid": "assistant-voice-proposal-tray",
217
- children: [/* @__PURE__ */ d("div", {
223
+ children: [/* @__PURE__ */ u("div", {
218
224
  className: "flex justify-end",
219
- children: /* @__PURE__ */ d("button", {
225
+ children: /* @__PURE__ */ u("button", {
220
226
  type: "button",
221
- onClick: k.onClose,
227
+ onClick: O.onClose,
222
228
  className: "flex size-8 items-center justify-center rounded-full text-text-secondary transition-colors hover:bg-bg-raised hover:text-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong",
223
- "aria-label": k.closeLabel,
229
+ "aria-label": O.closeLabel,
224
230
  "data-testid": "assistant-voice-proposal-tray-close",
225
- children: /* @__PURE__ */ d(u, {
231
+ children: /* @__PURE__ */ u(l, {
226
232
  className: "size-4",
227
233
  "aria-hidden": "true"
228
234
  })
229
235
  })
230
- }), k.content]
236
+ }), O.content]
231
237
  }) : null,
232
- h ? /* @__PURE__ */ d(_e, {
233
- view: I,
238
+ h ? /* @__PURE__ */ u(ye, {
239
+ view: R,
234
240
  onContinue: h.accept,
235
241
  onNotNow: h.decline
236
242
  }) : null,
237
- V ? /* @__PURE__ */ d(me, {
238
- turns: we,
239
- onClose: () => H(!1),
240
- onSave: Te,
241
- onContinueInChat: Ee,
242
- renderAfterTurn: Fe
243
+ H ? /* @__PURE__ */ u(ge, {
244
+ turns: Te,
245
+ onClose: () => U(!1),
246
+ onSave: Ee,
247
+ onContinueInChat: De,
248
+ renderAfterTurn: Le
243
249
  }) : null,
244
- y ? /* @__PURE__ */ d("div", {
245
- className: `absolute ${R ? "left-6 top-6" : z ? "left-11 top-2" : "left-3 top-3"}`,
246
- children: /* @__PURE__ */ d(le, {
250
+ y ? /* @__PURE__ */ u("div", {
251
+ className: `absolute ${z ? "left-6 top-6" : B ? "left-11 top-2" : "left-3 top-3"}`,
252
+ children: /* @__PURE__ */ u(de, {
247
253
  onFiles: (e) => {
248
- y(e), F((e) => e === "mini" ? "panel" : e);
254
+ y(e), I((e) => e === "mini" ? "panel" : e);
249
255
  },
250
- onPickerOpen: Re,
256
+ onPickerOpen: He,
251
257
  direction: "down",
252
- canAddMore: Ae,
253
- disabled: x
258
+ canAddMore: je,
259
+ disabled: b
254
260
  })
255
261
  }) : null,
256
- (v.length > 0 || b > 0) && !z ? /* @__PURE__ */ d("div", {
257
- className: `absolute ${R ? "bottom-24 left-24 right-24" : "bottom-16 left-16 right-16"}`,
262
+ (v.length > 0 || Me > 0) && !B ? /* @__PURE__ */ u("div", {
263
+ className: `absolute ${z ? "bottom-24 left-24 right-24" : "bottom-16 left-16 right-16"}`,
258
264
  "data-testid": "assistant-voice-attachments",
259
- children: /* @__PURE__ */ d(ue, {
265
+ children: /* @__PURE__ */ u(fe, {
260
266
  attachments: v,
261
- onRemove: Oe ?? (() => void 0),
262
- onRetry: ke ?? (() => void 0),
263
- preparingCount: b,
264
- disabled: x
267
+ onRemove: ke ?? (() => void 0),
268
+ onRetry: Ae ?? (() => void 0),
269
+ preparingCount: Me,
270
+ disabled: b
265
271
  })
266
272
  }) : null,
267
- /* @__PURE__ */ d("button", {
273
+ /* @__PURE__ */ u("button", {
268
274
  type: "button",
269
- onClick: () => H((e) => !e),
270
- className: `absolute ${R ? "bottom-6 right-6 size-14" : z ? "right-2 top-2 size-8" : "bottom-3 right-3 size-11"} flex items-center justify-center rounded-full bg-bg-elevated text-text-secondary transition-colors hover:bg-bg-raised hover:text-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong`,
271
- "aria-pressed": V,
275
+ onClick: () => U((e) => !e),
276
+ className: `absolute ${z ? "bottom-6 right-6 size-14" : B ? "right-2 top-2 size-8" : "bottom-3 right-3 size-11"} flex items-center justify-center rounded-full bg-bg-elevated text-text-secondary transition-colors hover:bg-bg-raised hover:text-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong`,
277
+ "aria-pressed": H,
272
278
  "aria-label": A("voiceSession.transcriptTitle", "Voice transcript"),
273
279
  "data-testid": "assistant-voice-transcript-toggle",
274
- children: /* @__PURE__ */ d(xe, { className: R ? "size-6" : z ? "size-4" : "size-5" })
280
+ children: /* @__PURE__ */ u(Ce, { className: z ? "size-6" : B ? "size-4" : "size-5" })
275
281
  }),
276
- /* @__PURE__ */ d("button", {
282
+ /* @__PURE__ */ u("button", {
277
283
  type: "button",
278
- onClick: () => F(B),
279
- className: `absolute ${R ? "bottom-6 left-6 size-14" : z ? "left-2 top-2 size-8" : "bottom-3 left-3 size-11"} flex items-center justify-center rounded-full bg-bg-elevated text-text-secondary transition-colors hover:bg-bg-raised hover:text-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong`,
280
- "aria-label": B === "panel" ? A("voiceSession.expandPanel", "Show the conversation in the panel") : B === "full" ? A("voiceSession.expand", "Expand the conversation to full screen") : A("voiceSession.collapse", "Shrink to the compact controller"),
284
+ onClick: () => I(V),
285
+ className: `absolute ${z ? "bottom-6 left-6 size-14" : B ? "left-2 top-2 size-8" : "bottom-3 left-3 size-11"} flex items-center justify-center rounded-full bg-bg-elevated text-text-secondary transition-colors hover:bg-bg-raised hover:text-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong`,
286
+ "aria-label": V === "panel" ? A("voiceSession.expandPanel", "Show the conversation in the panel") : V === "full" ? A("voiceSession.expand", "Expand the conversation to full screen") : A("voiceSession.collapse", "Shrink to the compact controller"),
281
287
  "data-testid": "assistant-voice-expand",
282
- children: R ? /* @__PURE__ */ d(be, { className: "size-5" }) : /* @__PURE__ */ d(ye, { className: z ? "size-4" : "size-5" })
288
+ children: z ? /* @__PURE__ */ u(Se, { className: "size-5" }) : /* @__PURE__ */ u(xe, { className: B ? "size-4" : "size-5" })
283
289
  })
284
290
  ]
285
291
  });
286
292
  };
287
- function Se(e, t) {
293
+ function p(e, t) {
288
294
  switch (e) {
289
295
  case "network": return t("voiceSession.noticeNetwork", "Speech recognition lost its connection. Check your network — it will retry.");
290
296
  case "not-capturing": return t("voiceSession.noticeNotCapturing", "The microphone did not start. Check that this site is allowed to use it, then try again.");
@@ -296,7 +302,7 @@ function Se(e, t) {
296
302
  default: return e;
297
303
  }
298
304
  }
299
- function Ce(e, t) {
305
+ function we(e, t) {
300
306
  switch (e) {
301
307
  case "mic-denied": return t("voiceSession.micDenied", "Microphone access is blocked. Allow it in your browser settings to talk.");
302
308
  case "no-speech-support": return t("voiceSession.noSpeechSupport", "This browser cannot listen. Try a different browser, or type instead.");
@@ -305,4 +311,4 @@ function Ce(e, t) {
305
311
  }
306
312
  }
307
313
  //#endregion
308
- export { p as AssistantVoiceSession };
314
+ export { f as AssistantVoiceSession };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Whether a voice conversation can start now, and if not, why (BOFF-9341,
3
+ * critic #6).
4
+ *
5
+ * ★★ ONE check, the composer's FULL one. The composer offers its Voice button
6
+ * only where voice can work at all and the active mode takes it
7
+ * (`onStartVoiceSession`: `voiceSession.available && voiceAllowedInActiveMode`),
8
+ * disables it while the conversation cannot take a turn (its `disabled` and
9
+ * `readOnly`: a turn in flight, an approval being recorded, history loading,
10
+ * navigation busy, a read-only conversation) or while a Send saves its
11
+ * attachments (`committing`), and hides it while dictation holds the
12
+ * microphone. The Avatar tab's "Start a voice conversation" is the same
13
+ * conversation, so it asks exactly this: a button that skipped part of it
14
+ * could open a voice conversation the composer would have refused, or turn on
15
+ * a paid avatar in a mode (the write-capable `api-calls` one with no voice
16
+ * proposal bound to it) where nothing could ever be asked of it.
17
+ *
18
+ * Pure, so the rule is testable without a DOM; the chat area hands it the
19
+ * same expressions it hands the composer (`avatarTab.client.test.tsx` pins
20
+ * that).
21
+ */
22
+ /** Why a voice conversation cannot start now, most lasting first. */
23
+ export type VoiceStartBlocker =
24
+ /** Nothing here can hold a voice conversation (no speech input, or nothing to speak the reply). */
25
+ "unavailable"
26
+ /** The active mode takes no voice (it writes, and no voice proposal is bound to it). */
27
+ | "mode"
28
+ /** This conversation takes no new turn at all. */
29
+ | "read-only"
30
+ /** Not now: a turn in flight, an approval being recorded, history loading, navigation busy. */
31
+ | "busy"
32
+ /** Not now: a Send is saving its attachments. */
33
+ | "committing"
34
+ /** Not now: dictation holds the microphone. */
35
+ | "dictating";
36
+ export interface VoiceStartGateInput {
37
+ /** `voiceSession.available`: speech in and out work here. */
38
+ readonly available: boolean;
39
+ /** `voiceAllowedInActiveMode`. */
40
+ readonly allowedInMode: boolean;
41
+ /** The conversation is read-only (the composer's `readOnly`). */
42
+ readonly readOnly: boolean;
43
+ /** The composer's `disabled`. */
44
+ readonly busy: boolean;
45
+ /** The composer is saving a Send's attachments. */
46
+ readonly committing: boolean;
47
+ /** Composer dictation is listening, or starting to. */
48
+ readonly dictating: boolean;
49
+ }
50
+ /** Null when a voice conversation can start now; else the first reason it cannot. */
51
+ export declare function voiceStartBlocker(input: VoiceStartGateInput): VoiceStartBlocker | null;
52
+ /**
53
+ * A reason nothing the person waits out will lift, here and now: voice cannot
54
+ * work, the mode refuses it, or the conversation takes nothing. The Avatar tab
55
+ * then still offers Turn on, and says that asking needs a voice conversation.
56
+ */
57
+ export declare function voiceStartBlockerLasts(blocker: VoiceStartBlocker | null): boolean;
@@ -0,0 +1,9 @@
1
+ //#region src/shared/assistant/ui/voice/voiceStartGate.ts
2
+ function e(e) {
3
+ return e.available ? e.allowedInMode ? e.readOnly ? "read-only" : e.busy ? "busy" : e.committing ? "committing" : e.dictating ? "dictating" : null : "mode" : "unavailable";
4
+ }
5
+ function t(e) {
6
+ return e === "unavailable" || e === "mode" || e === "read-only";
7
+ }
8
+ //#endregion
9
+ export { e as voiceStartBlocker, t as voiceStartBlockerLasts };