@burdenoff/fe-libs 2026.1014.5 → 2026.1015.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.
Files changed (45) 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 +6 -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 +1261 -1202
  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/useVoiceSession.d.ts +20 -5
  35. package/dist/shared/assistant/ui/useVoiceSession.js +11 -4
  36. package/dist/shared/assistant/ui/voice/AssistantVoiceSession.d.ts +8 -2
  37. package/dist/shared/assistant/ui/voice/AssistantVoiceSession.js +172 -160
  38. package/dist/shared/assistant/ui/voice/voiceStartGate.d.ts +57 -0
  39. package/dist/shared/assistant/ui/voice/voiceStartGate.js +9 -0
  40. package/dist/shared/assistant/voice/voiceReply.d.ts +23 -0
  41. package/dist/shared/assistant/voice/voiceReply.js +13 -0
  42. package/dist/shared/assistant/voice/voiceSession.d.ts +10 -0
  43. package/dist/shared/assistant/voice/voiceSession.js +7 -0
  44. package/dist/shared-assistant.js +260 -254
  45. package/package.json +1 -1
@@ -14,53 +14,72 @@ import { AssistantVoiceButton as ae, ListeningDot as oe } from "./AssistantVoice
14
14
  import { AssistantVoiceSessionButton as se } from "../voice/AssistantVoiceSessionButton.js";
15
15
  import { useAssistantAttachments as ce } from "../../useAssistantAttachments.js";
16
16
  import { useCallback as s, useEffect as c, useLayoutEffect as le, useMemo as ue, useRef as l, useState as u } from "react";
17
- import { ArrowUp as de, Loader2 as d } from "lucide-react";
18
- import { jsx as f, jsxs as p } from "react/jsx-runtime";
17
+ import { ArrowUp as de, Loader2 as fe } from "lucide-react";
18
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
19
19
  //#region src/shared/assistant/ui/composer/AssistantComposer.tsx
20
- var fe = 200, m = ({ writeAccessDenied: m = !1, onSend: h, disabled: g = !1, readOnly: pe = !1, placeholder: me, mode: he, availableModes: _, attachmentCharsPerFile: ge, onModeChange: _e, auto: ve, onStartVoiceSession: v, onDraftStart: y }) => {
21
- let b = r(), { locale: x } = e(), S = g || pe, C = l(null), [w, T] = u(""), [E, D] = u(!1), [O, k] = u(!1), A = l(!1), [j, M] = u(null), N = l(0), { attachments: P, isBusy: ye, preparingCount: be, sendBlocker: F, rejection: I, addFiles: L, remove: xe, retry: Se, clear: R, clearRejection: Ce, canAddMore: we, commit: z, cancelCommit: B } = ce({ perFileChars: ge }), V = w.trim() !== "" || P.length > 0, H = l(!1);
20
+ var pe = 200, p = ({ writeAccessDenied: p = !1, onSend: m, disabled: h = !1, readOnly: me = !1, placeholder: he, mode: ge, availableModes: g, attachmentCharsPerFile: _e, onModeChange: ve, auto: ye, onStartVoiceSession: be, onDraftStart: _, suspendDictation: v = !1, onVoiceStateChange: y }) => {
21
+ let b = r(), { locale: x } = e(), S = h || me, C = l(null), [w, T] = u(""), [E, D] = u(!1), [O, xe] = u(!1), k = l(!1), [A, j] = u(null), M = l(0), { attachments: N, isBusy: Se, preparingCount: Ce, sendBlocker: P, rejection: F, addFiles: I, remove: we, retry: Te, clear: L, clearRejection: Ee, canAddMore: De, commit: R, cancelCommit: z } = ce({ perFileChars: _e }), B = w.trim() !== "" || N.length > 0, V = l(!1);
22
22
  c(() => {
23
- V && !H.current ? (H.current = !0, y?.()) : V || (H.current = !1);
24
- }, [V, y]);
25
- let Te = s((e) => {
23
+ B && !V.current ? (V.current = !0, _?.()) : B || (V.current = !1);
24
+ }, [B, _]);
25
+ let H = s((e) => {
26
26
  T((t) => t ? `${t.replace(/\s+$/, "")} ${e}` : e);
27
27
  }, []), U = i((e) => e.assistantLanguage), W = n({
28
- onFinalize: Te,
28
+ onFinalize: H,
29
29
  language: ue(() => te(U, x), [U, x]).tag
30
- }), Ee = me ?? b("composer.placeholder", "Ask me anything…"), G = w.trim(), K = a(P), q = P.filter((e) => e.status === "error"), J = (G.length > 0 || K.length > 0) && !S && !O && F === null, Y = K.some((e) => !e.fileId && !e.inlineOnly), X = l(S);
31
- X.current = S;
30
+ }), G = W.isActive, K = W.interim, q = W.stop;
31
+ c(() => {
32
+ !v || !G || (K.trim() !== "" && H(K.trim()), q());
33
+ }, [
34
+ v,
35
+ G,
36
+ K,
37
+ q,
38
+ H
39
+ ]), c(() => {
40
+ y?.({
41
+ committing: O,
42
+ dictating: G
43
+ });
44
+ }, [
45
+ y,
46
+ O,
47
+ G
48
+ ]);
49
+ let Oe = he ?? b("composer.placeholder", "Ask me anything…"), ke = w.trim(), J = a(N), Y = N.filter((e) => e.status === "error"), X = (ke.length > 0 || J.length > 0) && !S && !O && P === null, Ae = J.some((e) => !e.fileId && !e.inlineOnly), je = l(S);
50
+ je.current = S;
32
51
  let Z = l(!0);
