@burdenoff/fe-libs 2026.1004.4 → 2026.1004.5

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 (37) hide show
  1. package/dist/shared/assistant/i18nManifest.d.ts +10 -0
  2. package/dist/shared/assistant/i18nManifest.d.ts.map +1 -1
  3. package/dist/shared/assistant/i18nManifest.js +22 -12
  4. package/dist/shared/assistant/index.d.ts +3 -0
  5. package/dist/shared/assistant/index.d.ts.map +1 -1
  6. package/dist/shared/assistant/store.d.ts +10 -0
  7. package/dist/shared/assistant/store.d.ts.map +1 -1
  8. package/dist/shared/assistant/store.js +52 -44
  9. package/dist/shared/assistant/ui/AssistantChatArea.d.ts.map +1 -1
  10. package/dist/shared/assistant/ui/AssistantChatArea.js +761 -746
  11. package/dist/shared/assistant/ui/AssistantMoreMenu.d.ts.map +1 -1
  12. package/dist/shared/assistant/ui/AssistantMoreMenu.js +58 -56
  13. package/dist/shared/assistant/ui/AssistantSpeechProvider.d.ts.map +1 -1
  14. package/dist/shared/assistant/ui/AssistantSpeechProvider.js +106 -105
  15. package/dist/shared/assistant/ui/composer/AssistantVoiceGraceSelector.d.ts +20 -0
  16. package/dist/shared/assistant/ui/composer/AssistantVoiceGraceSelector.d.ts.map +1 -0
  17. package/dist/shared/assistant/ui/composer/AssistantVoiceGraceSelector.js +39 -0
  18. package/dist/shared/assistant/ui/useVoiceSession.d.ts +35 -2
  19. package/dist/shared/assistant/ui/useVoiceSession.d.ts.map +1 -1
  20. package/dist/shared/assistant/ui/useVoiceSession.js +232 -171
  21. package/dist/shared/assistant/ui/voice/AssistantVoiceSession.d.ts +15 -1
  22. package/dist/shared/assistant/ui/voice/AssistantVoiceSession.d.ts.map +1 -1
  23. package/dist/shared/assistant/ui/voice/AssistantVoiceSession.js +121 -101
  24. package/dist/shared/assistant/voice/handsFree.d.ts +160 -0
  25. package/dist/shared/assistant/voice/handsFree.d.ts.map +1 -0
  26. package/dist/shared/assistant/voice/handsFree.js +104 -0
  27. package/dist/shared/assistant/voice/supersededTurns.d.ts +29 -0
  28. package/dist/shared/assistant/voice/supersededTurns.d.ts.map +1 -0
  29. package/dist/shared/assistant/voice/supersededTurns.js +17 -0
  30. package/dist/shared/assistant/voice/useVoiceInput.d.ts +7 -0
  31. package/dist/shared/assistant/voice/useVoiceInput.d.ts.map +1 -1
  32. package/dist/shared/assistant/voice/useVoiceInput.js +13 -10
  33. package/dist/shared/assistant/voice/voiceSession.d.ts +64 -20
  34. package/dist/shared/assistant/voice/voiceSession.d.ts.map +1 -1
  35. package/dist/shared/assistant/voice/voiceSession.js +120 -67
  36. package/dist/shared-assistant.js +206 -203
  37. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantVoiceGraceSelector.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/ui/composer/AssistantVoiceGraceSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAWhC;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,2BAA2B,EAAE,EA8CzC,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { isSpeechRecognitionSupported as e } from "../../voice/useVoiceInput.js";
2
+ import { useAssistantTr as t } from "../../i18n.js";
3
+ import { VOICE_GRACE_CHOICES_MS as n, resolveVoiceGraceMs as r } from "../../voice/handsFree.js";
4
+ import { useAssistantStore as i } from "../../store.js";
5
+ import { Timer as a } from "lucide-react";
6
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
7
+ //#region src/shared/assistant/ui/composer/AssistantVoiceGraceSelector.tsx
8
+ var c = () => {
9
+ let c = t(), l = i((e) => e.voiceGraceMs), u = i((e) => e.setVoiceGraceMs);
10
+ if (!e()) return null;
11
+ let d = r(l), f = (e) => e / 1e3;
12
+ return /* @__PURE__ */ s("label", {
13
+ className: "flex items-center gap-2.5 px-3 py-2 text-xs text-text-primary focus-within:ring-2 focus-within:ring-inset focus-within:ring-border-strong",
14
+ title: c("voice.graceHint", "How long the assistant waits in silence before it sends what you said. Start talking again and it carries on with the same message."),
15
+ "data-testid": "assistant-voice-grace",
16
+ children: [
17
+ /* @__PURE__ */ o(a, {
18
+ className: "size-3.5 flex-shrink-0 text-text-secondary",
19
+ "aria-hidden": "true"
20
+ }),
21
+ /* @__PURE__ */ o("span", {
22
+ className: "flex-1",
23
+ children: c("voice.graceLabel", "Wait after I stop talking")
24
+ }),
25
+ /* @__PURE__ */ o("select", {
26
+ value: d,
27
+ onChange: (e) => u(Number(e.target.value)),
28
+ className: "rounded-md border border-border-default bg-bg-surface px-1.5 py-1 text-xs text-text-primary focus:outline-none scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
29
+ "data-testid": "assistant-voice-grace-select",
30
+ children: n.map((e) => /* @__PURE__ */ o("option", {
31
+ value: e,
32
+ children: e === 2e3 ? c("voice.graceOptionDefault", "{{seconds}} seconds (default)", { seconds: f(e) }) : c("voice.graceOption", "{{seconds}} seconds", { seconds: f(e) })
33
+ }, e))
34
+ })
35
+ ]
36
+ });
37
+ };
38
+ //#endregion
39
+ export { c as AssistantVoiceGraceSelector };
@@ -1,4 +1,5 @@
1
1
  import { VoiceTurnTiming } from '../turnTimeline';
