@burdenoff/fe-libs 2026.1014.4 → 2026.1015.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/shared/assistant/avatar/avatarPreferences.d.ts +61 -14
- package/dist/shared/assistant/avatar/avatarPreferences.js +54 -21
- package/dist/shared/assistant/avatar/live/LiveAvatarTabView.d.ts +33 -0
- package/dist/shared/assistant/avatar/live/LiveAvatarTabView.js +278 -0
- package/dist/shared/assistant/avatar/live/avatarSessionClient.d.ts +5 -0
- package/dist/shared/assistant/avatar/live/avatarSessionClient.js +25 -6
- package/dist/shared/assistant/avatar/live/avatarVoice.d.ts +43 -0
- package/dist/shared/assistant/avatar/live/avatarVoice.js +45 -0
- package/dist/shared/assistant/avatar/live/createLiveAvatarSeam.d.ts +21 -1
- package/dist/shared/assistant/avatar/live/createLiveAvatarSeam.js +74 -24
- package/dist/shared/assistant/avatar/live/liveAvatarController.d.ts +47 -8
- package/dist/shared/assistant/avatar/live/liveAvatarController.js +244 -161
- package/dist/shared/assistant/avatar/live/liveAvatarStatus.d.ts +2 -2
- package/dist/shared/assistant/avatar/live/liveAvatarStatus.js +11 -4
- package/dist/shared/assistant/avatar/useAvatarCopy.d.ts +52 -21
- package/dist/shared/assistant/avatar/useAvatarCopy.js +93 -48
- package/dist/shared/assistant/i18nManifest.d.ts +24 -2
- package/dist/shared/assistant/i18nManifest.js +24 -2
- package/dist/shared/assistant/index.d.ts +5 -0
- package/dist/shared/assistant/speech/hostedVoiceCatalogue.d.ts +19 -0
- package/dist/shared/assistant/speech/hostedVoiceCatalogue.js +25 -18
- package/dist/shared/assistant/ui/AIAssistantPanel.js +136 -103
- package/dist/shared/assistant/ui/AssistantAvatarTabs.d.ts +10 -0
- package/dist/shared/assistant/ui/AssistantAvatarTabs.js +48 -0
- package/dist/shared/assistant/ui/AssistantChatArea.js +1260 -1199
- package/dist/shared/assistant/ui/assistantLiveAvatarSeam.d.ts +165 -5
- package/dist/shared/assistant/ui/assistantLiveAvatarSeam.js +141 -59
- package/dist/shared/assistant/ui/avatar/AssistantAvatarTab.d.ts +13 -0
- package/dist/shared/assistant/ui/avatar/AssistantAvatarTab.js +70 -0
- package/dist/shared/assistant/ui/composer/AssistantComposer.d.ts +18 -0
- package/dist/shared/assistant/ui/composer/AssistantComposer.js +121 -102
- package/dist/shared/assistant/ui/composer/AssistantHandsFreeInterruptToggle.d.ts +9 -0
- package/dist/shared/assistant/ui/composer/AssistantHandsFreeInterruptToggle.js +28 -14
- package/dist/shared/assistant/ui/panelOverlays.d.ts +24 -3
- package/dist/shared/assistant/ui/panelOverlays.js +11 -10
- package/dist/shared/assistant/ui/useVoiceSession.js +234 -233
- package/dist/shared/assistant/ui/voice/AssistantVoiceSession.d.ts +6 -0
- package/dist/shared/assistant/ui/voice/AssistantVoiceSession.js +161 -149
- package/dist/shared/assistant/ui/voice/VoiceMiniController.js +7 -7
- package/dist/shared/assistant/ui/voice/VoiceStage.js +7 -7
- package/dist/shared/assistant/ui/voice/voiceStartGate.d.ts +57 -0
- package/dist/shared/assistant/ui/voice/voiceStartGate.js +9 -0
- package/dist/shared/assistant/voice/handsfree/handsFreeSetting.d.ts +7 -1
- package/dist/shared/assistant/voice/handsfree/handsFreeSetting.js +2 -2
- package/dist/shared-assistant.js +259 -254
- package/package.json +1 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { hostedLocaleFor as e, hostedVoiceName as t, isHostedSpeaker as n } from "../../speech/hostedVoiceCatalogue.js";
|
|
2
|
+
import { resolveAvatarVoiceMode as r, resolveStoredAvatarId as i } from "../avatarPreferences.js";
|
|
3
|
+
//#region src/shared/assistant/avatar/live/avatarVoice.ts
|
|
4
|
+
function a(e) {
|
|
5
|
+
let t = /* @__PURE__ */ new Map();
|
|
6
|
+
if (typeof e != "object" || !e) return t;
|
|
7
|
+
for (let [r, a] of Object.entries(e)) i(r) === r && n(a) && t.set(r, a);
|
|
8
|
+
return t;
|
|
9
|
+
}
|
|
10
|
+
function o(e, t) {
|
|
11
|
+
switch (r(e, t.hostDefault)) {
|
|
12
|
+
case "mine": return null;
|
|
13
|
+
case "speaker": return e.avatarSpeaker;
|
|
14
|
+
case "paired": return t.avatarId === null ? null : t.avatarVoices.get(t.avatarId) ?? null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function s(n, r, i) {
|
|
18
|
+
let a = e(r);
|
|
19
|
+
if (a === null) return null;
|
|
20
|
+
let o = t(a, n);
|
|
21
|
+
return i.find((e) => e.name === o && e.source === "hosted") ?? null;
|
|
22
|
+
}
|
|
23
|
+
function c(e, t, n) {
|
|
24
|
+
let r = !1, i = null, a = (e) => {
|
|
25
|
+
r || (r = !0, i?.(), i = null, e && t());
|
|
26
|
+
};
|
|
27
|
+
i = n?.(() => a(!0)) ?? null;
|
|
28
|
+
let o = () => a(!0);
|
|
29
|
+
return {
|
|
30
|
+
write: (t, n, r) => {
|
|
31
|
+
let i = e.write(t, n, r);
|
|
32
|
+
return i.then(void 0, o), i;
|
|
33
|
+
},
|
|
34
|
+
drain: () => {
|
|
35
|
+
let t = e.drain();
|
|
36
|
+
return t.then(() => a(!1), o), t;
|
|
37
|
+
},
|
|
38
|
+
retract: (t) => e.retract(t),
|
|
39
|
+
cancel: () => {
|
|
40
|
+
a(!1), e.cancel();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { o as avatarSpeakerFor, s as hostedAvatarVoice, a as liveAvatarVoicesFrom, c as restoreVoiceWhenOutputLeaves };
|
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
import { ComponentType } from 'react';
|
|
2
|
+
import { HostedSpeaker } from '../../speech/hostedVoiceCatalogue';
|
|
2
3
|
import { SpeechEngine } from '../../speech/types';
|
|
3
|
-
import { AssistantLiveAvatarSeam, LiveAvatarEnabledStore, LiveAvatarStageProps } from '../../ui/assistantLiveAvatarSeam';
|
|
4
|
+
import { AssistantLiveAvatarSeam, LiveAvatarEnabledStore, LiveAvatarStageProps, LiveAvatarTabSeam } from '../../ui/assistantLiveAvatarSeam';
|
|
5
|
+
import { AvatarVoiceDefault } from '../avatarPreferences';
|
|
4
6
|
import { AvatarRoom, AvatarScheduler } from './avatarPorts';
|
|
5
7
|
import { AvatarSessionClient } from './avatarSessionClient';
|
|
8
|
+
import { LiveAvatarVoiceStore } from './LiveAvatarTabView';
|
|
6
9
|
import { LiveAvatarController, LiveAvatarControllerDeps, LiveAvatarLogEvent, LiveAvatarPreferences } from './liveAvatarController';
|
|
7
10
|
export interface LiveAvatarSeamOptions {
|
|
8
11
|
/** Display names for the avatar ids the server offers (it sends ids only); without one the chooser numbers them. */
|
|
9
12
|
readonly avatarNames?: Readonly<Record<string, string>>;
|
|
13
|
+
/**
|
|
14
|
+
* The speaker each avatar id speaks with as "Default for this avatar" (BOFF-9341): host config, never inferred. An id without one has
|
|
15
|
+
* no Default, and a value that is not a hosted speaker is ignored.
|
|
16
|
+
*/
|
|
17
|
+
readonly avatarVoices?: Readonly<Record<string, HostedSpeaker>>;
|
|
18
|
+
/**
|
|
19
|
+
* The avatar's voice for a person who has not chosen one: `mine` (the default: their current voice is kept) or `paired` (each avatar
|
|
20
|
+
* speaks with its `avatarVoices` speaker, and follows the avatar when it is switched; one with none keeps the assistant's voice). A
|
|
21
|
+
* person's stored choice always wins.
|
|
22
|
+
*/
|
|
23
|
+
readonly defaultAvatarVoiceMode?: AvatarVoiceDefault;
|
|
10
24
|
readonly onLog?: (event: LiveAvatarLogEvent) => void;
|
|
11
25
|
/** Overrides, for tests and for hosts with their own plumbing. Each defaults to the assistant's own. */
|
|
12
26
|
readonly client?: () => AvatarSessionClient | null;
|
|
13
27
|
readonly openRoom?: () => Promise<AvatarRoom>;
|
|
14
28
|
readonly preferences?: LiveAvatarPreferences;
|
|
29
|
+
/**
|
|
30
|
+
* The person's avatar voice choice: read per reply, and written by the Avatar tab's voice select (never the assistant's own voice).
|
|
31
|
+
* Defaults to the assistant's avatar preferences.
|
|
32
|
+
*/
|
|
33
|
+
readonly avatarVoiceStore?: LiveAvatarVoiceStore;
|
|
15
34
|
readonly scheduler?: AvatarScheduler;
|
|
16
35
|
readonly browserSupported?: () => boolean;
|
|
17
36
|
readonly controller?: Partial<Pick<LiveAvatarControllerDeps, 'createAudioElement' | 'releaseAudioElement' | 'startMeter' | 'decode' | 'now' | 'newIdempotencyKey' | 'subscribeWorkspaceChange' | 'subscribePageHide' | 'idleMs' | 'endBeforeDeadlineMs' | 'connectTimeoutMs' | 'trackerConfig' | 'maxDrainMs' | 'stallMs' | 'readinessProbe'>>;
|
|
@@ -21,6 +40,7 @@ export interface LiveAvatarSeamHandle extends AssistantLiveAvatarSeam {
|
|
|
21
40
|
readonly decorateSpeechEngine: (base: SpeechEngine) => SpeechEngine;
|
|
22
41
|
readonly Stage: ComponentType<LiveAvatarStageProps>;
|
|
23
42
|
readonly enabled: LiveAvatarEnabledStore;
|
|
43
|
+
readonly tab: LiveAvatarTabSeam;
|
|
24
44
|
readonly controller: LiveAvatarController;
|
|
25
45
|
/** Ends any session and stops listening. The handle is unusable afterwards. */
|
|
26
46
|
dispose(): void;
|
|
@@ -8,9 +8,11 @@ import { avatarApiBaseFromTtsEndpoint as l, avatarIdempotencyKey as u, createAva
|
|
|
8
8
|
import { createLiveAvatarController as f } from "./liveAvatarController.js";
|
|
9
9
|
import { openLiveKitAvatarRoom as p } from "./liveKitAvatarRoom.js";
|
|
10
10
|
import { LiveAvatarStageView as m } from "./LiveAvatarStage.js";
|
|
11
|
-
import {
|
|
11
|
+
import { avatarSpeakerFor as h, hostedAvatarVoice as g, liveAvatarVoicesFrom as _, restoreVoiceWhenOutputLeaves as v } from "./avatarVoice.js";
|
|
12
|
+
import { LiveAvatarTabView as y } from "./LiveAvatarTabView.js";
|
|
13
|
+
import { jsx as b } from "react/jsx-runtime";
|
|
12
14
|
//#region src/shared/assistant/avatar/live/createLiveAvatarSeam.tsx
|
|
13
|
-
function
|
|
15
|
+
function x(e = globalThis) {
|
|
14
16
|
let t = e;
|
|
15
17
|
if (typeof t.RTCPeerConnection != "function" || typeof t.OfflineAudioContext != "function" || typeof t.AudioContext != "function" || typeof t.MediaStream != "function") return !1;
|
|
16
18
|
let n = t.navigator, r = n?.userAgentData;
|
|
@@ -21,14 +23,14 @@ function g(e = globalThis) {
|
|
|
21
23
|
let i = n?.userAgent ?? "";
|
|
22
24
|
return /\b(?:Chrome|Chromium)\//.test(i) && !/\b(?:Edg|OPR|SamsungBrowser)\/|Mobile|Android|iPhone|iPad/.test(i);
|
|
23
25
|
}
|
|
24
|
-
function
|
|
26
|
+
function S() {
|
|
25
27
|
let e = i().getHeaders?.() ?? {};
|
|
26
28
|
for (let [t, n] of Object.entries(e)) if (t.toLowerCase() === "authorization" && typeof n == "string" && n.trim() !== "") return n.trim();
|
|
27
29
|
let n = t();
|
|
28
30
|
return n ? `Bearer ${n}` : null;
|
|
29
31
|
}
|
|
30
|
-
async function
|
|
31
|
-
let i = n(), a =
|
|
32
|
+
async function C(t) {
|
|
33
|
+
let i = n(), a = S(), o = a?.replace(/^Bearer\s+/i, "") ?? null;
|
|
32
34
|
if (!i || !o) return null;
|
|
33
35
|
let s = t.refused?.replace(/^Bearer\s+/i, ""), c = await e({
|
|
34
36
|
url: r("workspace"),
|
|
@@ -37,13 +39,13 @@ async function v(t) {
|
|
|
37
39
|
currentQuery: "liveAvatarSession",
|
|
38
40
|
...t.fresh ? { issuanceGuard: {
|
|
39
41
|
identityKey: "liveAvatarSession",
|
|
40
|
-
isCurrent: () => n() === i &&
|
|
42
|
+
isCurrent: () => n() === i && S() === a,
|
|
41
43
|
accepts: (e, t) => e !== s && t === i
|
|
42
44
|
} } : {}
|
|
43
45
|
});
|
|
44
46
|
return c ? `Bearer ${c}` : null;
|
|
45
47
|
}
|
|
46
|
-
function
|
|
48
|
+
function w() {
|
|
47
49
|
let e = null;
|
|
48
50
|
return () => {
|
|
49
51
|
let t = typeof location > "u" ? void 0 : location.href, n = l(i().endpoint, t);
|
|
@@ -51,39 +53,44 @@ function y() {
|
|
|
51
53
|
base: n,
|
|
52
54
|
client: d({
|
|
53
55
|
baseUrl: n,
|
|
54
|
-
getAuthorization:
|
|
55
|
-
getWorkspaceAuthorization:
|
|
56
|
+
getAuthorization: S,
|
|
57
|
+
getWorkspaceAuthorization: C
|
|
56
58
|
})
|
|
57
59
|
}), e.client);
|
|
58
60
|
};
|
|
59
61
|
}
|
|
60
|
-
var
|
|
62
|
+
var T = {
|
|
61
63
|
getState: () => a.getState(),
|
|
62
64
|
setAvatarEnabled: (e) => a.getState().setAvatarEnabled(e),
|
|
63
65
|
setAvatarId: (e) => a.getState().setAvatarId(e),
|
|
64
66
|
subscribe: (e) => a.subscribe(() => e())
|
|
67
|
+
}, E = {
|
|
68
|
+
getState: () => a.getState(),
|
|
69
|
+
subscribe: (e) => a.subscribe(() => e()),
|
|
70
|
+
setVoiceMode: (e) => a.getState().setVoiceMode(e),
|
|
71
|
+
setAvatarSpeaker: (e) => a.getState().setAvatarSpeaker(e)
|
|
65
72
|
};
|
|
66
|
-
function
|
|
73
|
+
function D() {
|
|
67
74
|
if (typeof document > "u") return null;
|
|
68
75
|
let e = document.createElement("audio");
|
|
69
76
|
return e.autoplay = !0, e.hidden = !0, e.setAttribute("data-testid", "assistant-live-avatar-audio"), document.body.appendChild(e), e;
|
|
70
77
|
}
|
|
71
|
-
function
|
|
78
|
+
function O(e) {
|
|
72
79
|
if (!(typeof window > "u")) return (t) => (window.addEventListener(e, t), () => window.removeEventListener(e, t));
|
|
73
80
|
}
|
|
74
|
-
function
|
|
75
|
-
let t = e.preferences ??
|
|
76
|
-
client: e.client ??
|
|
81
|
+
function k(e = {}) {
|
|
82
|
+
let t = e.preferences ?? T, n = e.controller ?? {}, r = n.subscribeWorkspaceChange ?? O("burdenoff:workspace-changed"), i = n.subscribePageHide ?? O("pagehide"), a = f({
|
|
83
|
+
client: e.client ?? w(),
|
|
77
84
|
openRoom: e.openRoom ?? p,
|
|
78
85
|
scheduler: e.scheduler ?? o,
|
|
79
86
|
preferences: t,
|
|
80
87
|
decode: n.decode ?? ((e, t) => c(e, { signal: t })),
|
|
81
|
-
createAudioElement: n.createAudioElement ??
|
|
88
|
+
createAudioElement: n.createAudioElement ?? D,
|
|
82
89
|
releaseAudioElement: n.releaseAudioElement ?? ((e) => {
|
|
83
90
|
e.srcObject = null, e.remove();
|
|
84
91
|
}),
|
|
85
92
|
startMeter: n.startMeter ?? ((e, t) => typeof MediaStream > "u" ? null : s(new MediaStream([e]), t)),
|
|
86
|
-
browserSupported: e.browserSupported ?? (() =>
|
|
93
|
+
browserSupported: e.browserSupported ?? (() => x()),
|
|
87
94
|
now: n.now ?? (() => Date.now()),
|
|
88
95
|
newIdempotencyKey: n.newIdempotencyKey ?? (() => u()),
|
|
89
96
|
...r ? { subscribeWorkspaceChange: r } : {},
|
|
@@ -96,20 +103,47 @@ function C(e = {}) {
|
|
|
96
103
|
...n.stallMs === void 0 ? {} : { stallMs: n.stallMs },
|
|
97
104
|
...n.readinessProbe === void 0 ? {} : { readinessProbe: n.readinessProbe },
|
|
98
105
|
...e.onLog ? { onLog: e.onLog } : {}
|
|
99
|
-
})
|
|
106
|
+
}), l = _(e.avatarVoices), d = e.avatarVoiceStore ?? E, S = () => d.getState(), C = (n, r) => {
|
|
107
|
+
let i = h(S(), {
|
|
108
|
+
avatarVoices: l,
|
|
109
|
+
avatarId: t.getState().avatarId,
|
|
110
|
+
...e.defaultAvatarVoiceMode ? { hostDefault: e.defaultAvatarVoiceMode } : {}
|
|
111
|
+
});
|
|
112
|
+
return i === null ? null : g(i, n, r.listVoices());
|
|
113
|
+
}, k = (e, t) => {
|
|
114
|
+
let n = {
|
|
115
|
+
...e,
|
|
116
|
+
voice: t
|
|
117
|
+
};
|
|
118
|
+
return n.audioOutput = v(a.createOutput(), () => {
|
|
119
|
+
n.voice = e.voice;
|
|
120
|
+
}, (e) => a.subscribe(() => {
|
|
121
|
+
a.getSnapshot().phase !== "ready" && e();
|
|
122
|
+
})), n;
|
|
123
|
+
};
|
|
100
124
|
return {
|
|
101
125
|
decorateSpeechEngine: (e) => ({
|
|
102
126
|
id: e.id,
|
|
103
127
|
isSupported: () => e.isSupported(),
|
|
104
128
|
listVoices: () => e.listVoices(),
|
|
105
129
|
subscribeVoices: (t) => e.subscribeVoices(t),
|
|
106
|
-
speak: (t, n, r) =>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
130
|
+
speak: (t, n, r) => {
|
|
131
|
+
let i = C(n.lang, e), o = i === null ? n : {
|
|
132
|
+
...n,
|
|
133
|
+
voice: i
|
|
134
|
+
};
|
|
135
|
+
if (a.route(o, e) !== "avatar") {
|
|
136
|
+
e.speak(t, n, r);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
e.speak(t, i === null ? {
|
|
140
|
+
...n,
|
|
141
|
+
audioOutput: a.createOutput()
|
|
142
|
+
} : k(n, i), r);
|
|
143
|
+
},
|
|
110
144
|
cancel: () => e.cancel()
|
|
111
145
|
}),
|
|
112
|
-
Stage: ({ expanded: t, fallback: n }) => /* @__PURE__ */
|
|
146
|
+
Stage: ({ expanded: t, fallback: n }) => /* @__PURE__ */ b(m, {
|
|
113
147
|
controller: a,
|
|
114
148
|
expanded: t,
|
|
115
149
|
fallback: n,
|
|
@@ -121,9 +155,25 @@ function C(e = {}) {
|
|
|
121
155
|
switchOff: () => a.switchOff(),
|
|
122
156
|
voiceLive: (e) => a.setVoiceLive(e)
|
|
123
157
|
},
|
|
158
|
+
tab: {
|
|
159
|
+
offered: {
|
|
160
|
+
subscribe: (e) => a.subscribe(e),
|
|
161
|
+
getSnapshot: () => a.getSnapshot().tabOffered
|
|
162
|
+
},
|
|
163
|
+
setOpen: (e) => a.setTabOpen(e),
|
|
164
|
+
heldByVoice: () => a.getSnapshot().heldByVoice,
|
|
165
|
+
View: (t) => /* @__PURE__ */ b(y, {
|
|
166
|
+
...t,
|
|
167
|
+
controller: a,
|
|
168
|
+
avatarVoices: l,
|
|
169
|
+
voiceStore: d,
|
|
170
|
+
...e.avatarNames ? { avatarNames: e.avatarNames } : {},
|
|
171
|
+
...e.defaultAvatarVoiceMode ? { defaultAvatarVoiceMode: e.defaultAvatarVoiceMode } : {}
|
|
172
|
+
})
|
|
173
|
+
},
|
|
124
174
|
controller: a,
|
|
125
175
|
dispose: () => a.dispose()
|
|
126
176
|
};
|
|
127
177
|
}
|
|
128
178
|
//#endregion
|
|
129
|
-
export {
|
|
179
|
+
export { k as createLiveAvatarSeam, x as isLiveAvatarBrowserSupported, S as liveAvatarAuthorization, C as liveAvatarWorkspaceAuthorization };
|
|
@@ -30,11 +30,27 @@ export type LiveAvatarPhase =
|
|
|
30
30
|
/** Ending the session. */
|
|
31
31
|
| 'ending';
|
|
32
32
|
export type LiveAvatarNotice = 'interrupted' | 'voice-only' | 'time-limit';
|
|
33
|
+
/** A Turn off this soon after the tab's start click is the second click of a double click, not a choice (BOFF-9341, critic #2). */
|
|
34
|
+
export declare const LIVE_AVATAR_TAB_OFF_GUARD_MS = 1000;
|
|
33
35
|
export interface LiveAvatarSnapshot {
|
|
34
|
-
/** A voice conversation is live: the only time the avatar is offered (P2). */
|
|
36
|
+
/** A voice conversation is live: outside the Avatar tab, the only time the avatar is offered (P2). */
|
|
35
37
|
readonly voiceLive: boolean;
|
|
36
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* The avatar may be asked for now: the browser can show it, it was not refused on this page, and a voice conversation is live or the
|
|
40
|
+
* Avatar tab is open.
|
|
41
|
+
*/
|
|
37
42
|
readonly offered: boolean;
|
|
43
|
+
/** The Avatar tab is open (BOFF-9341): while it is, the tab owns the avatar. */
|
|
44
|
+
readonly tabOpen: boolean;
|
|
45
|
+
/** The Avatar tab may be offered: the browser can show the avatar, and it was not refused on this page. */
|
|
46
|
+
readonly tabOffered: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* After a "not now" refusal that said when (the server's Retry-After): the wall-clock ms a start may be tried again. Null otherwise.
|
|
49
|
+
* It goes with `problem`: cleared together, and both clear by themselves when the time comes.
|
|
50
|
+
*/
|
|
51
|
+
readonly retryAt: number | null;
|
|
52
|
+
/** The avatar was turned on outside the tab during the voice conversation that is still live: closing the tab hands it back to Voice. */
|
|
53
|
+
readonly heldByVoice: boolean;
|
|
38
54
|
readonly phase: LiveAvatarPhase;
|
|
39
55
|
/** The avatars the server offers, from the last availability answer. */
|
|
40
56
|
readonly avatars: readonly string[];
|
|
@@ -104,26 +120,49 @@ export interface LiveAvatarControllerDeps {
|
|
|
104
120
|
readonly readinessProbe?: AvatarReadinessProbeOptions | false;
|
|
105
121
|
readonly onLog?: (event: LiveAvatarLogEvent) => void;
|
|
106
122
|
}
|
|
123
|
+
/** How a `requestOn` ends once the server offers the avatar. */
|
|
124
|
+
export interface LiveAvatarRequestOptions {
|
|
125
|
+
/**
|
|
126
|
+
* Omitted: today's rule (the voice stage's toggle): the chooser, or a start at once when this page already confirmed this disclosure
|
|
127
|
+
* and there is nothing to choose. `false`: the chooser only, never a start (the tab opening, "Try again", a workspace switch).
|
|
128
|
+
* `true`: the person's Turn on, with the disclosure on screen: start with `avatarId` (else the one selected) once the answer comes,
|
|
129
|
+
* only if it is still offered and the disclosure version is the one shown at the click; otherwise the chooser.
|
|
130
|
+
*/
|
|
131
|
+
readonly start?: boolean;
|
|
132
|
+
readonly avatarId?: string;
|
|
133
|
+
}
|
|
107
134
|
export interface LiveAvatarController {
|
|
108
135
|
subscribe(listener: () => void): () => void;
|
|
109
136
|
getSnapshot(): LiveAvatarSnapshot;
|
|
110
|
-
/** From the seam (P2): a voice conversation is live, or over. */
|
|
137
|
+
/** From the seam (P2): a voice conversation is live, or over. While the Avatar tab is open, its end ends nothing. */
|
|
111
138
|
setVoiceLive(live: boolean): void;
|
|
112
|
-
/** The person asked for the avatar: ask the server whether it is offered, then show the chooser and the disclosure. */
|
|
113
|
-
requestOn(): void;
|
|
139
|
+
/** The person asked for the avatar: ask the server whether it is offered, then show the chooser and the disclosure (`options`). */
|
|
140
|
+
requestOn(options?: LiveAvatarRequestOptions): void;
|
|
114
141
|
/** The person confirmed the disclosure: start a session with the chosen avatar. */
|
|
115
142
|
confirm(): void;
|
|
143
|
+
/**
|
|
144
|
+
* The Avatar tab opened or closed (BOFF-9341). Open: the avatar may be asked for with no voice conversation, and an avatar already on
|
|
145
|
+
* is adopted; a question already in flight is asked again, choose only. Close: any question, choice or session ends as the person's
|
|
146
|
+
* OFF (`user`), except an avatar `heldByVoice`, which stays on for Voice.
|
|
147
|
+
*/
|
|
148
|
+
setTabOpen(open: boolean): void;
|
|
116
149
|
/** The person declined the disclosure. */
|
|
117
150
|
cancelChoice(): void;
|
|
118
151
|
selectAvatar(avatarId: string): void;
|
|
119
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* Avatar OFF: the toggle, the compact view's stop, the tab's Turn off, the end of the voice conversation (P2). Idempotent. In the tab,
|
|
154
|
+
* an OFF within `LIVE_AVATAR_TAB_OFF_GUARD_MS` of the start click, while it is still starting, is ignored (a double click).
|
|
155
|
+
*/
|
|
120
156
|
switchOff(): void;
|
|
121
157
|
/** A user gesture: let the avatar's sound play (autoplay) and wake the level meter. */
|
|
122
158
|
resumeAudio(): void;
|
|
123
159
|
/** The stage that shows the avatar mounted (or unmounted): replies go to the avatar only while it is shown (D4). */
|
|
124
160
|
setStageMounted(mounted: boolean): void;
|
|
125
|
-
/**
|
|
126
|
-
|
|
161
|
+
/**
|
|
162
|
+
* A view's video element (a React 19 ref callback): the avatar's video shows in the latest one mounted. The returned cleanup releases
|
|
163
|
+
* THAT element only, so a view going never blanks another (the tab adopting the voice stage's avatar). `null`: release the current one.
|
|
164
|
+
*/
|
|
165
|
+
attachVideo(element: HTMLVideoElement | null): (() => void) | undefined;
|
|
127
166
|
/** Where one reply goes: the avatar, or the speakers (untouched). */
|
|
128
167
|
route(options: SpeechSpeakOptions, base: SpeechEngine): 'avatar' | 'speakers';
|
|
129
168
|
/** A fresh audio output for one reply routed to the avatar. */
|