33
52
  c(() => (Z.current = !0, () => {
34
53
  Z.current = !1;
35
54
  }), []), le(() => {
36
55
  let e = C.current;
37
- e && (e.style.height = "auto", e.style.height = `${Math.min(e.scrollHeight, fe)}px`);
56
+ e && (e.style.height = "auto", e.style.height = `${Math.min(e.scrollHeight, pe)}px`);
38
57
  }, [w]);
39
58
  let Q = s(async () => {
40
- if (!J || A.current) return;
59
+ if (!X || k.current) return;
41
60
  W.isActive && W.stop();
42
61
  let e = w.trim();
43
- if (M(null), !Y) {
44
- h(e, K), T(""), R();
62
+ if (j(null), !Ae) {
63
+ m(e, J), T(""), L();
45
64
  return;
46
65
  }
47
66
  let t = i.getState().activeConversationId;
48
- A.current = !0, k(!0);
67
+ k.current = !0, xe(!0);
49
68
  try {
50
- let n = await z();
69
+ let n = await R();
51
70
  if (!Z.current) {
52
71
  n.ok && await n.rollback();
53
72
  return;
54
73
  }
55
74
  if (!n.ok) {
56
- n.cancelled || M({
75
+ n.cancelled || j({
57
76
  kind: "failed",
58
77
  failures: n.failures
59
78
  });
60
79
  return;
61
80
  }
62
- if (X.current) {
63
- await n.rollback(), Z.current && M({
81
+ if (je.current) {
82
+ await n.rollback(), Z.current && j({
64
83
  kind: "busy",
65
84
  failures: []
66
85
  });
@@ -68,52 +87,52 @@ var fe = 200, m = ({ writeAccessDenied: m = !1, onSend: h, disabled: g = !1, rea
68
87
  }
69
88
  let r = i.getState();
70
89
  if (!(r.activeConversationId === t && (t === null || r.conversations.some((e) => e.id === t)))) {
71
- await n.rollback(), Z.current && M({
90
+ await n.rollback(), Z.current && j({
72
91
  kind: "moved",
73
92
  failures: []
74
93
  });
75
94
  return;
76
95
  }
77
- h(e, a(n.attachments)), n.handedOver(), T(""), R();
96
+ m(e, a(n.attachments)), n.handedOver(), T(""), L();
78
97
  } finally {
79
- A.current = !1, Z.current && k(!1);
98
+ k.current = !1, Z.current && xe(!1);
80
99
  }
81
100
  }, [
82
- J,
83
- Y,
84
- z,
85
- h,
86
- w,
87
- K,
101
+ X,
102
+ Ae,
88
103
  R,
104
+ m,
105
+ w,
106
+ J,
107
+ L,
89
108
  W
90
- ]), $ = s(() => {
91
- B(), C.current?.focus();
92
- }, [B]);
109
+ ]), Me = s(() => {
110
+ z(), C.current?.focus();
111
+ }, [z]);
93
112
  c(() => {
94
- M(null);
95
- }, [P.length]);
96
- let De = (e) => {
113
+ j(null);
114
+ }, [N.length]);
115
+ let Ne = (e) => {
97
116
  if (e.kind === "busy") return b("attachments.saveBusy", "The assistant started another reply, so nothing was sent. Your message and files are still here.");
98
117
  if (e.kind === "moved") return b("attachments.saveMoved", "You switched conversations while the files were being saved, so nothing was sent. Your message and files are still here.");
99
118
  let { failures: t } = e;
100
119
  if (t.some((e) => e.code === o.quotaExceeded || e.code === o.quotaNotProvisioned)) return `${b("attachments.quotaFull", "File storage is full or not set up for this workspace, so the attachments couldn't be saved. Nothing was sent.")} ${b("attachments.retrySend", "Press Send to try again, or remove the file.")}`;
101
- let n = t[0], r = P.find((e) => e.id === n?.id)?.filename ?? "";
120
+ let n = t[0], r = N.find((e) => e.id === n?.id)?.filename ?? "";
102
121
  return `${t.length <= 1 ? b("attachments.saveFailedOne", "{{name}} couldn't be saved, so nothing was sent.", { name: r }) : b("attachments.saveFailedMany", "{{count}} attachments couldn't be saved, so nothing was sent.", { count: t.length })} ${b("attachments.retrySend", "Press Send to try again, or remove the file.")}`;
103
- }, Oe = s((e) => {
122
+ }, Pe = s((e) => {
104
123
  e.key === "Enter" && !e.shiftKey && (e.preventDefault(), Q());
105
- }, [Q]), ke = s((e) => {
124
+ }, [Q]), Fe = s((e) => {
106
125
  let t = Array.from(e.clipboardData?.files ?? []);
107
- t.length !== 0 && (e.preventDefault(), L(t));
108
- }, [L]), Ae = s((e) => {
109
- Array.from(e.dataTransfer?.types ?? []).includes("Files") && (N.current += 1, D(!0));
110
- }, []), je = s(() => {
111
- N.current = Math.max(0, N.current - 1), N.current === 0 && D(!1);
112
- }, []), Me = s((e) => {
126
+ t.length !== 0 && (e.preventDefault(), I(t));
127
+ }, [I]), Ie = s((e) => {
128
+ Array.from(e.dataTransfer?.types ?? []).includes("Files") && (M.current += 1, D(!0));
129
+ }, []), Le = s(() => {
130
+ M.current = Math.max(0, M.current - 1), M.current === 0 && D(!1);
131
+ }, []), Re = s((e) => {
113
132
  Array.from(e.dataTransfer?.types ?? []).includes("Files") && e.preventDefault();
114
- }, []), Ne = s((e) => {
115
- e.preventDefault(), N.current = 0, D(!1), L(e.dataTransfer?.files ?? null);
116
- }, [L]), Pe = (e) => {
133
+ }, []), ze = s((e) => {
134
+ e.preventDefault(), M.current = 0, D(!1), I(e.dataTransfer?.files ?? null);
135
+ }, [I]), Be = (e) => {
117
136
  switch (e) {
118
137
  case "too-many": return b("attachments.tooMany", "You can attach up to {{max}} files per message.", { max: 5 });
119
138
  case "too-large": return b("attachments.tooLarge", "That file is larger than 10 MB.");
@@ -122,7 +141,7 @@ var fe = 200, m = ({ writeAccessDenied: m = !1, onSend: h, disabled: g = !1, rea
122
141
  case "unsupported-image": return b("attachments.unsupportedImage", "This device can't read that image format, so it can't be resized to send. Save it as JPEG and try again.");
123
142
  case "too-large-to-save": return b("attachments.tooLargeToSave", "That file is over 5 MB, so it can't be saved to Files, and no text could be read from it.");
124
143
  }
125
- }, Fe = (() => {
144
+ }, $ = (() => {
126
145
  switch (W.error) {
127
146
  case "not-allowed": return b("voice.denied", "Microphone access is blocked. Allow it in your browser settings to dictate.");
128
147
  case "audio-capture": return b("voice.noMic", "No microphone was found.");
@@ -132,136 +151,136 @@ var fe = 200, m = ({ writeAccessDenied: m = !1, onSend: h, disabled: g = !1, rea
132
151
  default: return null;
133
152
  }
134
153
  })();
135
- return /* @__PURE__ */ p("div", {
154
+ return /* @__PURE__ */ f("div", {
136
155
  className: "flex-shrink-0 px-3 pb-3 pt-1.5",
137
156
  children: [
138
- /* @__PURE__ */ p("div", {
139
- onDragEnter: Ae,
140
- onDragLeave: je,
141
- onDragOver: Me,
142
- onDrop: Ne,
157
+ /* @__PURE__ */ f("div", {
158
+ onDragEnter: Ie,
159
+ onDragLeave: Le,
160
+ onDragOver: Re,
161
+ onDrop: ze,
143
162
  className: `rounded-2xl border p-2 shadow-sm transition-colors focus-within:border-action-primary-bg focus-within:ring-2 focus-within:ring-action-primary-bg/15 ${E ? "border-action-primary-bg border-dashed bg-action-primary-bg/5" : "border-border-default bg-bg-sunken"}`,
144
163
  "data-testid": "assistant-composer",
145
164
  children: [
146
- /* @__PURE__ */ f(re, {
147
- attachments: P,
148
- onRemove: xe,
149
- onRetry: Se,
165
+ /* @__PURE__ */ d(re, {
166
+ attachments: N,
167
+ onRemove: we,
168
+ onRetry: Te,
150
169
  disabled: S || O,
151
- preparingCount: be
170
+ preparingCount: Ce
152
171
  }),
153
- /* @__PURE__ */ p("div", {
172
+ /* @__PURE__ */ f("div", {
154
173
  className: "flex items-end gap-2",
155
174
  children: [
156
- /* @__PURE__ */ f(ne, {
157
- onFiles: L,
158
- canAddMore: we,
175
+ /* @__PURE__ */ d(ne, {
176
+ onFiles: I,
177
+ canAddMore: De,
159
178
  disabled: S || O,
160
179
  showLabel: !0
161
180
  }),
162
- /* @__PURE__ */ f("textarea", {
181
+ /* @__PURE__ */ d("textarea", {
163
182
  ref: C,
164
183
  value: w,
165
184
  onChange: (e) => T(e.target.value),
166
- onKeyDown: Oe,
167
- onPaste: ke,
185
+ onKeyDown: Pe,
186
+ onPaste: Fe,
168
187
  disabled: S,
169
188
  readOnly: O,
170
189
  "aria-busy": O || void 0,
171
- placeholder: E ? b("composer.dropHere", "Drop files to attach") : Ee,
190
+ placeholder: E ? b("composer.dropHere", "Drop files to attach") : Oe,
172
191
  rows: 1,
173
192
  className: "min-h-[36px] flex-1 resize-none self-center bg-transparent px-1 py-2 text-[15px] leading-relaxed text-text-primary placeholder:text-text-muted focus:outline-none disabled:opacity-50",
174
193
  "aria-label": b("chatInput.ariaLabel", "Message input"),
175
194
  "data-testid": "assistant-input"
176
195
  }),
177
- W.isSupported && /* @__PURE__ */ f(ae, {
196
+ W.isSupported && /* @__PURE__ */ d(ae, {
178
197
  isListening: W.isListening,
179
198
  isStarting: W.isStarting,
180
199
  onToggle: W.toggle,
181
200
  disabled: S || O
182
201
  }),
183
- v && G.length === 0 && P.length === 0 && !W.isActive ? /* @__PURE__ */ f(se, {
184
- onStart: v,
202
+ be && ke.length === 0 && N.length === 0 && !W.isActive ? /* @__PURE__ */ d(se, {
203
+ onStart: be,
185
204
  disabled: S || O
186
- }) : /* @__PURE__ */ f("button", {
205
+ }) : /* @__PURE__ */ d("button", {
187
206
  type: "button",
188
207
  onClick: () => void Q(),
189
- disabled: !J,
190
- className: `flex size-9 flex-shrink-0 items-center justify-center rounded-full shadow-sm transition-all focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong ${J ? "bg-action-primary-bg text-action-primary-text hover:scale-105 hover:bg-action-primary-bg-hover" : "bg-bg-elevated text-text-muted"}`,
208
+ disabled: !X,
209
+ className: `flex size-9 flex-shrink-0 items-center justify-center rounded-full shadow-sm transition-all focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong ${X ? "bg-action-primary-bg text-action-primary-text hover:scale-105 hover:bg-action-primary-bg-hover" : "bg-bg-elevated text-text-muted"}`,
191
210
  "aria-label": b("chatInput.send", "Send message"),
192
211
  "data-testid": "assistant-send",
193
- children: g || ye || O ? /* @__PURE__ */ f(d, { className: "size-4 motion-safe:animate-spin" }) : /* @__PURE__ */ f(de, { className: "size-4" })
212
+ children: h || Se || O ? /* @__PURE__ */ d(fe, { className: "size-4 motion-safe:animate-spin" }) : /* @__PURE__ */ d(de, { className: "size-4" })
194
213
  })
195
214
  ]
196
215
  }),
197
- _.length > 1 && /* @__PURE__ */ f("div", {
216
+ g.length > 1 && /* @__PURE__ */ d("div", {
198
217
  className: "mt-2 px-0.5",
199
- children: /* @__PURE__ */ f(ie, {
200
- mode: he,
201
- availableModes: _,
202
- onModeChange: _e,
218
+ children: /* @__PURE__ */ d(ie, {
219
+ mode: ge,
220
+ availableModes: g,
221
+ onModeChange: ve,
203
222
  disabled: S || O,
204
- auto: ve
223
+ auto: ye
205
224
  })
206
225
  })
207
226
  ]
208
227
  }),
209
- m ? /* @__PURE__ */ f("p", {
228
+ p ? /* @__PURE__ */ d("p", {
210
229
  className: "mt-1.5 px-1 text-center text-[11px] text-status-warning",
211
230
  "data-testid": "assistant-write-access-warning",
212
231
  children: b("composer.writeAccessDenied", "Changes may not save — your role in this workspace does not currently allow edits. You can still plan and review an action.")
213
232
  }) : null,
214
- /* @__PURE__ */ f("div", {
233
+ /* @__PURE__ */ d("div", {
215
234
  className: "mt-1.5 px-1 text-center text-[11px]",
216
235
  "aria-live": "polite",
217
- children: O ? /* @__PURE__ */ f(t, {
218
- onDismiss: $,
219
- children: /* @__PURE__ */ p("p", {
236
+ children: O ? /* @__PURE__ */ d(t, {
237
+ onDismiss: Me,
238
+ children: /* @__PURE__ */ f("p", {
220
239
  className: "flex items-center justify-center gap-2 text-text-muted",
221
240
  "data-testid": "assistant-commit-status",
222
241
  children: [
223
- /* @__PURE__ */ f(d, {
242
+ /* @__PURE__ */ d(fe, {
224
243
  className: "size-3 motion-safe:animate-spin",
225
244
  "aria-hidden": "true"
226
245
  }),
227
- /* @__PURE__ */ f("span", { children: b("attachments.saving", "Saving your attachments…") }),
228
- /* @__PURE__ */ f("button", {
246
+ /* @__PURE__ */ d("span", { children: b("attachments.saving", "Saving your attachments…") }),
247
+ /* @__PURE__ */ d("button", {
229
248
  type: "button",
230
- onClick: $,
249
+ onClick: Me,
231
250
  className: "rounded text-text-primary underline underline-offset-2 hover:text-action-primary-bg focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong",
232
251
  "data-testid": "assistant-commit-cancel",
233
252
  children: b("attachments.cancelSaving", "Cancel")
234
253
  })
235
254
  ]
236
255
  })
237
- }) : j ? /* @__PURE__ */ f("p", {
256
+ }) : A ? /* @__PURE__ */ d("p", {
238
257
  className: "text-status-error-text",
239
258
  role: "alert",
240
259
  "data-testid": "assistant-commit-failed",
241
- children: De(j)
242
- }) : W.isStarting && !W.isListening ? /* @__PURE__ */ f("p", {
260
+ children: Ne(A)
261
+ }) : W.isStarting && !W.isListening ? /* @__PURE__ */ d("p", {
243
262
  className: "text-text-muted",
244
263
  "data-testid": "assistant-voice-starting",
245
264
  children: b("voice.starting", "Starting the microphone…")
246
- }) : W.isListening ? /* @__PURE__ */ p("p", {
265
+ }) : W.isListening ? /* @__PURE__ */ f("p", {
247
266
  className: "text-status-error-text",
248
267
  "data-testid": "assistant-voice-status",
249
- children: [/* @__PURE__ */ f(oe, {}), W.interim || b("voice.listening", "Listening… speak now, tap the square to stop")]
250
- }) : Fe ? /* @__PURE__ */ f("p", {
268
+ children: [/* @__PURE__ */ d(oe, {}), W.interim || b("voice.listening", "Listening… speak now, tap the square to stop")]
269
+ }) : $ ? /* @__PURE__ */ d("p", {
251
270
  className: "text-status-error-text",
252
271
  "data-testid": "assistant-voice-error",
253
- children: Fe
254
- }) : I ? /* @__PURE__ */ f("button", {
272
+ children: $
273
+ }) : F ? /* @__PURE__ */ d("button", {
255
274
  type: "button",
256
- onClick: Ce,
275
+ onClick: Ee,
257
276
  className: "text-status-error-text underline-offset-2 hover:underline",
258
277
  "data-testid": "assistant-attachment-rejection",
259
- children: Pe(I)
260
- }) : F === "failed" ? /* @__PURE__ */ f("p", {
278
+ children: Be(F)
279
+ }) : P === "failed" ? /* @__PURE__ */ d("p", {
261
280
  className: "text-status-error-text",
262
281
  "data-testid": "assistant-attachment-blocked",
263
- children: q.length === 1 ? b("attachments.blockedOne", "{{name}} didn't upload. Retry or remove it before sending.", { name: q[0].filename }) : b("attachments.blockedMany", "{{count}} attachments didn't upload. Retry or remove them before sending.", { count: q.length })
264
- }) : /* @__PURE__ */ f("p", {
282
+ children: Y.length === 1 ? b("attachments.blockedOne", "{{name}} didn't upload. Retry or remove it before sending.", { name: Y[0].filename }) : b("attachments.blockedMany", "{{count}} attachments didn't upload. Retry or remove them before sending.", { count: Y.length })
283
+ }) : /* @__PURE__ */ d("p", {
265
284
  className: "text-text-muted",
266
285
  children: b("composer.hint", "Enter to send · Shift+Enter for newline · drag or paste files to attach")
267
286
  })
@@ -270,4 +289,4 @@ var fe = 200, m = ({ writeAccessDenied: m = !1, onSend: h, disabled: g = !1, rea
270
289
  });
271
290
  };
272
291
  //#endregion
273
- export { m as AssistantComposer };
292
+ export { p as AssistantComposer };
@@ -33,6 +33,20 @@ export declare const NO_PANEL_OVERLAY: PanelOverlayState;
33
33
  * other one swaps to it. There is deliberately no way to express "both".
34
34
  */
35
35
  export declare function nextPanelOverlays(current: PanelOverlayState, toggled: "history" | "preview"): PanelOverlayState;
36
+ /**
37
+ * The overlay slot, plus the Avatar tab (BOFF-9341) where the panel offers it.
38
+ *
39
+ * ★★ The tab is NOT part of the slot. It is a view of the chat area drawn
40
+ * under History and the preview (`AssistantAvatarTab`, `z-[5]` below their
41
+ * `z-10`, the voice strip's `z-20` and the transcript drawer's `z-30`), so
42
+ * History can open OVER it and closing History shows it again. Escape backs
43
+ * out one layer at a time: the transcript drawer, the voice session, the tab,
44
+ * then the panel.
45
+ */
46
+ export interface PanelLayerState extends PanelOverlayState {
47
+ /** The Avatar tab is the view on screen; absent where the panel offers none. */
48
+ readonly avatarOpen?: boolean;
49
+ }
36
50
  /**
37
51
  * What Escape should dismiss.
38
52
  *
@@ -44,7 +58,7 @@ export declare function nextPanelOverlays(current: PanelOverlayState, toggled: "
44
58
  * Escape is "back out of the thing I just opened"; here it destroyed a
45
59
  * conversation instead.
46
60
  */
47
- export declare function escapeDismisses(current: PanelOverlayState): "overlay" | "panel";
61
+ export declare function escapeDismisses(current: PanelLayerState): "overlay" | "tab" | "panel";
48
62
  /**
49
63
  * Attribute marking the assistant panel's root element.
50
64
  *
@@ -94,9 +108,16 @@ export declare const ASSISTANT_VOICE_SESSION_ATTR = "data-assistant-voice-sessio
94
108
  * comes first. The panel backs out of it, as `escapeDismisses` says, and marks
95
109
  * the key handled so the session does not also act on it.
96
110
  */
97
- export declare function voiceSessionOwnsEscape(panel: Pick<Element, "hasAttribute" | "querySelector">): boolean;
111
+ export declare function voiceSessionOwnsEscape(panel: Pick<Element, "hasAttribute" | "querySelector"> & Partial<Pick<Element, "getAttribute">>): boolean;
112
+ /**
113
+ * `data-assistant-overlay` while the Avatar tab is the view on screen and no
114
+ * overlay covers it (BOFF-9341): the panel then owns an Escape pressed with
115
+ * focus on `<body>` (a control in the tab that unmounted itself on click), so
116
+ * it selects Chat again instead of the host closing the whole assistant.
117
+ */
118
+ export declare const AVATAR_TAB_OVERLAY = "avatar";
98
119
  /** Which overlay is showing, as the attribute value — `null` when none is. */
99
- export declare function activePanelOverlay(current: PanelOverlayState): "history" | "preview" | null;
120
+ export declare function activePanelOverlay(current: PanelLayerState): "history" | "preview" | typeof AVATAR_TAB_OVERLAY | null;
100
121
  /**
101
122
  * Is `active` the panel itself, or inside it?
102
123
  *
@@ -10,24 +10,25 @@ function t(t, n) {
10
10
  };
11
11
  }
12
12
  function n(e) {
13
- return e.historyOpen || e.previewOpen ? "overlay" : "panel";
13
+ return e.historyOpen || e.previewOpen ? "overlay" : e.avatarOpen === !0 ? "tab" : "panel";
14
14
  }
15
15
  var r = "data-assistant-panel", i = "data-assistant-overlay", a = "data-assistant-voice-session";
16
16
  function o(e) {
17
- return e.hasAttribute("data-assistant-overlay") ? !1 : e.querySelector(`[${a}]`) !== null;
17
+ return e.hasAttribute("data-assistant-overlay") && e.getAttribute?.("data-assistant-overlay") !== "avatar" ? !1 : e.querySelector(`[${a}]`) !== null;
18
18
  }
19
- function s(e) {
20
- return e.historyOpen ? "history" : e.previewOpen ? "preview" : null;
19
+ var s = "avatar";
20
+ function c(e) {
21
+ return e.historyOpen ? "history" : e.previewOpen ? "preview" : e.avatarOpen === !0 ? s : null;
21
22
  }
22
- function c(e, t) {
23
+ function l(e, t) {
23
24
  return !e || !t ? !1 : e === t || e.contains(t);
24
25
  }
25
- function l(e = document) {
26
+ function u(e = document) {
26
27
  let t = e.activeElement;
27
- return Array.from(e.querySelectorAll(`[${r}]`)).some((n) => u(n, t, e.body));
28
+ return Array.from(e.querySelectorAll(`[${r}]`)).some((n) => d(n, t, e.body));
28
29
  }
29
- function u(e, t, n) {
30
- return e ? c(e, t) ? !0 : (t === null || t === n) && e.hasAttribute("data-assistant-overlay") : !1;
30
+ function d(e, t, n) {
31
+ return e ? l(e, t) ? !0 : (t === null || t === n) && e.hasAttribute("data-assistant-overlay") : !1;
31
32
  }
32
33
  //#endregion
33
- export { i as ASSISTANT_OVERLAY_ATTR, r as ASSISTANT_PANEL_ATTR, a as ASSISTANT_VOICE_SESSION_ATTR, e as NO_PANEL_OVERLAY, s as activePanelOverlay, l as assistantPanelOwnsEscape, n as escapeDismisses, c as focusIsInsidePanel, t as nextPanelOverlays, u as panelOwnsEscape, o as voiceSessionOwnsEscape };
34
+ export { i as ASSISTANT_OVERLAY_ATTR, r as ASSISTANT_PANEL_ATTR, a as ASSISTANT_VOICE_SESSION_ATTR, s as AVATAR_TAB_OVERLAY, e as NO_PANEL_OVERLAY, c as activePanelOverlay, u as assistantPanelOwnsEscape, n as escapeDismisses, l as focusIsInsidePanel, t as nextPanelOverlays, d as panelOwnsEscape, o as voiceSessionOwnsEscape };
@@ -1,6 +1,7 @@
1
1
  import { VoiceTurnTiming } from '../turnTimeline';
2
2
  import { VoiceGraceMs } from '../voice/handsFree';
3
3
  import { VoiceSessionSnapshot } from '../voice/voiceSession';
4
+ import { VoiceReply } from '../voice/voiceReply';
4
5
  /**
5
6
  * A problem worth TELLING the user about that does not warrant killing the
6
7
  * session.
@@ -21,6 +22,18 @@ export type VoiceSessionNotice = "network" | "not-capturing" | "nothing-heard" |
21
22
  * transcript.
22
23
  */
23
24
  | "reply-not-heard";
25
+ /**
26
+ * ★ BOFF-9207: why the turn just spoken ended WITHOUT an answer - a refusal
27
+ * before it started ("No active subscription covers ..."), or a failure. The
28
+ * text is the server's own reason (already in the person's language), shown as
29
+ * given; it is never spoken, so no new speech is paid for.
30
+ */
31
+ export interface VoiceTurnRefusalNotice {
32
+ readonly kind: "turn-refused";
33
+ readonly text: string;
34
+ }
35
+ /** Everything the voice stage can show in its notice slot. */
36
+ export type VoiceSessionNoticeState = VoiceSessionNotice | VoiceTurnRefusalNotice;
24
37
  /**
25
38
  * A line the PANEL says in the voice conversation, outside any turn (BOFF-7803
26
39
  * FE9: the readback of a change that waits for its button).
@@ -69,11 +82,13 @@ interface Options {
69
82
  * Id + text of the newest assistant reply. A CHANGE of id is what advances
70
83
  * the session out of `thinking` — the text alone is not enough, because two
71
84
  * consecutive replies can be identical.
85
+ *
86
+ * ★ BOFF-9207: `refused` is set when the turn ended without an answer (a
87
+ * refusal or a failure) and `text` is the reason. Such a turn has nothing
88
+ * queued for speech, so it is shown, not spoken, and never enters `speaking`.
89
+ * Absent for a real answer, so its object is exactly what it was.
72
90
  */
73
- latestReply: {
74
- id: string;
75
- text: string;
76
- } | null;
91
+ latestReply: VoiceReply | null;
77
92
  /** Language the user is speaking, so the recogniser is pointed correctly. */
78
93
  language: string;
79
94
  /**
@@ -130,7 +145,7 @@ export interface VoiceSubmitMeta {
130
145
  export declare function useVoiceSession({ onSubmit, latestReply, language, graceMs: graceChoice, onSupersede, handsFreeInterrupt, handsFreeStreamNoticeAccepted, onAcceptHandsFreeStreamNotice, onBargeIn, }: Options): {
131
146
  session: Readonly<VoiceSessionSnapshot>;
132
147
  /** A recoverable problem worth showing, or null when all is well. */
133
- notice: VoiceSessionNotice | null;
148
+ notice: VoiceSessionNoticeState | null;
134
149
  /**
135
150
  * The reply as it is being spoken, chunk by chunk — for the live caption.
136
151
  *
@@ -146,16 +146,23 @@ function Te({ onSubmit: Te, latestReply: f, language: p, graceMs: Ee, onSupersed
146
146
  let Ue = u("");
147
147
  _.phase === "listening" && (Ue.current = o(_)), l(() => {
148
148
  if (_.phase !== "thinking") return;
149
- x(!1), V();
149
+ x(!1), V(), j((e) => typeof e == "object" && e ? null : e);
150
150
  let e = Ue.current.trim();
151
151
  e && Te(e, Q.current ?? `vs-orphan-${Date.now().toString(36)}`, E.current, { correction: L.current.correcting }), E.current = {}, D.current = i;
152
152
  }, [_.phase]), l(() => {
153
153
  let e = ce(f?.id, _.phase, O.current, pe(f?.id));
154
- e.claim && (O.current = f?.id ?? null, e.advance && f && v({
154
+ e.claim && (O.current = f?.id ?? null, e.advance && f && (f.refused ? (V(), j({
155
+ kind: "turn-refused",
156
+ text: f.text
157
+ }), v({ type: "turn-refused" })) : v({
155
158
  type: "reply",
156
159
  text: f.text
157
- }));
158
- }, [f, _.phase]), l(() => {
160
+ })));
161
+ }, [
162
+ f,
163
+ _.phase,
164
+ V
165
+ ]), l(() => {
159
166
  if (S === null || _.phase !== "thinking" || O.current === S) return;
160
167
  if (pe(S)) {
161
168
  C();
@@ -2,7 +2,7 @@ import { FC, ReactNode } from 'react';
2
2
  import { AssistantAttachment } from '../../attachments';
3
3
  import { HandsFreePrivacyNoticeActions } from '../../voice/handsfree/useHandsFreeInterrupt';
4
4
  import { VoiceSessionSnapshot } from '../../voice/voiceSession';
5
- import { VoiceSessionNotice } from '../useVoiceSession';
5
+ import { VoiceSessionNoticeState } from '../useVoiceSession';
6
6
  import { VoiceTranscriptTurn } from '../../voice/turnVisibility';
7
7
  /** How much room the voice session takes: a strip, the panel, or the screen. */
8
8
  export type VoiceView = "mini" | "panel" | "full";
@@ -17,7 +17,7 @@ interface Props {
17
17
  /** Cut the assistant off mid-reply. */
18
18
  onInterrupt?: () => void;
19
19
  /** A recoverable problem to surface. Null when the microphone is healthy. */
20
- notice?: VoiceSessionNotice | null;
20
+ notice?: VoiceSessionNoticeState | null;
21
21
  /**
22
22
  * ★ BOFF-8613 H3: the backend speech stream's privacy notice, while it must
23
23
  * be answered before the microphone opens. Null otherwise (always, unless an
@@ -98,6 +98,12 @@ interface Props {
98
98
  * — the page the model offered in that voice turn.
99
99
  */
100
100
  renderAfterTurn?: (turnId: string) => ReactNode;
101
+ /**
102
+ * ★ BOFF-9341: told the view shown, while the panel has the Avatar tab: an
103
+ * enlarged session covers the tab, which then is no stage (D4). Absent, the
104
+ * session reports nothing.
105
+ */
106
+ onViewChange?: (view: VoiceView) => void;
101
107
  }
102
108
  /**
103
109
  * Full-screen voice conversation (BOFF-7120).