2
+ import { VoiceGraceMs } from '../voice/handsFree';
2
3
  import { VoiceSessionSnapshot } from '../voice/voiceSession';
3
4
  /**
4
5
  * A problem worth TELLING the user about that does not warrant killing the
@@ -46,7 +47,14 @@ interface Options {
46
47
  * When the speech that was submitted ended and when its correction grace
47
48
  * ran out (BOFF-7801) — taken here because the turn has no id yet.
48
49
  */
49
- timing: VoiceTurnTiming) => void;
50
+ timing: VoiceTurnTiming,
51
+ /**
52
+ * H2: facts about THIS turn the sender needs. `correction` is true when it
53
+ * is what the person said to correct a turn still being prepared: the
54
+ * sender must send it as the next message of the same conversation even
55
+ * though the superseded turn may not have let go yet.
56
+ */
57
+ meta: VoiceSubmitMeta) => void;
50
58
  /**
51
59
  * Id + text of the newest assistant reply. A CHANGE of id is what advances
52
60
  * the session out of `thinking` — the text alone is not enough, because two
@@ -58,6 +66,23 @@ interface Options {
58
66
  } | null;
59
67
  /** Language the user is speaking, so the recogniser is pointed correctly. */
60
68
  language: string;
69
+ /**
70
+ * H1: the end-of-speech grace — 2 s (the default), 3 s or 5 s. The ONE value
71
+ * the submit timer waits (`planSubmitTimer`); anything else is the default.
72
+ */
73
+ graceMs?: VoiceGraceMs | number;
74
+ /**
75
+ * H2: a correction was heard while a turn was still being prepared. Cancel
76
+ * that turn — abort it on the sandbox, record it as superseded
77
+ * (`markTurnSuperseded`), withdraw the proposals it made outdated. Called
78
+ * once per supersession, from an effect (never during a reduction).
79
+ */
80
+ onSupersede?: () => void;
81
+ }
82
+ /** What `onSubmit` is told about the turn beyond its words (H2). */
83
+ export interface VoiceSubmitMeta {
84
+ /** The words are a correction of a turn that was superseded. */
85
+ correction: boolean;
61
86
  }
62
87
  /**
63
88
  * Drives a live voice conversation (BOFF-7120).
@@ -73,7 +98,7 @@ interface Options {
73
98
  * microphone's lifetime, and turning "auto-speak is currently on" back off again
74
99
  * when the session ends.
75
100
  */
