@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ComponentType, FC, ReactNode } from 'react';
|
|
1
|
+
import { ComponentType, FC, ReactNode, RefObject } from 'react';
|
|
2
2
|
import { SpeechEngine } from '../speech/types';
|
|
3
3
|
import { VoiceView } from './voice/AssistantVoiceSession';
|
|
4
4
|
export interface LiveAvatarStageProps {
|
|
@@ -15,10 +15,115 @@ export interface AssistantLiveAvatarSeam {
|
|
|
15
15
|
/**
|
|
16
16
|
* The avatar's ON switch. Optional. It is SHOWN only when `Stage` is given,
|
|
17
17
|
* since without a stage there is nothing to show; told when a voice
|
|
18
|
-
* conversation is live, and switched OFF at the end of every one, either way
|
|
18
|
+
* conversation is live, and switched OFF at the end of every one, either way
|
|
19
|
+
* (except while the Avatar tab is open, which owns its avatar).
|
|
19
20
|
*/
|
|
20
21
|
enabled?: LiveAvatarEnabledStore;
|
|
22
|
+
/**
|
|
23
|
+
* The Avatar tab (BOFF-9341). Optional; without it there is no tab, and the
|
|
24
|
+
* panel is exactly what it was.
|
|
25
|
+
*
|
|
26
|
+
* ★★ HOST CONTRACT: hand the seam over only once the server's availability
|
|
27
|
+
* has said the avatar is offered to this person here (as VibeControls does),
|
|
28
|
+
* and keep it stable. The tab asks the same free availability question when
|
|
29
|
+
* it opens, and starts a paid session only on the person's Turn on.
|
|
30
|
+
*/
|
|
31
|
+
tab?: LiveAvatarTabSeam;
|
|
32
|
+
}
|
|
33
|
+
/** Whether the Avatar tab may be offered, as a store read with `useSyncExternalStore`. STABLE. */
|
|
34
|
+
export interface LiveAvatarTabOfferedStore {
|
|
35
|
+
subscribe: (onChange: () => void) => () => void;
|
|
36
|
+
/** This browser can show the avatar (desktop Chrome first), and it was not refused on this page. */
|
|
37
|
+
getSnapshot: () => boolean;
|
|
38
|
+
}
|
|
39
|
+
/** The Avatar tab's part of the seam (`createLiveAvatarSeam` builds it). STABLE, like the rest of the seam. */
|
|
40
|
+
export interface LiveAvatarTabSeam {
|
|
41
|
+
readonly offered: LiveAvatarTabOfferedStore;
|
|
42
|
+
/**
|
|
43
|
+
* Told when the Avatar tab opens and closes; idempotent. Opening starts
|
|
44
|
+
* nothing paid. Closing is the person's OFF for the tab's avatar, except one
|
|
45
|
+
* `heldByVoice`, which stays ON for the voice stage.
|
|
46
|
+
*/
|
|
47
|
+
readonly setOpen: (open: boolean) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Whether the avatar now ON was turned on outside the tab, during the voice
|
|
50
|
+
* conversation that is still live: closing the tab hands it back to Voice
|
|
51
|
+
* instead of ending it (critic #9). Read when the tab closes.
|
|
52
|
+
*/
|
|
53
|
+
readonly heldByVoice: () => boolean;
|
|
54
|
+
/**
|
|
55
|
+
* The tab's avatar area, drawn inside the panel's Avatar tab (`AssistantAvatarTab`): the frame, the chooser, the avatar's voice, the
|
|
56
|
+
* disclosure with Turn on / Turn off, its status line, and the way into the voice conversation. Mounted only while the tab is open.
|
|
57
|
+
*/
|
|
58
|
+
readonly View: ComponentType<LiveAvatarTabProps>;
|
|
59
|
+
}
|
|
60
|
+
/** The voice conversation, as the Avatar tab shows it and starts it (the composer's own conversation, `useVoiceSession`). */
|
|
61
|
+
export interface LiveAvatarTabVoice {
|
|
62
|
+
/** A voice conversation is live: its compact strip sits under the tab. */
|
|
63
|
+
readonly live: boolean;
|
|
64
|
+
/** Why one cannot start now, in the person's words (the composer's full check, `voiceStartBlocker`); null when it can. */
|
|
65
|
+
readonly blocked: string | null;
|
|
66
|
+
/** The reason lasts (no voice here, in this mode, or in this conversation): asking the avatar is impossible for now. */
|
|
67
|
+
readonly impossible: boolean;
|
|
68
|
+
/** Start it: the composer's own start (`startVoiceWithPrewarm`). Opens the microphone; never called by anything but its button. */
|
|
69
|
+
readonly start: () => void;
|
|
70
|
+
}
|
|
71
|
+
/** What the panel hands the tab's `View`. */
|
|
72
|
+
export interface LiveAvatarTabProps {
|
|
73
|
+
/**
|
|
74
|
+
* The tab is on screen and nothing covers it (History, the preview, the voice session enlarged, a transcript drawer): replies may
|
|
75
|
+
* go to the avatar only while this is true (D4, critic #4). Per reply: each reply is routed once, when it starts, so a reply already
|
|
76
|
+
* on the avatar when this turns false finishes there; the next goes to the speakers.
|
|
77
|
+
*/
|
|
78
|
+
readonly shown: boolean;
|
|
79
|
+
readonly voice: LiveAvatarTabVoice;
|
|
80
|
+
/** Hands-free interrupt was switched off for this avatar (`useLiveAvatarHandsFreeGuard`): said in the tab while no voice is live. */
|
|
81
|
+
readonly handsFreeOff: boolean;
|
|
82
|
+
/** The id the leave hint ("Leaving the Avatar tab turns the avatar off.") must carry: the Chat tab is described by it. */
|
|
83
|
+
readonly leaveHintId: string;
|
|
84
|
+
/** The "Start a voice conversation" button: focus returns to it when a voice conversation started in the tab ends. */
|
|
85
|
+
readonly startVoiceRef: RefObject<HTMLButtonElement | null>;
|
|
86
|
+
}
|
|
87
|
+
/** The ids the tab row and the two tab panels share (`AIAssistantPanel` makes them). */
|
|
88
|
+
export interface LiveAvatarTabIds {
|
|
89
|
+
readonly chatTab: string;
|
|
90
|
+
readonly avatarTab: string;
|
|
91
|
+
readonly chatPanel: string;
|
|
92
|
+
readonly avatarPanel: string;
|
|
93
|
+
readonly leaveHint: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The Avatar tab's state as the panel provides it to the views below it: the
|
|
97
|
+
* chat area, the voice session and the seam's own hooks. Closed by default, so
|
|
98
|
+
* a panel that provides nothing (every host without the tab) is today's.
|
|
99
|
+
*/
|
|
100
|
+
export interface LiveAvatarTabState {
|
|
101
|
+
/** The Avatar tab is the view on screen. */
|
|
102
|
+
readonly open: boolean;
|
|
103
|
+
/** Select Chat again: what something in the tab calls to hand the person back to the chat. */
|
|
104
|
+
readonly close: () => void;
|
|
105
|
+
/**
|
|
106
|
+
* The tab row's ids while it is shown, else null (or absent): only then is the chat region a tab panel, so a panel without the
|
|
107
|
+
* tab is today's markup, attribute for attribute.
|
|
108
|
+
*/
|
|
109
|
+
readonly ids?: LiveAvatarTabIds | null;
|
|
110
|
+
/** A full-surface overlay (History, the preview) covers the panel: the tab under it is not shown (D4). */
|
|
111
|
+
readonly covered?: boolean;
|
|
21
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* @internal Provided by `AIAssistantPanel` and read by the assistant's own
|
|
115
|
+
* views; not part of the module's public surface.
|
|
116
|
+
*/
|
|
117
|
+
export declare const LiveAvatarTabContext: import('react').Context<LiveAvatarTabState>;
|
|
118
|
+
/** @internal The Avatar tab's state (`LiveAvatarTabContext`): closed without a panel that offers it. */
|
|
119
|
+
export declare function useLiveAvatarTab(): LiveAvatarTabState;
|
|
120
|
+
/** @internal The seam's tab part, or null: what the chat area draws the open tab with. */
|
|
121
|
+
export declare function useLiveAvatarTabSeam(): LiveAvatarTabSeam | null;
|
|
122
|
+
/**
|
|
123
|
+
* @internal For `AIAssistantPanel`: whether the seam offers the Avatar tab now. False without a tab part, and then nothing is
|
|
124
|
+
* subscribed to.
|
|
125
|
+
*/
|
|
126
|
+
export declare function useLiveAvatarTabOffered(tab: LiveAvatarTabSeam | undefined): boolean;
|
|
22
127
|
/**
|
|
23
128
|
* The avatar's ON flag as a store, read with `useSyncExternalStore`: the voice
|
|
24
129
|
* session subscribes and nothing above it re-renders. It must be STABLE, and
|
|
@@ -28,7 +133,9 @@ export interface AssistantLiveAvatarSeam {
|
|
|
28
133
|
* ★★ ON is only ever the person's own click, on a control the host offers
|
|
29
134
|
* only while `voiceLive` says a voice conversation is live, and a paid session
|
|
30
135
|
* starts only then: no avatar after its conversation has ended. The panel
|
|
31
|
-
* switches the avatar OFF, never ON.
|
|
136
|
+
* switches the avatar OFF, never ON. The one other place is the Avatar tab
|
|
137
|
+
* (BOFF-9341), whose own Turn on starts the avatar with or without a voice
|
|
138
|
+
* conversation, and whose avatar ends when the tab closes.
|
|
32
139
|
*/
|
|
33
140
|
export interface LiveAvatarEnabledStore {
|
|
34
141
|
subscribe: (onChange: () => void) => () => void;
|
|
@@ -39,7 +146,9 @@ export interface LiveAvatarEnabledStore {
|
|
|
39
146
|
* Called at the end of EVERY voice conversation, even when the avatar is
|
|
40
147
|
* already OFF; again for any ON while a failed one is still on screen; and
|
|
41
148
|
* by the compact view's stop. So it must be idempotent and cheap. It touches
|
|
42
|
-
* no audio: a reply already playing is the speech engine's to finish.
|
|
149
|
+
* no audio: a reply already playing is the speech engine's to finish. None
|
|
150
|
+
* of the three end calls is made while the Avatar tab is open: the tab owns
|
|
151
|
+
* its avatar then.
|
|
43
152
|
*/
|
|
44
153
|
switchOff: () => void;
|
|
45
154
|
/**
|
|
@@ -53,7 +162,12 @@ export declare const LiveAvatarSeamProvider: FC<{
|
|
|
53
162
|
value?: AssistantLiveAvatarSeam;
|
|
54
163
|
children: ReactNode;
|
|
55
164
|
}>;
|
|
56
|
-
/**
|
|
165
|
+
/**
|
|
166
|
+
* The stage component, or null — which is what every host without an avatar
|
|
167
|
+
* gets, and what the voice stage gets while the Avatar tab is open: the tab is
|
|
168
|
+
* then the one place the avatar is shown, and the voice stage draws its
|
|
169
|
+
* waveform (BOFF-9341).
|
|
170
|
+
*/
|
|
57
171
|
export declare function useLiveAvatarStage(): ComponentType<LiveAvatarStageProps> | null;
|
|
58
172
|
/**
|
|
59
173
|
* The engine the speech provider should be handed, or `undefined` for "use your
|
|
@@ -63,6 +177,42 @@ export declare function useLiveAvatarStage(): ComponentType<LiveAvatarStageProps
|
|
|
63
177
|
* is to the store `AssistantSpeechProvider` already subscribes to.
|
|
64
178
|
*/
|
|
65
179
|
export declare function useLiveAvatarSpeechEngine(seam: AssistantLiveAvatarSeam | undefined): SpeechEngine | undefined;
|
|
180
|
+
/**
|
|
181
|
+
* @internal Exported for the assistant's own hands-free wiring (`useVoiceSession`,
|
|
182
|
+
* `AssistantHandsFreeInterruptToggle`); not part of the module's public surface.
|
|
183
|
+
*
|
|
184
|
+
* Whether the live avatar is ON: never without a seam, so false in every app that passes no
|
|
185
|
+
* `liveAvatar`. Read from the ON switch with or without a stage: what it guards is the avatar's
|
|
186
|
+
* voice, not its picture.
|
|
187
|
+
*/
|
|
188
|
+
export declare function useLiveAvatarOn(): boolean;
|
|
189
|
+
/**
|
|
190
|
+
* @internal Exported for `AssistantVoiceSession` alone; not part of the module's public surface.
|
|
191
|
+
*
|
|
192
|
+
* ★★ THE PILOT RULE (BOFF-8822), while the avatar's echo reference (P5) is not in: hands-free
|
|
193
|
+
* interrupt is OFF whenever the avatar is ON. The avatar's voice plays on the speakers, where the
|
|
194
|
+
* microphone hears it, and hands-free would take it for the person speaking over the reply: a
|
|
195
|
+
* barge-in that cuts the avatar off.
|
|
196
|
+
* - Switched ON with hands-free on: hands-free is switched OFF in the setting the person sees (and
|
|
197
|
+
* stored, so it stays off), and this returns true until the avatar is OFF again, for the voice
|
|
198
|
+
* session to say why.
|
|
199
|
+
* - While it is ON: a write that turns hands-free back on is turned back at once, its menu control
|
|
200
|
+
* is `aria-disabled` (it keeps its focus) and says why (`AssistantHandsFreeInterruptToggle`), and the voice session's own
|
|
201
|
+
* reading of the setting is OFF whatever it says (`resolveHandsFreeEnabled`).
|
|
202
|
+
* - Switched OFF: hands-free stays OFF. Nothing turns it back on behind the person's back, and the
|
|
203
|
+
* copy (`avatar.handsFreeOff`) says so and where to turn it back on (More options).
|
|
204
|
+
*
|
|
205
|
+
* ★ ONE notice per avatar, shared by every view that mounts this guard (BOFF-9341: the voice session
|
|
206
|
+
* and the Avatar tab). Whichever of them sees the ON first switches hands-free off; every one of them
|
|
207
|
+
* then says so, so a voice conversation started after the tab's Turn on still explains it. Which view
|
|
208
|
+
* SHOWS the notice is the views' call: the tab while no voice conversation is live, the session's
|
|
209
|
+
* strip otherwise, so it is said in exactly one place. It is kept while any of them is mounted and
|
|
210
|
+
* the avatar stays ON, and goes with the last of them (a microtask late, so React's development
|
|
211
|
+
* double mount keeps it, as component state was kept before).
|
|
212
|
+
*
|
|
213
|
+
* Without a seam nothing here subscribes to, reads or writes anything.
|
|
214
|
+
*/
|
|
215
|
+
export declare function useLiveAvatarHandsFreeGuard(): boolean;
|
|
66
216
|
/**
|
|
67
217
|
* The voice view to SHOW, given the one the user CHOSE; and the avatar kept to
|
|
68
218
|
* the live conversation (`ended`: over, but still on screen to say why it
|
|
@@ -85,6 +235,13 @@ export declare function useLiveAvatarSpeechEngine(seam: AssistantLiveAvatarSeam
|
|
|
85
235
|
* ★ Adjusted DURING RENDER, as `AIAssistantPanel` adjusts its preview flag: no
|
|
86
236
|
* frame shows the stale view, and the update is conditional on the state it
|
|
87
237
|
* replaces, so it cannot loop.
|
|
238
|
+
*
|
|
239
|
+
* ★★ The Avatar tab (BOFF-9341). While it is open it shows the avatar, so this
|
|
240
|
+
* holds nothing: the user's choice, and whatever changes meanwhile lets go.
|
|
241
|
+
* When it closes on an avatar Voice turned on, in this conversation
|
|
242
|
+
* (`heldByVoice`, critic #9), the avatar stays ON, and over the compact view
|
|
243
|
+
* the hold shows it again here. Any other avatar of the tab ends as the tab
|
|
244
|
+
* closes, so nothing is held for it: no frame shows a stage going away.
|
|
88
245
|
*/
|
|
89
246
|
export declare function useLiveAvatarVoiceView(chosen: VoiceView, ended: boolean): VoiceView;
|
|
90
247
|
/**
|
|
@@ -116,6 +273,9 @@ export declare function useLiveAvatarMiniIndicatorShown(): boolean;
|
|
|
116
273
|
* comes after the microphone control, so Tab meets the strip's own control
|
|
117
274
|
* first.
|
|
118
275
|
*
|
|
276
|
+
* ★ Hidden while the Avatar tab is open (BOFF-9341): the tab shows the avatar,
|
|
277
|
+
* and its own Turn off, right above the strip.
|
|
278
|
+
*
|
|
119
279
|
* ★ "Avatar on" is a state, not a label. The control is an icon named by
|
|
120
280
|
* `avatar.stop`, so no button is named for the opposite of what it does, and
|
|
121
281
|
* its name is all of its words (WCAG 2.5.3, as the session's End).
|
|
@@ -1,100 +1,182 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { useAssistantStore as e } from "../store.js";
|
|
2
|
+
import { getSpeechEngine as t, subscribeSpeechEngine as n } from "../speech/createSpeechEngine.js";
|
|
3
|
+
import { useAvatarCopy as r } from "../avatar/useAvatarCopy.js";
|
|
4
|
+
import { ASSISTANT_VOICE_SESSION_ATTR as i } from "./panelOverlays.js";
|
|
5
|
+
import { createContext as a, useContext as o, useEffect as s, useLayoutEffect as c, useMemo as l, useRef as u, useState as d, useSyncExternalStore as f } from "react";
|
|
6
|
+
import { VideoOff as p } from "lucide-react";
|
|
7
|
+
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
8
|
+
var g = a({
|
|
9
|
+
open: !1,
|
|
10
|
+
close: () => {},
|
|
11
|
+
ids: null,
|
|
12
|
+
covered: !1
|
|
11
13
|
});
|
|
12
14
|
function _() {
|
|
13
|
-
return
|
|
15
|
+
return o(g);
|
|
14
16
|
}
|
|
15
|
-
function v(
|
|
16
|
-
|
|
17
|
-
return c(() => i ? i(r) : void 0, [i, r]);
|
|
17
|
+
function v() {
|
|
18
|
+
return o(x)?.tab ?? null;
|
|
18
19
|
}
|
|
19
20
|
var y = {
|
|
21
|
+
subscribe: () => () => {},
|
|
22
|
+
getSnapshot: () => !1
|
|
23
|
+
};
|
|
24
|
+
function b(e) {
|
|
25
|
+
let t = e?.offered ?? y;
|
|
26
|
+
return f(t.subscribe, t.getSnapshot, t.getSnapshot);
|
|
27
|
+
}
|
|
28
|
+
var x = a(null), S = ({ value: e, children: t }) => /* @__PURE__ */ m(x.Provider, {
|
|
29
|
+
value: e ?? null,
|
|
30
|
+
children: t
|
|
31
|
+
});
|
|
32
|
+
function C() {
|
|
33
|
+
let e = o(x)?.Stage ?? null;
|
|
34
|
+
return _().open ? null : e;
|
|
35
|
+
}
|
|
36
|
+
function w(e) {
|
|
37
|
+
let r = f(n, t, t), i = e?.decorateSpeechEngine;
|
|
38
|
+
return l(() => i ? i(r) : void 0, [i, r]);
|
|
39
|
+
}
|
|
40
|
+
var T = {
|
|
20
41
|
subscribe: () => () => {},
|
|
21
42
|
getSnapshot: () => !1,
|
|
22
43
|
switchOff: () => {},
|
|
23
44
|
voiceLive: () => {}
|
|
24
45
|
};
|
|
25
|
-
function
|
|
46
|
+
function E(e) {
|
|
26
47
|
return e?.Stage ? e.enabled : void 0;
|
|
27
48
|
}
|
|
28
|
-
function
|
|
29
|
-
let t = e ??
|
|
30
|
-
return
|
|
49
|
+
function D(e) {
|
|
50
|
+
let t = e ?? T;
|
|
51
|
+
return f(t.subscribe, t.getSnapshot, t.getSnapshot);
|
|
52
|
+
}
|
|
53
|
+
function O() {
|
|
54
|
+
return D(o(x)?.enabled);
|
|
55
|
+
}
|
|
56
|
+
function k() {
|
|
57
|
+
let t = o(x)?.enabled, n = t ? M(t) : A, r = f(n.subscribe, n.isShown, n.isShown);
|
|
58
|
+
return s(() => {
|
|
59
|
+
if (!t) return;
|
|
60
|
+
let r = () => {
|
|
61
|
+
if (!t.getSnapshot()) {
|
|
62
|
+
n.set(!1);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
let r = e.getState();
|
|
66
|
+
r.handsFreeInterrupt && (r.setHandsFreeInterrupt(!1), n.set(!0));
|
|
67
|
+
}, i = n.hold();
|
|
68
|
+
r();
|
|
69
|
+
let a = t.subscribe(r), o = e.subscribe(r);
|
|
70
|
+
return () => {
|
|
71
|
+
a(), o(), i();
|
|
72
|
+
};
|
|
73
|
+
}, [t, n]), r;
|
|
74
|
+
}
|
|
75
|
+
var A = {
|
|
76
|
+
subscribe: () => () => {},
|
|
77
|
+
isShown: () => !1,
|
|
78
|
+
set: () => {},
|
|
79
|
+
hold: () => () => {}
|
|
80
|
+
}, j = /* @__PURE__ */ new WeakMap();
|
|
81
|
+
function M(e) {
|
|
82
|
+
let t = j.get(e);
|
|
83
|
+
if (t) return t;
|
|
84
|
+
let n = !1, r = 0, i = /* @__PURE__ */ new Set(), a = (e) => {
|
|
85
|
+
if (n !== e) {
|
|
86
|
+
n = e;
|
|
87
|
+
for (let e of [...i]) e();
|
|
88
|
+
}
|
|
89
|
+
}, o = {
|
|
90
|
+
subscribe: (e) => (i.add(e), () => {
|
|
91
|
+
i.delete(e);
|
|
92
|
+
}),
|
|
93
|
+
isShown: () => n,
|
|
94
|
+
set: a,
|
|
95
|
+
hold: () => {
|
|
96
|
+
r += 1;
|
|
97
|
+
let e = !1;
|
|
98
|
+
return () => {
|
|
99
|
+
e || (e = !0, --r, queueMicrotask(() => {
|
|
100
|
+
r === 0 && a(!1);
|
|
101
|
+
}));
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
return j.set(e, o), o;
|
|
31
106
|
}
|
|
32
|
-
function
|
|
33
|
-
let n =
|
|
34
|
-
|
|
35
|
-
let
|
|
36
|
-
on:
|
|
107
|
+
function N(e, t) {
|
|
108
|
+
let n = o(x), r = _().open;
|
|
109
|
+
P(n?.enabled, t, r);
|
|
110
|
+
let i = E(n), a = D(i), [s, c] = d(() => ({
|
|
111
|
+
on: a,
|
|
37
112
|
chosen: e,
|
|
38
|
-
|
|
113
|
+
tabOpen: r,
|
|
114
|
+
open: a && e === "mini" && !r
|
|
39
115
|
}));
|
|
40
|
-
if (!
|
|
41
|
-
let
|
|
42
|
-
return (
|
|
43
|
-
on:
|
|
116
|
+
if (!i) return e;
|
|
117
|
+
let l = s.open;
|
|
118
|
+
return (s.on !== a || s.chosen !== e || s.tabOpen !== r) && (l = r ? !1 : s.tabOpen ? a && e === "mini" && n?.tab?.heldByVoice() === !0 : s.on !== a && a && e === "mini", c({
|
|
119
|
+
on: a,
|
|
44
120
|
chosen: e,
|
|
45
|
-
|
|
46
|
-
|
|
121
|
+
tabOpen: r,
|
|
122
|
+
open: l
|
|
123
|
+
})), l && !t && !r ? "panel" : e;
|
|
47
124
|
}
|
|
48
|
-
function
|
|
49
|
-
let
|
|
50
|
-
|
|
125
|
+
function P(e, t, n) {
|
|
126
|
+
let r = u(0), i = u(n);
|
|
127
|
+
c(() => {
|
|
128
|
+
i.current = n;
|
|
129
|
+
}, [n]), s(() => {
|
|
51
130
|
if (!e) return;
|
|
52
|
-
let
|
|
53
|
-
|
|
54
|
-
}),
|
|
55
|
-
|
|
131
|
+
let n = ++r.current, a = (e) => queueMicrotask(() => {
|
|
132
|
+
r.current === n && e();
|
|
133
|
+
}), o = () => {
|
|
134
|
+
i.current || e.switchOff();
|
|
135
|
+
}, s = () => a(() => {
|
|
136
|
+
e.voiceLive(!1), o();
|
|
56
137
|
});
|
|
57
|
-
if (!t) return
|
|
58
|
-
|
|
59
|
-
let
|
|
138
|
+
if (!t) return a(() => e.voiceLive(!0)), s;
|
|
139
|
+
s();
|
|
140
|
+
let c = e.getSnapshot();
|
|
60
141
|
return e.subscribe(() => {
|
|
61
|
-
let t =
|
|
62
|
-
|
|
142
|
+
let t = c;
|
|
143
|
+
c = e.getSnapshot(), c && !t && a(o);
|
|
63
144
|
});
|
|
64
145
|
}, [e, t]);
|
|
65
146
|
}
|
|
66
|
-
function
|
|
67
|
-
|
|
147
|
+
function F() {
|
|
148
|
+
let e = D(E(o(x))), t = _().open;
|
|
149
|
+
return e && !t;
|
|
68
150
|
}
|
|
69
|
-
var
|
|
70
|
-
let e =
|
|
71
|
-
return e && t ? /* @__PURE__ */
|
|
151
|
+
var I = () => {
|
|
152
|
+
let e = E(o(x)), t = D(e), n = _().open;
|
|
153
|
+
return e && t && !n ? /* @__PURE__ */ m(z, {
|
|
72
154
|
onStop: e.switchOff,
|
|
73
155
|
isOn: e.getSnapshot
|
|
74
156
|
}) : null;
|
|
75
|
-
},
|
|
76
|
-
let
|
|
77
|
-
return
|
|
157
|
+
}, L = "[data-testid=\"assistant-voice-orb\"]", R = "[data-testid=\"assistant-voice-end\"]", z = ({ onStop: e, isOn: t }) => {
|
|
158
|
+
let n = r(), a = u(null), o = u(t);
|
|
159
|
+
return c(() => {
|
|
78
160
|
let e = a.current, t = o.current;
|
|
79
161
|
return () => {
|
|
80
162
|
if (!e?.contains(e.ownerDocument.activeElement)) return;
|
|
81
|
-
let n = e.closest(`[${
|
|
82
|
-
(
|
|
163
|
+
let n = e.closest(`[${i}]`), r = t() ? null : n?.querySelector(L);
|
|
164
|
+
(r && !r.hasAttribute("disabled") ? r : n?.querySelector(R))?.focus();
|
|
83
165
|
};
|
|
84
|
-
}, []), /* @__PURE__ */
|
|
166
|
+
}, []), /* @__PURE__ */ h("div", {
|
|
85
167
|
ref: a,
|
|
86
168
|
className: "absolute bottom-1.5 left-7 flex h-8 max-w-[calc(100%-13.875rem)] items-center gap-1 rounded-full border border-border-default bg-bg-elevated ps-2.5 pe-0.5 text-xs font-medium text-text-secondary rtl:left-auto rtl:right-7",
|
|
87
169
|
"data-testid": "assistant-voice-avatar",
|
|
88
|
-
children: [/* @__PURE__ */
|
|
170
|
+
children: [/* @__PURE__ */ m("span", {
|
|
89
171
|
className: "truncate",
|
|
90
|
-
children:
|
|
91
|
-
}), /* @__PURE__ */
|
|
172
|
+
children: n.stateOn
|
|
173
|
+
}), /* @__PURE__ */ m("button", {
|
|
92
174
|
type: "button",
|
|
93
175
|
onClick: () => e(),
|
|
94
176
|
className: "relative flex size-7 shrink-0 items-center justify-center rounded-full transition-colors after:absolute after:-inset-2 after:-ms-1.5 after:me-1.5 hover:bg-bg-raised hover:text-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong",
|
|
95
|
-
"aria-label":
|
|
177
|
+
"aria-label": n.stop,
|
|
96
178
|
"data-testid": "assistant-voice-avatar-stop",
|
|
97
|
-
children: /* @__PURE__ */ p
|
|
179
|
+
children: /* @__PURE__ */ m(p, {
|
|
98
180
|
className: "size-4",
|
|
99
181
|
"aria-hidden": "true"
|
|
100
182
|
})
|
|
@@ -102,4 +184,4 @@ var T = () => {
|
|
|
102
184
|
});
|
|
103
185
|
};
|
|
104
186
|
//#endregion
|
|
105
|
-
export {
|
|
187
|
+
export { I as LiveAvatarMiniIndicator, S as LiveAvatarSeamProvider, g as LiveAvatarTabContext, k as useLiveAvatarHandsFreeGuard, F as useLiveAvatarMiniIndicatorShown, O as useLiveAvatarOn, w as useLiveAvatarSpeechEngine, C as useLiveAvatarStage, _ as useLiveAvatarTab, b as useLiveAvatarTabOffered, v as useLiveAvatarTabSeam, N as useLiveAvatarVoiceView };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { LiveAvatarTabSeam, LiveAvatarTabVoice } from '../assistantLiveAvatarSeam';
|
|
3
|
+
export interface AssistantAvatarTabProps {
|
|
4
|
+
readonly tab: LiveAvatarTabSeam;
|
|
5
|
+
/** This panel's id, and the Avatar tab that labels it. */
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly labelledBy: string;
|
|
8
|
+
readonly leaveHintId: string;
|
|
9
|
+
/** Nothing covers the tab (D4): see `LiveAvatarTabProps.shown`. */
|
|
10
|
+
readonly shown: boolean;
|
|
11
|
+
readonly voice: LiveAvatarTabVoice;
|
|
12
|
+
}
|
|
13
|
+
export declare const AssistantAvatarTab: FC<AssistantAvatarTabProps>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ASSISTANT_VOICE_SESSION_ATTR as e } from "../panelOverlays.js";
|
|
2
|
+
import { useLiveAvatarHandsFreeGuard as t } from "../assistantLiveAvatarSeam.js";
|
|
3
|
+
import { useEffect as n, useLayoutEffect as r, useRef as i, useState as a } from "react";
|
|
4
|
+
import { jsx as o } from "react/jsx-runtime";
|
|
5
|
+
//#region src/shared/assistant/ui/avatar/AssistantAvatarTab.tsx
|
|
6
|
+
var s = 180;
|
|
7
|
+
function c(t, n) {
|
|
8
|
+
let [i, o] = a(0);
|
|
9
|
+
return r(() => {
|
|
10
|
+
if (!n) return;
|
|
11
|
+
let r = t.current?.parentElement?.querySelector(`[${e}]`);
|
|
12
|
+
if (!r) return;
|
|
13
|
+
let i = () => {
|
|
14
|
+
let e = typeof r.getBoundingClientRect == "function" ? r.getBoundingClientRect().height : 0;
|
|
15
|
+
o(Math.round(e));
|
|
16
|
+
};
|
|
17
|
+
if (i(), typeof ResizeObserver > "u") return;
|
|
18
|
+
let a = new ResizeObserver(i);
|
|
19
|
+
return a.observe(r), () => a.disconnect();
|
|
20
|
+
}, [t, n]), n ? i > 0 ? i : s : 0;
|
|
21
|
+
}
|
|
22
|
+
function l() {
|
|
23
|
+
if (typeof document > "u") return !1;
|
|
24
|
+
let e = document.activeElement;
|
|
25
|
+
return e === null || e === document.body || !e.isConnected || e.hasAttribute("data-assistant-panel");
|
|
26
|
+
}
|
|
27
|
+
var u = ({ tab: e, id: a, labelledBy: s, leaveHintId: u, shown: d, voice: f }) => {
|
|
28
|
+
let p = i(null), m = i(null), h = t(), g = c(p, f.live), _ = i(null);
|
|
29
|
+
r(() => {
|
|
30
|
+
let t = (_.current?.setup ?? 0) + 1;
|
|
31
|
+
return _.current = {
|
|
32
|
+
tab: e,
|
|
33
|
+
setup: t
|
|
34
|
+
}, e.setOpen(!0), () => {
|
|
35
|
+
queueMicrotask(() => {
|
|
36
|
+
let n = _.current;
|
|
37
|
+
n !== null && n.tab === e && n.setup !== t || e.setOpen(!1);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
}, [e]);
|
|
41
|
+
let v = i(f.live);
|
|
42
|
+
return n(() => {
|
|
43
|
+
let e = v.current;
|
|
44
|
+
v.current = f.live, !(!e || f.live) && l() && m.current?.focus();
|
|
45
|
+
}, [f.live]), /* @__PURE__ */ o("div", {
|
|
46
|
+
ref: p,
|
|
47
|
+
role: "tabpanel",
|
|
48
|
+
id: a,
|
|
49
|
+
"aria-labelledby": s,
|
|
50
|
+
className: "absolute inset-0 z-[5] flex flex-col bg-bg-surface",
|
|
51
|
+
"data-testid": "assistant-avatar-tab",
|
|
52
|
+
children: /* @__PURE__ */ o("div", {
|
|
53
|
+
className: "flex min-h-0 flex-1 flex-col overflow-y-auto px-4 pt-4",
|
|
54
|
+
style: {
|
|
55
|
+
paddingBottom: g + 16,
|
|
56
|
+
scrollPaddingBottom: g + 16
|
|
57
|
+
},
|
|
58
|
+
"data-testid": "assistant-avatar-tab-column",
|
|
59
|
+
children: /* @__PURE__ */ o(e.View, {
|
|
60
|
+
shown: d,
|
|
61
|
+
voice: f,
|
|
62
|
+
handsFreeOff: h,
|
|
63
|
+
leaveHintId: u,
|
|
64
|
+
startVoiceRef: m
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
//#endregion
|
|
70
|
+
export { u as AssistantAvatarTab };
|
|
@@ -55,6 +55,24 @@ interface Props {
|
|
|
55
55
|
* sandbox while the person is still typing (BOFF-7801).
|
|
56
56
|
*/
|
|
57
57
|
onDraftStart?: () => void;
|
|
58
|
+
/**
|
|
59
|
+
* ★ BOFF-9341: something covers the composer (the Avatar tab). Dictation
|
|
60
|
+
* stops, keeping every word it heard in the draft, and stays stopped: a
|
|
61
|
+
* microphone recording behind a surface the person cannot see is a privacy
|
|
62
|
+
* problem, and the tab's own voice conversation needs the microphone.
|
|
63
|
+
*/
|
|
64
|
+
suspendDictation?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Told what holds the voice conversation back here (BOFF-9341): a Send
|
|
67
|
+
* saving its attachments, and dictation holding the microphone. The rest of
|
|
68
|
+
* the composer's check comes from the props it is given.
|
|
69
|
+
*/
|
|
70
|
+
onVoiceStateChange?: (state: AssistantComposerVoiceState) => void;
|
|
71
|
+
}
|
|
72
|
+
/** What in the composer holds a voice conversation back (`onVoiceStateChange`). */
|
|
73
|
+
export interface AssistantComposerVoiceState {
|
|
74
|
+
readonly committing: boolean;
|
|
75
|
+
readonly dictating: boolean;
|
|
58
76
|
}
|
|
59
77
|
/**
|
|
60
78
|
* AssistantComposer — the v2 input surface (per the ai-assistant-v2 mockups).
|