@burdenoff/fe-libs 2026.1015.1 → 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.
- package/dist/shared/assistant/index.d.ts +1 -0
- package/dist/shared/assistant/ui/AssistantChatArea.js +939 -941
- package/dist/shared/assistant/ui/useVoiceSession.d.ts +20 -5
- package/dist/shared/assistant/ui/useVoiceSession.js +11 -4
- package/dist/shared/assistant/ui/voice/AssistantVoiceSession.d.ts +2 -2
- package/dist/shared/assistant/ui/voice/AssistantVoiceSession.js +94 -88
- package/dist/shared/assistant/voice/voiceReply.d.ts +23 -0
- package/dist/shared/assistant/voice/voiceReply.js +13 -0
- package/dist/shared/assistant/voice/voiceSession.d.ts +10 -0
- package/dist/shared/assistant/voice/voiceSession.js +7 -0
- package/dist/shared-assistant.js +247 -246
- package/package.json +1 -1
|
@@ -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:
|
|
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 &&
|
|
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
|
-
}, [
|
|
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 {
|
|
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?:
|
|
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
|
|
@@ -1,40 +1,40 @@
|
|
|
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
|
|
5
|
-
import { VOICE_THINKING_LABEL as
|
|
6
|
-
import { spokenSoFar as
|
|
7
|
-
import { useAvatarCopy as
|
|
8
|
-
import { ASSISTANT_PANEL_ATTR as
|
|
9
|
-
import { useLiveAvatarHandsFreeGuard as
|
|
10
|
-
import { AssistantAttachmentButton as
|
|
11
|
-
import { AssistantAttachmentChips as
|
|
12
|
-
import { AssistantVoiceOrb as
|
|
13
|
-
import { VoiceMiniController as
|
|
14
|
-
import { VoiceStage as
|
|
15
|
-
import { VoiceTranscriptDrawer as
|
|
16
|
-
import { escapeBelongsToLayerAbove as
|
|
17
|
-
import { HandsFreePrivacyNotice as
|
|
18
|
-
import { useCallback as
|
|
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, useLiveAvatarTab as ce, useLiveAvatarVoiceView as le } from "../assistantLiveAvatarSeam.js";
|
|
10
|
+
import { AssistantAttachmentButton as ue } from "../composer/AssistantAttachmentButton.js";
|
|
11
|
+
import { AssistantAttachmentChips as de } from "../composer/AssistantAttachmentChips.js";
|
|
12
|
+
import { AssistantVoiceOrb as fe } from "./AssistantVoiceOrb.js";
|
|
13
|
+
import { VoiceMiniController as pe } from "./VoiceMiniController.js";
|
|
14
|
+
import { VoiceStage as me } from "./VoiceStage.js";
|
|
15
|
+
import { VoiceTranscriptDrawer as he } from "./VoiceTranscriptDrawer.js";
|
|
16
|
+
import { escapeBelongsToLayerAbove as ge, voiceSessionEscapeAction as _e } from "./voiceSessionEscape.js";
|
|
17
|
+
import { HandsFreePrivacyNotice as ve } from "./HandsFreePrivacyNotice.js";
|
|
18
|
+
import { useCallback as ye, useEffect as o, useMemo as be, useRef as s, useState as c } from "react";
|
|
19
19
|
import { Maximize2 as xe, Minimize2 as Se, ScrollText as Ce, X as l } from "lucide-react";
|
|
20
20
|
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
21
21
|
//#region src/shared/assistant/ui/voice/AssistantVoiceSession.tsx
|
|
22
|
-
var f = ({ session: f, turns:
|
|
23
|
-
let A = n(), { muted: j, error: M } = f, N = s(null), P = s(null), [
|
|
24
|
-
|
|
25
|
-
let
|
|
22
|
+
var f = ({ session: f, turns: m = [], onSaveTranscript: Ee, onContinueInChat: De, notice: h = null, privacyNotice: g = null, spoken: _, onInterrupt: Oe, onToggleMute: v, attachments: y = [], onAttachFiles: ke, onRemoveAttachment: Ae, onRetryAttachment: je, canAttachMore: Me = !0, preparingAttachments: b = 0, attachmentsLocked: Ne = !1, attachmentNotice: x = null, spokenChunks: S = null, onEnd: C, capturing: Pe = !0, interrupting: Fe = !1, awaitingEndpoint: Ie = !1, holding: w = !1, correcting: Le = !1, onLetMeFinish: T, onImDone: E, proposalChip: D = null, proposalTray: O = null, renderAfterTurn: Re, onViewChange: k }) => {
|
|
23
|
+
let A = n(), { muted: j, error: M } = f, N = s(null), P = s(null), [ze, F] = c("mini"), I = ce().open, [Be, Ve] = c(I);
|
|
24
|
+
Be !== I && (Ve(I), I && ze !== "mini" && F("mini"));
|
|
25
|
+
let L = le(ze, f.phase === "ended"), He = se();
|
|
26
26
|
o(() => {
|
|
27
|
-
k?.(
|
|
28
|
-
}, [k,
|
|
29
|
-
let
|
|
27
|
+
k?.(L);
|
|
28
|
+
}, [k, L]);
|
|
29
|
+
let R = ie(), z = L === "full", B = L === "mini", V = L === "mini" ? "panel" : L === "panel" ? "full" : "mini", [H, U] = c(!1);
|
|
30
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 =
|
|
34
|
-
f.phase !== "listening" && f.phase !== "thinking" || j || G.current ||
|
|
35
|
-
}, [j, f]), J =
|
|
31
|
+
let W = O !== null && !B, G = s(!1), K = s(v);
|
|
32
|
+
K.current = v;
|
|
33
|
+
let q = ye(() => {
|
|
34
|
+
f.phase !== "listening" && f.phase !== "thinking" || j || G.current || re(f) || (G.current = !0, K.current());
|
|
35
|
+
}, [j, f]), J = ye(() => {
|
|
36
36
|
G.current && (G.current = !1, K.current());
|
|
37
|
-
}, []),
|
|
37
|
+
}, []), Ue = q;
|
|
38
38
|
o(() => {
|
|
39
39
|
let e = () => {
|
|
40
40
|
document.visibilityState === "hidden" ? q() : J();
|
|
@@ -47,12 +47,12 @@ var f = ({ session: f, turns: Te = [], onSaveTranscript: Ee, onContinueInChat: D
|
|
|
47
47
|
};
|
|
48
48
|
}, []), o(() => {
|
|
49
49
|
document.activeElement?.closest("[data-hands-free-notice]") || N.current?.focus();
|
|
50
|
-
}, []), e(P, { fallbackSelector: `[${
|
|
50
|
+
}, []), e(P, { fallbackSelector: `[${ae}]` }), o(() => {
|
|
51
51
|
let e = (e) => {
|
|
52
52
|
if (e.key === "Escape") {
|
|
53
|
-
let t =
|
|
53
|
+
let t = _e({
|
|
54
54
|
defaultPrevented: e.defaultPrevented,
|
|
55
|
-
layerAbove:
|
|
55
|
+
layerAbove: ge({
|
|
56
56
|
target: e.target,
|
|
57
57
|
session: P.current,
|
|
58
58
|
doc: document
|
|
@@ -60,7 +60,7 @@ var f = ({ session: f, turns: Te = [], onSaveTranscript: Ee, onContinueInChat: D
|
|
|
60
60
|
transcriptOpen: H
|
|
61
61
|
});
|
|
62
62
|
if (t === "ignore") return;
|
|
63
|
-
e.preventDefault(), t === "close-transcript" ? U(!1) :
|
|
63
|
+
e.preventDefault(), t === "close-transcript" ? U(!1) : g ? g.decline() : W ? O?.onClose() : C();
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
if (e.key !== "Tab" || !z) return;
|
|
@@ -76,48 +76,48 @@ var f = ({ session: f, turns: Te = [], onSaveTranscript: Ee, onContinueInChat: D
|
|
|
76
76
|
H,
|
|
77
77
|
W,
|
|
78
78
|
O,
|
|
79
|
-
|
|
79
|
+
g
|
|
80
80
|
]);
|
|
81
|
-
let Y =
|
|
81
|
+
let Y = h !== null && M === null, X = He && h === null && M === null, We = re(f), Z = T && E && f.phase === "listening" && !j && M === null ? {
|
|
82
82
|
letMeFinish: T,
|
|
83
83
|
imDone: E
|
|
84
|
-
} : null,
|
|
84
|
+
} : null, Ge = Z !== null;
|
|
85
85
|
o(() => {
|
|
86
|
-
if (
|
|
86
|
+
if (Ge) return;
|
|
87
87
|
let e = document.activeElement;
|
|
88
88
|
(!e || e === document.body) && N.current?.focus();
|
|
89
|
-
}, [
|
|
90
|
-
let Q =
|
|
89
|
+
}, [Ge]);
|
|
90
|
+
let Q = i({
|
|
91
91
|
snapshot: f,
|
|
92
|
-
capturing:
|
|
93
|
-
interrupting:
|
|
94
|
-
awaitingEndpoint:
|
|
95
|
-
}),
|
|
92
|
+
capturing: Pe,
|
|
93
|
+
interrupting: Fe,
|
|
94
|
+
awaitingEndpoint: Ie
|
|
95
|
+
}), Ke = (() => {
|
|
96
96
|
switch (Q) {
|
|
97
97
|
case "connecting": return A("voiceSession.connecting", "Starting the microphone…");
|
|
98
98
|
case "endpoint-grace": return A("voiceSession.endpointGrace", "Still listening — take your time");
|
|
99
99
|
case "interrupting": return A("voiceSession.interrupting", "Stopped — go ahead");
|
|
100
|
-
case "thinking": return A(
|
|
100
|
+
case "thinking": return A(a.key, a.fallback);
|
|
101
101
|
case "speaking": return A("voiceSession.speaking", "Speaking — tap to stop");
|
|
102
102
|
case "mic-error":
|
|
103
103
|
case "ended": return "";
|
|
104
104
|
case "listening": return j ? A("voiceSession.muted", "Microphone off") : A("voiceSession.listening", "Listening…");
|
|
105
105
|
default: return Q;
|
|
106
106
|
}
|
|
107
|
-
})(),
|
|
107
|
+
})(), qe = !j && (Q === "listening" || Q === "endpoint-grace"), Je = qe && w ? A("voiceSession.holding", "Take your time — tap “I’m done” when you finish") : qe && Le ? A("voiceSession.correcting", "Stopped that answer — listening to your correction") : Ke, $ = ne({
|
|
108
108
|
tr: A,
|
|
109
109
|
stage: Q,
|
|
110
110
|
muted: j,
|
|
111
|
-
label:
|
|
112
|
-
}),
|
|
111
|
+
label: Je
|
|
112
|
+
}), Ye = te(Je, $), Xe = Q === "thinking";
|
|
113
113
|
return /* @__PURE__ */ d("div", {
|
|
114
114
|
ref: P,
|
|
115
|
-
className: `flex flex-col items-center justify-between bg-bg-sunken ${
|
|
115
|
+
className: `flex flex-col items-center justify-between bg-bg-sunken ${L === "full" ? "fixed inset-x-0 bottom-0 top-14 z-[60] px-6 py-8" : L === "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"}`,
|
|
116
116
|
role: "dialog",
|
|
117
117
|
"aria-modal": z || void 0,
|
|
118
118
|
"aria-label": A("voiceSession.title", "Voice conversation"),
|
|
119
119
|
"data-testid": "assistant-voice-session",
|
|
120
|
-
[
|
|
120
|
+
[oe]: "",
|
|
121
121
|
children: [
|
|
122
122
|
/* @__PURE__ */ d("button", {
|
|
123
123
|
ref: N,
|
|
@@ -140,12 +140,12 @@ var f = ({ session: f, turns: Te = [], onSaveTranscript: Ee, onContinueInChat: D
|
|
|
140
140
|
Y ? /* @__PURE__ */ u("p", {
|
|
141
141
|
className: "max-w-xs text-center text-xs text-status-warning",
|
|
142
142
|
"data-testid": "assistant-voice-notice",
|
|
143
|
-
children: p(
|
|
143
|
+
children: p(h, A)
|
|
144
144
|
}) : null,
|
|
145
145
|
X ? /* @__PURE__ */ u("p", {
|
|
146
146
|
className: "max-w-xs text-center text-xs text-text-secondary",
|
|
147
147
|
"data-testid": "assistant-voice-avatar-hands-free",
|
|
148
|
-
children:
|
|
148
|
+
children: R.handsFreeOff
|
|
149
149
|
}) : null,
|
|
150
150
|
/* @__PURE__ */ u("span", {
|
|
151
151
|
className: "sr-only start-0 top-0",
|
|
@@ -153,7 +153,7 @@ var f = ({ session: f, turns: Te = [], onSaveTranscript: Ee, onContinueInChat: D
|
|
|
153
153
|
"aria-live": "polite",
|
|
154
154
|
"aria-atomic": "true",
|
|
155
155
|
"data-testid": "assistant-voice-notice-announced",
|
|
156
|
-
children: Y &&
|
|
156
|
+
children: Y && we(h) ? p(h, A) : X ? R.handsFreeOff : null
|
|
157
157
|
}),
|
|
158
158
|
x ? /* @__PURE__ */ u("p", {
|
|
159
159
|
className: "max-w-xs text-center text-xs text-status-warning",
|
|
@@ -165,11 +165,11 @@ var f = ({ session: f, turns: Te = [], onSaveTranscript: Ee, onContinueInChat: D
|
|
|
165
165
|
]
|
|
166
166
|
}),
|
|
167
167
|
(() => {
|
|
168
|
-
let e = /* @__PURE__ */ u(
|
|
168
|
+
let e = /* @__PURE__ */ u(fe, {
|
|
169
169
|
session: f,
|
|
170
170
|
compact: B,
|
|
171
171
|
connecting: Q === "connecting",
|
|
172
|
-
onPress: r(Q) ? Oe :
|
|
172
|
+
onPress: r(Q) ? Oe : v,
|
|
173
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
174
|
}), t = Z ? /* @__PURE__ */ u("button", {
|
|
175
175
|
type: "button",
|
|
@@ -182,26 +182,26 @@ var f = ({ session: f, turns: Te = [], onSaveTranscript: Ee, onContinueInChat: D
|
|
|
182
182
|
}) : null, n = t ? /* @__PURE__ */ d("div", {
|
|
183
183
|
className: "flex flex-col items-center gap-2",
|
|
184
184
|
children: [e, t]
|
|
185
|
-
}) : e,
|
|
186
|
-
return B ? /* @__PURE__ */ u(
|
|
185
|
+
}) : e, i = ee(Q) ? _ : We || null, a = M === null ? null : Te(M, A);
|
|
186
|
+
return B ? /* @__PURE__ */ u(pe, {
|
|
187
187
|
spokenChunks: S,
|
|
188
188
|
state: Q,
|
|
189
|
-
caption:
|
|
190
|
-
statusText:
|
|
189
|
+
caption: i,
|
|
190
|
+
statusText: Ye,
|
|
191
191
|
statusSuffix: $,
|
|
192
|
-
rotateStatus:
|
|
193
|
-
errorText:
|
|
192
|
+
rotateStatus: Xe,
|
|
193
|
+
errorText: a,
|
|
194
194
|
control: n
|
|
195
|
-
}) : /* @__PURE__ */ u(
|
|
195
|
+
}) : /* @__PURE__ */ u(me, {
|
|
196
196
|
state: Q,
|
|
197
|
-
userCaption:
|
|
197
|
+
userCaption: We || null,
|
|
198
198
|
spokenChunks: S,
|
|
199
|
-
assistantCaption:
|
|
199
|
+
assistantCaption: _,
|
|
200
200
|
expanded: z,
|
|
201
|
-
statusText:
|
|
201
|
+
statusText: Ye,
|
|
202
202
|
statusSuffix: $,
|
|
203
|
-
rotateStatus:
|
|
204
|
-
errorText:
|
|
203
|
+
rotateStatus: Xe,
|
|
204
|
+
errorText: a,
|
|
205
205
|
control: n
|
|
206
206
|
});
|
|
207
207
|
})(),
|
|
@@ -210,7 +210,7 @@ var f = ({ session: f, turns: Te = [], onSaveTranscript: Ee, onContinueInChat: D
|
|
|
210
210
|
children: /* @__PURE__ */ u("button", {
|
|
211
211
|
type: "button",
|
|
212
212
|
onClick: () => {
|
|
213
|
-
|
|
213
|
+
F("panel"), D.onOpen();
|
|
214
214
|
},
|
|
215
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"}`,
|
|
216
216
|
"data-testid": "assistant-voice-proposal-chip",
|
|
@@ -235,39 +235,39 @@ var f = ({ session: f, turns: Te = [], onSaveTranscript: Ee, onContinueInChat: D
|
|
|
235
235
|
})
|
|
236
236
|
}), O.content]
|
|
237
237
|
}) : null,
|
|
238
|
-
|
|
239
|
-
view:
|
|
240
|
-
onContinue:
|
|
241
|
-
onNotNow:
|
|
238
|
+
g ? /* @__PURE__ */ u(ve, {
|
|
239
|
+
view: L,
|
|
240
|
+
onContinue: g.accept,
|
|
241
|
+
onNotNow: g.decline
|
|
242
242
|
}) : null,
|
|
243
|
-
H ? /* @__PURE__ */ u(
|
|
244
|
-
turns:
|
|
243
|
+
H ? /* @__PURE__ */ u(he, {
|
|
244
|
+
turns: m,
|
|
245
245
|
onClose: () => U(!1),
|
|
246
246
|
onSave: Ee,
|
|
247
247
|
onContinueInChat: De,
|
|
248
|
-
renderAfterTurn:
|
|
248
|
+
renderAfterTurn: Re
|
|
249
249
|
}) : null,
|
|
250
|
-
|
|
250
|
+
ke ? /* @__PURE__ */ u("div", {
|
|
251
251
|
className: `absolute ${z ? "left-6 top-6" : B ? "left-11 top-2" : "left-3 top-3"}`,
|
|
252
|
-
children: /* @__PURE__ */ u(
|
|
252
|
+
children: /* @__PURE__ */ u(ue, {
|
|
253
253
|
onFiles: (e) => {
|
|
254
|
-
|
|
254
|
+
ke(e), F((e) => e === "mini" ? "panel" : e);
|
|
255
255
|
},
|
|
256
|
-
onPickerOpen:
|
|
256
|
+
onPickerOpen: Ue,
|
|
257
257
|
direction: "down",
|
|
258
|
-
canAddMore:
|
|
259
|
-
disabled:
|
|
258
|
+
canAddMore: Me,
|
|
259
|
+
disabled: Ne
|
|
260
260
|
})
|
|
261
261
|
}) : null,
|
|
262
|
-
(
|
|
262
|
+
(y.length > 0 || b > 0) && !B ? /* @__PURE__ */ u("div", {
|
|
263
263
|
className: `absolute ${z ? "bottom-24 left-24 right-24" : "bottom-16 left-16 right-16"}`,
|
|
264
264
|
"data-testid": "assistant-voice-attachments",
|
|
265
|
-
children: /* @__PURE__ */ u(
|
|
266
|
-
attachments:
|
|
267
|
-
onRemove:
|
|
268
|
-
onRetry:
|
|
269
|
-
preparingCount:
|
|
270
|
-
disabled:
|
|
265
|
+
children: /* @__PURE__ */ u(de, {
|
|
266
|
+
attachments: y,
|
|
267
|
+
onRemove: Ae ?? (() => void 0),
|
|
268
|
+
onRetry: je ?? (() => void 0),
|
|
269
|
+
preparingCount: b,
|
|
270
|
+
disabled: Ne
|
|
271
271
|
})
|
|
272
272
|
}) : null,
|
|
273
273
|
/* @__PURE__ */ u("button", {
|
|
@@ -281,7 +281,7 @@ var f = ({ session: f, turns: Te = [], onSaveTranscript: Ee, onContinueInChat: D
|
|
|
281
281
|
}),
|
|
282
282
|
/* @__PURE__ */ u("button", {
|
|
283
283
|
type: "button",
|
|
284
|
-
onClick: () =>
|
|
284
|
+
onClick: () => F(V),
|
|
285
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
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"),
|
|
287
287
|
"data-testid": "assistant-voice-expand",
|
|
@@ -291,6 +291,12 @@ var f = ({ session: f, turns: Te = [], onSaveTranscript: Ee, onContinueInChat: D
|
|
|
291
291
|
});
|
|
292
292
|
};
|
|
293
293
|
function p(e, t) {
|
|
294
|
+
return typeof e == "string" ? m(e, t) : e.text;
|
|
295
|
+
}
|
|
296
|
+
function we(e) {
|
|
297
|
+
return e !== null && (typeof e != "string" || e === "reply-not-heard");
|
|
298
|
+
}
|
|
299
|
+
function m(e, t) {
|
|
294
300
|
switch (e) {
|
|
295
301
|
case "network": return t("voiceSession.noticeNetwork", "Speech recognition lost its connection. Check your network — it will retry.");
|
|
296
302
|
case "not-capturing": return t("voiceSession.noticeNotCapturing", "The microphone did not start. Check that this site is allowed to use it, then try again.");
|
|
@@ -302,7 +308,7 @@ function p(e, t) {
|
|
|
302
308
|
default: return e;
|
|
303
309
|
}
|
|
304
310
|
}
|
|
305
|
-
function
|
|
311
|
+
function Te(e, t) {
|
|
306
312
|
switch (e) {
|
|
307
313
|
case "mic-denied": return t("voiceSession.micDenied", "Microphone access is blocked. Allow it in your browser settings to talk.");
|
|
308
314
|
case "no-speech-support": return t("voiceSession.noSpeechSupport", "This browser cannot listen. Try a different browser, or type instead.");
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AssistantConversationProcessing } from '../conversation';
|
|
2
|
+
/**
|
|
3
|
+
* What the voice session is handed as "the newest reply" (`useVoiceSession`'s
|
|
4
|
+
* `latestReply`).
|
|
5
|
+
*
|
|
6
|
+
* `refused` is set only when the turn ENDED IN AN ERROR: a refusal before it
|
|
7
|
+
* started ("No active subscription covers ...") or a failure. Its text is the
|
|
8
|
+
* reason, not an answer, and nothing was published to the speech feed for it, so
|
|
9
|
+
* the session must show it rather than enter `speaking` (BOFF-9207). A real
|
|
10
|
+
* answer is exactly `{ id, text }`, with no extra key, as it always was.
|
|
11
|
+
*
|
|
12
|
+
* `processing` is the entry for the message's OWN conversation; it is matched on
|
|
13
|
+
* the message id because one entry serves a whole conversation and outlives the
|
|
14
|
+
* turn, so an older turn's error must not mark a newer, answered one.
|
|
15
|
+
*/
|
|
16
|
+
export interface VoiceReply {
|
|
17
|
+
id: string;
|
|
18
|
+
text: string;
|
|
19
|
+
refused?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function toVoiceReply(message: {
|
|
22
|
+
id: string;
|
|
23
|
+
}, text: string, processing: AssistantConversationProcessing | null | undefined): VoiceReply;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/shared/assistant/voice/voiceReply.ts
|
|
2
|
+
function e(e, t, n) {
|
|
3
|
+
return n?.messageId === e.id && n.status === "error" ? {
|
|
4
|
+
id: e.id,
|
|
5
|
+
text: t,
|
|
6
|
+
refused: !0
|
|
7
|
+
} : {
|
|
8
|
+
id: e.id,
|
|
9
|
+
text: t
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { e as toVoiceReply };
|
|
@@ -118,6 +118,16 @@ export type VoiceSessionEvent =
|
|
|
118
118
|
| {
|
|
119
119
|
type: 'reply-audio-started';
|
|
120
120
|
replyId: string;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* ★ BOFF-9207: the turn ended WITHOUT an answer - the assistant refused it
|
|
124
|
+
* (a plan that does not cover it, a permission) or it failed before anything
|
|
125
|
+
* was said. There is nothing to speak, so the session must never enter
|
|
126
|
+
* `speaking` for it: only a `thinking` moves, straight back to `listening`.
|
|
127
|
+
* The reason is shown by the caller (`VoiceTurnRefusalNotice`), not spoken.
|
|
128
|
+
*/
|
|
129
|
+
| {
|
|
130
|
+
type: 'turn-refused';
|
|
121
131
|
} | {
|
|
122
132
|
type: 'speech-ended';
|
|
123
133
|
} | {
|
|
@@ -156,6 +156,13 @@ function v(t, r, i = null) {
|
|
|
156
156
|
pending: "",
|
|
157
157
|
interim: ""
|
|
158
158
|
} : t;
|
|
159
|
+
case "turn-refused": return t.phase === "thinking" ? {
|
|
160
|
+
...t,
|
|
161
|
+
...g,
|
|
162
|
+
phase: "listening",
|
|
163
|
+
pending: "",
|
|
164
|
+
interim: ""
|
|
165
|
+
} : t;
|
|
159
166
|
case "speech-ended": return t.phase === "speaking" ? {
|
|
160
167
|
...t,
|
|
161
168
|
...g,
|