76
- export declare function useVoiceSession({ onSubmit, latestReply, language }: Options): {
101
+ export declare function useVoiceSession({ onSubmit, latestReply, language, graceMs: graceChoice, onSupersede, }: Options): {
77
102
  session: Readonly<VoiceSessionSnapshot>;
78
103
  /** A recoverable problem worth showing, or null when all is well. */
79
104
  notice: VoiceSessionNotice | null;
@@ -101,6 +126,10 @@ export declare function useVoiceSession({ onSubmit, latestReply, language }: Opt
101
126
  interrupting: boolean;
102
127
  /** Quiet has fallen and the correction window is running. */
103
128
  awaitingEndpoint: boolean;
129
+ /** H1: "Let me finish" is on — nothing is sent until `imDone`. */
130
+ holding: boolean;
131
+ /** H2: what is being collected corrects a turn that was superseded. */
132
+ correcting: boolean;
104
133
  /** Can a session be offered at all on this device? */
105
134
  available: boolean;
106
135
  start: () => void;
@@ -108,6 +137,10 @@ export declare function useVoiceSession({ onSubmit, latestReply, language }: Opt
108
137
  isCurrentSession: (voiceSessionId: string) => boolean;
109
138
  toggleMute: () => void;
110
139
  interrupt: () => void;
140
+ /** H1: hold auto-submit until the person is done. */
141
+ letMeFinish: () => void;
142
+ /** H1: the person is done — send what was said now. */
143
+ imDone: () => void;
111
144
  /** Say a line of the panel's own (BOFF-7803 FE9, `VoiceAnnouncement`). */
112
145
  announce: (item: VoiceAnnouncement) => void;
113
146
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"useVoiceSession.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/ui/useVoiceSession.ts"],"names":[],"mappings":"AAUA,OAAO,EAAe,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAIpE,OAAO,EAgBL,KAAK,oBAAoB,EAE1B,MAAM,uBAAuB,CAAC;AAsC/B;;;;;;;;GAQG;AACH,MAAM,MAAM,kBAAkB,GAC5B,SAAS,GAAG,eAAe,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAEvE;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,iFAAiF;IACjF,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;CAC7B;AAMD,UAAU,OAAO;IACf,+EAA+E;IAC/E;;;;;;;OAOG;IACH,QAAQ,EAAE,CACR,IAAI,EAAE,MAAM,EACZ,cAAc,EAAE,MAAM;IACtB;;;OAGG;IACH,MAAM,EAAE,eAAe,KACpB,IAAI,CAAC;IACV;;;;OAIG;IACH,WAAW,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,OAAO;;IA4xBtE,qEAAqE;;IAErE;;;;;;;OAOG;;IAEH,+DAA+D;;IAE/D;;;;;;;OAOG;;IAIH,0EAA0E;;IAE1E,6DAA6D;;IAE7D,sDAAsD;;;;uCApEvC,MAAM;;;IA2ErB,0EAA0E;qBAtsB1C,iBAAiB;IAwsBjD;;;OAGG;+BAttBuC,OAAO;IAwtBjD;;;OAGG;0BArrBE,iBAAiB;EAitB3B"}
1
+ {"version":3,"file":"useVoiceSession.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/ui/useVoiceSession.ts"],"names":[],"mappings":"AAUA,OAAO,EAAe,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAIpE,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAgBL,KAAK,oBAAoB,EAE1B,MAAM,uBAAuB,CAAC;AAsC/B;;;;;;;;GAQG;AACH,MAAM,MAAM,kBAAkB,GAC5B,SAAS,GAAG,eAAe,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAEvE;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,iFAAiF;IACjF,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;CAC7B;AAMD,UAAU,OAAO;IACf,+EAA+E;IAC/E;;;;;;;OAOG;IACH,QAAQ,EAAE,CACR,IAAI,EAAE,MAAM,EACZ,cAAc,EAAE,MAAM;IACtB;;;OAGG;IACH,MAAM,EAAE,eAAe;IACvB;;;;;OAKG;IACH,IAAI,EAAE,eAAe,KAClB,IAAI,CAAC;IACV;;;;OAIG;IACH,WAAW,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IAChC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,OAAO,EAAE,WAAW,EACpB,WAAW,GACZ,EAAE,OAAO;;IAq4BJ,qEAAqE;;IAErE;;;;;;;OAOG;;IAEH,+DAA+D;;IAE/D;;;;;;;OAOG;;IAIH,0EAA0E;;IAE1E,6DAA6D;;IAE7D,kEAAkE;;IAElE,uEAAuE;;IAEvE,sDAAsD;;;;uCA1GvC,MAAM;;;IAiHrB,qDAAqD;;IAErD,uDAAuD;;IAEvD,0EAA0E;qBA7yB1C,iBAAiB;IA+yBjD;;;OAGG;+BA7zBuC,OAAO;IA+zBjD;;;OAGG;0BA5xBE,iBAAiB;EA2zB3B"}
@@ -1,176 +1,214 @@
1
1
  import { useVoiceInput as e } from "../voice/useVoiceInput.js";
2
- import { speechFeed as t } from "../speech/speechFeed.js";
3
- import { INITIAL_SESSION as n, INTERRUPT_MAX_MS as ee, NOTHING_HEARD as r, SILENCE_SUBMIT_MS as te, advanceHeardClock as ne, isCapturing as i, isSubmittable as re, planListeningHold as ie, planReplyConsumption as ae, planVoiceAnnouncement as oe, spokenSoFar as a, voiceSessionReducer as se } from "../voice/voiceSession.js";
4
- import { captionChunks as ce } from "../voice/speechPlayback.js";
5
- import { turnClockMs as o } from "../turnTimeline.js";
6
- import { useAssistantSpeech as le } from "./AssistantSpeechProvider.js";
7
- import { useCallback as s, useEffect as c, useLayoutEffect as ue, useMemo as de, useReducer as fe, useRef as l, useState as u } from "react";
2
+ import { planSubmitTimer as t, resolveVoiceGraceMs as n } from "../voice/handsFree.js";
3
+ import { speechFeed as r } from "../speech/speechFeed.js";
4
+ import { INITIAL_SESSION as ee, INTERRUPT_MAX_MS as te, NOTHING_HEARD as i, advanceHeardClock as ne, isCapturing as a, isSubmittable as re, planListeningHold as ie, planReplyConsumption as ae, planVoiceAnnouncement as oe, shouldRecognise as se, spokenSoFar as o, voiceSessionReducer as ce } from "../voice/voiceSession.js";
5
+ import { isTurnSuperseded as s } from "../voice/supersededTurns.js";
6
+ import { captionChunks as le } from "../voice/speechPlayback.js";
7
+ import { turnClockMs as c } from "../turnTimeline.js";
8
+ import { useAssistantSpeech as ue } from "./AssistantSpeechProvider.js";
9
+ import { useCallback as l, useEffect as u, useLayoutEffect as de, useMemo as fe, useReducer as pe, useRef as d, useState as f } from "react";
8
10
  //#region src/shared/assistant/ui/useVoiceSession.ts
9
- var pe = 4e3, me = 2500, he = 5e3, ge = 12e3, _e = "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=";
10
- function d({ onSubmit: d, latestReply: f, language: ve }) {
11
- let p = l(null), [m, h] = fe(s((e, t) => se(e, t, p.current), []), n), g = le(), { setSessionSpeaking: _, setSessionMicOpen: v, speakingMessageId: y, stop: b } = g, x = l(g.toggle), S = g.toggle;
12
- ue(() => {
13
- x.current = S;
14
- }, [S]);
15
- let C = l(null), w = l({}), T = l(r), E = l(null), D = l(!1), O = l(null), [k, A] = u(null), [j, M] = u(!1), [N, P] = u(!1), F = l(m);
16
- F.current = m;
17
- let I = l([]), L = l(null), R = l(!1), z = s(() => {
18
- I.current = [], L.current = null, R.current = !1;
19
- }, []), B = s(() => {
11
+ var me = 4e3, he = 2500, ge = 5e3, _e = 12e3, ve = "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=";
12
+ function ye({ onSubmit: ye, latestReply: p, language: be, graceMs: xe, onSupersede: m }) {
13
+ let h = n(xe), g = d(null), [_, v] = pe(l((e, t) => ce(e, t, g.current), []), ee), y = ue(), { setSessionSpeaking: b, setSessionMicOpen: x, speakingMessageId: S, stop: C } = y, w = d(y.toggle), T = y.toggle;
14
+ de(() => {
15
+ w.current = T;
16
+ }, [T]);
17
+ let E = d(null), D = d({}), O = d(i), k = d(null), A = d(!1), j = d(null), [M, N] = f(null), [P, F] = f(!1), [I, L] = f(!1), [R, z] = f(!1), B = d(_);
18
+ B.current = _;
19
+ let V = d([]), H = d(null), U = d(!1), W = l(() => {
20
+ V.current = [], H.current = null, U.current = !1;
21
+ }, []), G = l(() => {
20
22
  for (;;) {
21
- let e = I.current.shift();
23
+ let e = V.current.shift();
22
24
  if (!e) return !1;
23
25
  if (!(e.stillWanted && !e.stillWanted())) {
24
- L.current = e, p.current = p.current ? `${p.current} ${e.text}` : e.text;
26
+ H.current = e, g.current = g.current ? `${g.current} ${e.text}` : e.text;
25
27
  try {
26
- x.current(e.id, e.text, e.lang);
28
+ w.current(e.id, e.text, e.lang);
27
29
  } catch {
28
- L.current = null;
30
+ H.current = null;
29
31
  continue;
30
32
  }
31
33
  return !0;
32
34
  }
33
35
  }
34
- }, []), V = l(!1), H = s((e) => {
36
+ }, []), K = d(!1), q = l((e) => {
35
37
  let t = ie({
36
38
  requested: e,
37
- held: V.current,
38
- muted: F.current.muted
39
+ held: K.current,
40
+ muted: B.current.muted
39
41
  });
40
- V.current = t.held, t.mute !== null && h({
42
+ K.current = t.held, t.mute !== null && v({
41
43
  type: "mute",
42
44
  muted: t.mute
43
45
  });
44
- }, []), U = s((e) => {
45
- let t = oe(F.current);
46
- t !== "drop" && (L.current?.id === e.id || I.current.some((t) => t.id === e.id) || (I.current.push(e), t === "start" && !R.current && (R.current = !0, h({ type: "announce" }))));
47
- }, []), W = s((e) => {
48
- if (F.current.phase !== "thinking") {
49
- U(e);
46
+ }, []), J = l((e) => {
47
+ let t = oe(B.current);
48
+ t !== "drop" && (H.current?.id === e.id || V.current.some((t) => t.id === e.id) || (V.current.push(e), t === "start" && !U.current && (U.current = !0, v({ type: "announce" }))));
49
+ }, []), Se = l((e) => {
50
+ if (B.current.phase !== "thinking") {
51
+ J(e);
50
52
  return;
51
53
  }
52
- I.current.push(e), R.current = !0, h({ type: "answered-locally" });
53
- }, [U]), G = l(f);
54
- G.current = f, c(() => {
55
- if (m.phase === "speaking") {
56
- let e = O.current;
57
- p.current = e && E.current === e.id && f?.id !== e.id ? e.text : f?.text ?? null;
54
+ V.current.push(e), U.current = !0, v({ type: "answered-locally" });
55
+ }, [J]), Ce = d(p);
56
+ Ce.current = p, u(() => {
57
+ if (_.phase === "speaking") {
58
+ let e = j.current;
59
+ g.current = e && k.current === e.id && p?.id !== e.id ? e.text : p?.text ?? null;
58
60
  return;
59
61
  }
60
- if (p.current === null) return;
62
+ if (g.current === null) return;
61
63
  let e = setTimeout(() => {
62
- p.current = null;
63
- }, me);
64
+ g.current = null;
65
+ }, he);
64
66
  return () => clearTimeout(e);
65
- }, [m.phase, f]);
66
- let K = s((e, t) => {
67
- h({
67
+ }, [_.phase, p]);
68
+ let Y = l((e, t) => {
69
+ v({
68
70
  type: "speech",
69
71
  transcript: e,
70
- final: t
72
+ final: t,
73
+ at: Date.now()
71
74
  });
72
- }, []), q = l(0), J = s(() => {
73
- z(), b(), q.current = Date.now(), M(!0), h({ type: "interrupt" });
74
- }, [b, z]), Y = e({
75
- onFinalize: s((e) => K(e, !0), [K]),
76
- language: ve
75
+ }, []), we = d(0), Te = l(() => {
76
+ W(), C(), we.current = Date.now(), F(!0), v({ type: "interrupt" });
77
+ }, [C, W]), X = e({
78
+ onFinalize: l((e) => Y(e, !0), [Y]),
79
+ language: be
77
80
  });
78
- c(() => {
79
- Y.interim && K(Y.interim, !1);
80
- }, [Y.interim, K]), c(() => {
81
- if (C.current && clearTimeout(C.current), P(!1), T.current = ne(T.current, m, o()), m.phase !== "listening" || m.muted || !re(a(m))) return;
82
- let e = setTimeout(() => P(!0), 800), t = o();
83
- return C.current = setTimeout(() => {
84
- w.current = {
85
- speechEnded: T.current.at ?? t,
86
- graceStarted: t,
87
- graceEnded: o()
88
- }, h({ type: "silence" });
89
- }, te), () => {
90
- clearTimeout(e), C.current && clearTimeout(C.current);
81
+ u(() => {
82
+ X.interim && Y(X.interim, !1);
83
+ }, [X.interim, Y]), u(() => {
84
+ E.current && clearTimeout(E.current), L(!1), O.current = ne(O.current, _, c());
85
+ let e = t({
86
+ phase: _.phase,
87
+ muted: _.muted,
88
+ spoken: o(_),
89
+ holding: R,
90
+ graceMs: h
91
+ });
92
+ if (!e.arm) return;
93
+ let n = setTimeout(() => L(!0), Math.min(800, e.delayMs)), r = c();
94
+ return E.current = setTimeout(() => {
95
+ D.current = {
96
+ speechEnded: O.current.at ?? r,
97
+ graceStarted: r,
98
+ graceEnded: c()
99
+ }, v({
100
+ type: "silence",
101
+ at: Date.now()
102
+ });
103
+ }, e.delayMs), () => {
104
+ clearTimeout(n), E.current && clearTimeout(E.current);
91
105
  };
92
- }, [m]), c(() => {
93
- if (!j) return;
94
- let e = Date.now() - q.current, t = Y.isListening ? Math.max(0, 500 - e) : Math.max(0, ee - e), n = setTimeout(() => M(!1), t);
106
+ }, [
107
+ _,
108
+ R,
109
+ h
110
+ ]), u(() => {
111
+ (_.phase !== "listening" || _.muted) && z(!1);
112
+ }, [_.phase, _.muted]), u(() => {
113
+ if (!P) return;
114
+ let e = Date.now() - we.current, t = X.isListening ? Math.max(0, 500 - e) : Math.max(0, te - e), n = setTimeout(() => F(!1), t);
95
115
  return () => clearTimeout(n);
96
- }, [j, Y.isListening]);
97
- let X = l("");
98
- m.phase === "listening" && (X.current = a(m)), c(() => {
99
- if (m.phase !== "thinking") return;
100
- v(!1), z();
101
- let e = X.current.trim();
102
- e && d(e, Z.current ?? `vs-orphan-${Date.now().toString(36)}`, w.current), w.current = {}, T.current = r;
103
- }, [m.phase]), c(() => {
104
- let e = ae(f?.id, m.phase, E.current);
105
- e.claim && (E.current = f?.id ?? null, e.advance && f && h({
116
+ }, [P, X.isListening]);
117
+ let Ee = d("");
118
+ _.phase === "listening" && (Ee.current = o(_)), u(() => {
119
+ if (_.phase !== "thinking") return;
120
+ x(!1), W();
121
+ let e = Ee.current.trim();
122
+ e && ye(e, Z.current ?? `vs-orphan-${Date.now().toString(36)}`, D.current, { correction: B.current.correcting }), D.current = {}, O.current = i;
123
+ }, [_.phase]), u(() => {
124
+ let e = ae(p?.id, _.phase, k.current, s(p?.id));
125
+ e.claim && (k.current = p?.id ?? null, e.advance && p && v({
106
126
  type: "reply",
107
- text: f.text
127
+ text: p.text
108
128
  }));
109
- }, [f, m.phase]), c(() => {
110
- if (y === null || m.phase !== "thinking" || E.current === y) return;
111
- let e = t.peek(y);
112
- !e || e.channel !== "voice" || e.voiceSessionId === void 0 || e.voiceSessionId !== Z.current || (E.current = y, O.current = {
113
- id: y,
129
+ }, [p, _.phase]), u(() => {
130
+ if (S === null || _.phase !== "thinking" || k.current === S) return;
131
+ if (s(S)) {
132
+ C();
133
+ return;
134
+ }
135
+ let e = r.peek(S);
136
+ !e || e.channel !== "voice" || e.voiceSessionId === void 0 || e.voiceSessionId !== Z.current || (k.current = S, j.current = {
137
+ id: S,
114
138
  text: e.text
115
- }, h({
139
+ }, v({
116
140
  type: "reply-audio-started",
117
- replyId: y
141
+ replyId: S
118
142
  }));
119
- }, [y, m.phase]), c(() => {
120
- let e = y !== null;
121
- D.current && !e && (L.current = null, (F.current.phase !== "speaking" || !B()) && h({ type: "speech-ended" })), D.current = e;
122
- }, [y, B]), c(() => {
123
- m.phase !== "listening" && A((e) => e === "nothing-heard" ? null : e);
124
- }, [m.phase]), c(() => {
125
- if (m.phase !== "speaking" || y !== null) return;
143
+ }, [
144
+ S,
145
+ _.phase,
146
+ C
147
+ ]), u(() => {
148
+ let e = S !== null;
149
+ A.current && !e && (H.current = null, (B.current.phase !== "speaking" || !G()) && v({ type: "speech-ended" })), A.current = e;
150
+ }, [S, G]), u(() => {
151
+ _.phase !== "listening" && N((e) => e === "nothing-heard" ? null : e);
152
+ }, [_.phase]), u(() => {
153
+ if (_.phase !== "speaking" || S !== null) return;
126
154
  let e = setTimeout(() => {
127
- D.current || (z(), h({ type: "speech-ended" }));
128
- }, pe);
155
+ A.current || (W(), v({ type: "speech-ended" }));
156
+ }, me);
129
157
  return () => clearTimeout(e);
130
158
  }, [
131
- m.phase,
132
- y,
133
- z
134
- ]), c(() => {
135
- let e = i(m);
136
- v(e), e ? Y.isActive || Y.start("auto") : Y.isActive && Y.stop();
159
+ _.phase,
160
+ S,
161
+ W
162
+ ]);
163
+ let De = d(m);
164
+ De.current = m;
165
+ let Oe = d(0);
166
+ u(() => {
167
+ let e = _.superseded;
168
+ e !== Oe.current && (Oe.current = e, e !== 0 && (W(), C(), De.current?.()));
169
+ }, [
170
+ _.superseded,
171
+ W,
172
+ C
173
+ ]), u(() => {
174
+ x(a(_)), se(_) ? X.isActive || X.start("auto") : X.isActive && X.stop();
137
175
  }, [
138
- m,
139
- Y,
140
- v
141
- ]), c(() => {
142
- m.phase !== "speaking" || !R.current || (R.current = !1, B() || h({ type: "speech-ended" }));
143
- }, [m.phase, B]), c(() => {
144
- Y.error === "not-allowed" || Y.error === "audio-capture" ? h({
176
+ _,
177
+ X,
178
+ x
179
+ ]), u(() => {
180
+ _.phase !== "speaking" || !U.current || (U.current = !1, G() || v({ type: "speech-ended" }));
181
+ }, [_.phase, G]), u(() => {
182
+ X.error === "not-allowed" || X.error === "audio-capture" ? v({
145
183
  type: "fail",
146
184
  error: "mic-denied"
147
- }) : Y.error === "language-not-supported" ? h({
185
+ }) : X.error === "language-not-supported" ? v({
148
186
  type: "fail",
149
187
  error: "no-speech-support"
150
- }) : Y.error === "network" ? A("network") : Y.error === "unknown" && A("recognition-failed");
151
- }, [Y.error]), c(() => {
152
- Y.error !== null || !Y.isListening || A((e) => e === "network" || e === "recognition-failed" ? null : e);
153
- }, [Y.error, Y.isListening]), c(() => {
154
- if (Y.isListening && A((e) => e === "not-capturing" ? null : e), !i(m) || Y.isListening) return;
155
- let e = setTimeout(() => A("not-capturing"), he);
188
+ }) : X.error === "network" ? N("network") : X.error === "unknown" && N("recognition-failed");
189
+ }, [X.error]), u(() => {
190
+ X.error !== null || !X.isListening || N((e) => e === "network" || e === "recognition-failed" ? null : e);
191
+ }, [X.error, X.isListening]), u(() => {
192
+ if (X.isListening && N((e) => e === "not-capturing" ? null : e), !a(_) || X.isListening) return;
193
+ let e = setTimeout(() => N("not-capturing"), ge);
156
194
  return () => clearTimeout(e);
157
- }, [m, Y.isListening]), c(() => {
158
- if (m.phase !== "listening" || m.muted || !Y.isListening || a(m) !== "") return;
159
- let e = setTimeout(() => A("nothing-heard"), ge);
195
+ }, [_, X.isListening]), u(() => {
196
+ if (_.phase !== "listening" || _.muted || !X.isListening || o(_) !== "") return;
197
+ let e = setTimeout(() => N("nothing-heard"), _e);
160
198
  return () => clearTimeout(e);
161
- }, [m, Y.isListening]), c(() => {
162
- a(m) !== "" && A(null);
163
- }, [m]);
164
- let Z = l(null), Q = s(() => {
165
- if (M(!1), P(!1), z(), V.current = !1, !Y.isSupported) {
166
- h({
199
+ }, [_, X.isListening]), u(() => {
200
+ o(_) !== "" && N(null);
201
+ }, [_]);
202
+ let Z = d(null), Q = l(() => {
203
+ if (F(!1), L(!1), z(!1), W(), K.current = !1, !X.isSupported) {
204
+ v({
167
205
  type: "fail",
168
206
  error: "no-speech-support"
169
207
  });
170
208
  return;
171
209
  }
172
- if (g.isBlocked) {
173
- h({
210
+ if (y.isBlocked) {
211
+ v({
174
212
  type: "fail",
175
213
  error: "no-voice-support"
176
214
  });
@@ -185,67 +223,90 @@ function d({ onSubmit: d, latestReply: f, language: ve }) {
185
223
  } catch {}
186
224
  try {
187
225
  if (typeof window < "u" && typeof window.Audio == "function") {
188
- let e = new window.Audio(_e);
226
+ let e = new window.Audio(ve);
189
227
  e.volume = 0, e.play().catch(() => {});
190
228
  }
191
229
  } catch {}
192
- _(!0), A(null), E.current = G.current?.id ?? null, O.current = null, Z.current = `vs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`, h({ type: "start" });
230
+ b(!0), N(null), k.current = Ce.current?.id ?? null, j.current = null, Z.current = `vs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`, v({ type: "start" });
193
231
  }, [
194
- Y.isSupported,
195
- g.isBlocked,
196
- _,
197
- z
198
- ]), $ = s(() => {
199
- M(!1), P(!1), z(), V.current = !1, Z.current = null, h({ type: "end" }), b(), _(!1);
232
+ X.isSupported,
233
+ y.isBlocked,
234
+ b,
235
+ W
236
+ ]), ke = l(() => {
237
+ F(!1), L(!1), z(!1), W(), K.current = !1, Z.current = null, v({ type: "end" }), C(), b(!1);
200
238
  }, [
239
+ C,
201
240
  b,
202
- _,
203
- z
204
- ]), ye = s((e) => Z.current === e, []), be = s(() => {
205
- h({
241
+ W
242
+ ]), Ae = l((e) => Z.current === e, []), je = l(() => {
243
+ let e = B.current;
244
+ e.phase !== "listening" || e.muted || z(!0);
245
+ }, []), Me = l(() => {
246
+ z(!1);
247
+ let e = B.current;
248
+ if (e.phase !== "listening" || e.muted || !re(o(e))) return;
249
+ let t = c();
250
+ D.current = {
251
+ speechEnded: O.current.at ?? t,
252
+ graceStarted: t,
253
+ graceEnded: t
254
+ }, v({
255
+ type: "silence",
256
+ at: Date.now()
257
+ });
258
+ }, []), Ne = l(() => {
259
+ v({
206
260
  type: "mute",
207
- muted: !F.current.muted
261
+ muted: !B.current.muted
208
262
  });
209
263
  }, []);
210
- c(() => () => {
211
- _(!1), v(!1);
212
- }, [_, v]);
213
- let xe = ce(g.spokenChunks, m.phase, y);
214
- return de(() => ({
215
- session: m,
216
- notice: k,
217
- spokenChunks: xe,
218
- capturing: Y.isListening,
219
- active: m.phase !== "idle" && (m.phase !== "ended" || m.error !== null),
220
- interrupting: j,
221
- awaitingEndpoint: N,
222
- available: Y.isSupported && !g.isBlocked,
264
+ u(() => () => {
265
+ b(!1), x(!1);
266
+ }, [b, x]);
267
+ let $ = le(y.spokenChunks, _.phase, S);
268
+ return fe(() => ({
269
+ session: _,
270
+ notice: M,
271
+ spokenChunks: $,
272
+ capturing: X.isListening,
273
+ active: _.phase !== "idle" && (_.phase !== "ended" || _.error !== null),
274
+ interrupting: P,
275
+ awaitingEndpoint: I,
276
+ holding: R,
277
+ correcting: _.correcting,
278
+ available: X.isSupported && !y.isBlocked,
223
279
  start: Q,
224
- end: $,
225
- isCurrentSession: ye,
226
- toggleMute: be,
227
- interrupt: J,
228
- announce: U,
229
- holdListening: H,
230
- answerLocally: W
280
+ end: ke,
281
+ isCurrentSession: Ae,
282
+ toggleMute: Ne,
283
+ interrupt: Te,
284
+ letMeFinish: je,
285
+ imDone: Me,
286
+ announce: J,
287
+ holdListening: q,
288
+ answerLocally: Se
231
289
  }), [
232
- m,
233
- k,
234
- j,
235
- N,
236
- Y.isListening,
237
- Y.isSupported,
238
- g.isBlocked,
239
- xe,
240
- Q,
290
+ _,
291
+ M,
292
+ P,
293
+ I,
294
+ R,
295
+ X.isListening,
296
+ X.isSupported,
297
+ y.isBlocked,
241
298
  $,
242
- ye,
243
- be,
299
+ Q,
300
+ ke,
301
+ Ae,
302
+ Ne,
303
+ Te,
304
+ je,
305
+ Me,
244
306
  J,
245
- U,
246
- H,
247
- W
307
+ q,
308
+ Se
248
309
  ]);
249
310
  }
250
311
  //#endregion
251
- export { d as useVoiceSession };
312
+ export { ye as useVoiceSession };
@@ -44,8 +44,22 @@ interface Props {
44
44
  capturing?: boolean;
45
45
  /** The user has cut the assistant off and capture has not resumed yet. */
46
46
  interrupting?: boolean;
47
- /** Quiet has fallen and the two-second correction window is running. */
47
+ /** Quiet has fallen and the correction window is running. */
48
48
  awaitingEndpoint?: boolean;
49
+ /**
50
+ * ★ H1: "Let me finish" is on — nothing is sent until the person says they
51
+ * are done. The control swaps to "I'm done" while it is.
52
+ */
53
+ holding?: boolean;
54
+ /**
55
+ * ★ H2: what is being heard corrects an answer that was still being
56
+ * prepared (it was cancelled when the person began to speak).
57
+ */
58
+ correcting?: boolean;
59
+ /** Hold auto-send (H1). With `onImDone`, shows the control. */
60
+ onLetMeFinish?: () => void;
61
+ /** End the hold and send what was said now (H1). */
62
+ onImDone?: () => void;
49
63
  /**
50
64
  * ★ BOFF-7803 FE9: a change of this conversation waiting for its on-screen
51
65
  * button. The thread's card is covered (and inert) while the session runs,
@@ -1 +1 @@
1
- {"version":3,"file":"AssistantVoiceSession.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/ui/voice/AssistantVoiceSession.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,EAAE,EACP,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAyB7D,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAElD,UAAU,KAAK;IACb,OAAO,EAAE,oBAAoB,CAAC;IAC9B,iEAAiE;IACjE,KAAK,CAAC,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAC;IAC3C,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACnC,gEAAgE;IAChE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACpC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;IACjD,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0EAA0E;IAC1E,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,4DAA4D;IAC5D,YAAY,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1D,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,mFAAmF;IACnF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;OAKG;IACH,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,6EAA6E;IAC7E,YAAY,CAAC,EAAE;QACb,OAAO,EAAE,SAAS,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,IAAI,CAAC;KACrB,GAAG,IAAI,CAAC;CACV;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,qBAAqB,EAAE,EAAE,CAAC,KAAK,CAuuB3C,CAAC"}
1
+ {"version":3,"file":"AssistantVoiceSession.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/ui/voice/AssistantVoiceSession.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,EAAE,EACP,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAyB7D,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAElD,UAAU,KAAK;IACb,OAAO,EAAE,oBAAoB,CAAC;IAC9B,iEAAiE;IACjE,KAAK,CAAC,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAC;IAC3C,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACnC,gEAAgE;IAChE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACpC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;IACjD,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0EAA0E;IAC1E,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,4DAA4D;IAC5D,YAAY,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1D,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,mFAAmF;IACnF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB;;;;;OAKG;IACH,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,6EAA6E;IAC7E,YAAY,CAAC,EAAE;QACb,OAAO,EAAE,SAAS,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,IAAI,CAAC;KACrB,GAAG,IAAI,CAAC;CACV;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,qBAAqB,EAAE,EAAE,CAAC,KAAK,CA+zB3C,CAAC"}