@burdenoff/fe-libs 2026.1013.8 → 2026.1013.9
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/ui/AIAssistantPanel.js +16 -16
- package/dist/shared/assistant/ui/assistantLiveAvatarSeam.d.ts +117 -0
- package/dist/shared/assistant/ui/assistantLiveAvatarSeam.js +97 -9
- package/dist/shared/assistant/ui/voice/AssistantVoiceSession.js +135 -134
- package/dist/shared/assistant/ui/voice/VoiceMiniController.js +54 -49
- package/dist/shared/assistant/ui/voice/VoiceStage.js +39 -35
- package/dist/shared-assistant.js +115 -115
- package/package.json +1 -1
|
@@ -11,14 +11,14 @@ import { hasAssistantPreviewSurface as l } from "../preview/types.js";
|
|
|
11
11
|
import { ASSISTANT_OVERLAY_ATTR as u, ASSISTANT_PANEL_ATTR as d, activePanelOverlay as f, escapeDismisses as p, nextPanelOverlays as ee } from "./panelOverlays.js";
|
|
12
12
|
import { panelOwnedPreviewBridge as te, useHostedAssistantPreviewBridge as ne } from "../preview/bridgeHostContext.js";
|
|
13
13
|
import { AssistantPreviewOverlay as re } from "../preview/AssistantPreviewOverlay.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
14
|
+
import { LiveAvatarSeamProvider as ie, useLiveAvatarSpeechEngine as ae } from "./assistantLiveAvatarSeam.js";
|
|
15
|
+
import { AssistantHeader as m } from "./AssistantHeader.js";
|
|
16
|
+
import { AssistantConversationList as h } from "./AssistantConversationList.js";
|
|
17
|
+
import { AssistantConversationDeleteDialogProvider as g } from "./conversationDeleteDialog.js";
|
|
18
|
+
import { AssistantControlConfirmProvider as _ } from "./controlConfirmDialog.js";
|
|
19
|
+
import { AssistantLeaveConfirmNavigateProvider as v } from "./leaveConfirm.js";
|
|
20
|
+
import { useAssistantServerHistory as y } from "../history/useAssistantServerHistory.js";
|
|
21
|
+
import { useFilesServiceWarmup as b } from "../../../storage/warmFilesService.js";
|
|
22
22
|
import { useAssistantHeartbeat as x } from "../useAssistantHeartbeat.js";
|
|
23
23
|
import { useAssistantKeyboardShortcuts as S } from "../useAssistantKeyboardShortcuts.js";
|
|
24
24
|
import { AssistantChatArea as C } from "./AssistantChatArea.js";
|
|
@@ -26,8 +26,8 @@ import { useMemo as w, useRef as T, useState as E } from "react";
|
|
|
26
26
|
import { jsx as D, jsxs as O } from "react/jsx-runtime";
|
|
27
27
|
//#region src/shared/assistant/ui/AIAssistantPanel.tsx
|
|
28
28
|
var k = ({ onClose: k, visible: A, docked: j, onToggleDock: M, writeAccess: N, onNavigate: oe, isShellLessTarget: P, connectSourceHref: F, onConnectSource: I, preview: L, liveAvatar: R, serverHistory: z = !1 }) => {
|
|
29
|
-
let B = r(), V =
|
|
30
|
-
|
|
29
|
+
let B = r(), V = ae(R);
|
|
30
|
+
y(z);
|
|
31
31
|
let [H, U] = E(!1), W = l(L);
|
|
32
32
|
H && !W && U(!1);
|
|
33
33
|
let G = H && W;
|
|
@@ -54,7 +54,7 @@ var k = ({ onClose: k, visible: A, docked: j, onToggleDock: M, writeAccess: N, o
|
|
|
54
54
|
Y
|
|
55
55
|
]));
|
|
56
56
|
let $ = q.find((e) => e.id === se);
|
|
57
|
-
x($?.backendSessionId ?? null, $?.mode ?? null, A ?? !0),
|
|
57
|
+
x($?.backendSessionId ?? null, $?.mode ?? null, A ?? !0), b(A ?? !0);
|
|
58
58
|
let le = n(A !== !1);
|
|
59
59
|
return S({
|
|
60
60
|
panelRef: K,
|
|
@@ -71,17 +71,17 @@ var k = ({ onClose: k, visible: A, docked: j, onToggleDock: M, writeAccess: N, o
|
|
|
71
71
|
}), /* @__PURE__ */ D(a, {
|
|
72
72
|
visible: A ?? !0,
|
|
73
73
|
engine: V,
|
|
74
|
-
children: /* @__PURE__ */ D(
|
|
74
|
+
children: /* @__PURE__ */ D(ie, {
|
|
75
75
|
value: R,
|
|
76
76
|
children: /* @__PURE__ */ D(o, {
|
|
77
77
|
value: L,
|
|
78
|
-
children: /* @__PURE__ */ D(
|
|
78
|
+
children: /* @__PURE__ */ D(v, {
|
|
79
79
|
navigate: oe,
|
|
80
80
|
isShellLessTarget: P,
|
|
81
81
|
children: /* @__PURE__ */ D(c, {
|
|
82
82
|
value: F,
|
|
83
83
|
onOpen: I,
|
|
84
|
-
children: /* @__PURE__ */ D(
|
|
84
|
+
children: /* @__PURE__ */ D(g, { children: /* @__PURE__ */ D(_, { children: /* @__PURE__ */ O("div", {
|
|
85
85
|
ref: K,
|
|
86
86
|
className: "relative flex h-full w-full flex-col bg-bg-surface",
|
|
87
87
|
role: "complementary",
|
|
@@ -94,7 +94,7 @@ var k = ({ onClose: k, visible: A, docked: j, onToggleDock: M, writeAccess: N, o
|
|
|
94
94
|
/* @__PURE__ */ D("div", {
|
|
95
95
|
className: "contents",
|
|
96
96
|
inert: X || void 0,
|
|
97
|
-
children: /* @__PURE__ */ D(
|
|
97
|
+
children: /* @__PURE__ */ D(m, {
|
|
98
98
|
contextLabel: le,
|
|
99
99
|
onNewConversation: () => ce(),
|
|
100
100
|
onOpenHistory: () => Q("history"),
|
|
@@ -106,7 +106,7 @@ var k = ({ onClose: k, visible: A, docked: j, onToggleDock: M, writeAccess: N, o
|
|
|
106
106
|
onToggleDock: M
|
|
107
107
|
})
|
|
108
108
|
}),
|
|
109
|
-
J && /* @__PURE__ */ D(
|
|
109
|
+
J && /* @__PURE__ */ D(h, {}),
|
|
110
110
|
G && /* @__PURE__ */ D(re, { onClose: () => U(!1) }),
|
|
111
111
|
/* @__PURE__ */ D("div", {
|
|
112
112
|
className: "contents",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ComponentType, FC, ReactNode } from 'react';
|
|
2
2
|
import { SpeechEngine } from '../speech/types';
|
|
3
|
+
import { VoiceView } from './voice/AssistantVoiceSession';
|
|
3
4
|
export interface LiveAvatarStageProps {
|
|
4
5
|
/** Full-screen (true) or the in-panel stage (false). */
|
|
5
6
|
expanded: boolean;
|
|
@@ -11,6 +12,42 @@ export interface AssistantLiveAvatarSeam {
|
|
|
11
12
|
decorateSpeechEngine: (base: SpeechEngine) => SpeechEngine;
|
|
12
13
|
/** Drawn by the voice stage instead of the waveform. Optional. */
|
|
13
14
|
Stage?: ComponentType<LiveAvatarStageProps>;
|
|
15
|
+
/**
|
|
16
|
+
* The avatar's ON switch. Optional. It is SHOWN only when `Stage` is given,
|
|
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.
|
|
19
|
+
*/
|
|
20
|
+
enabled?: LiveAvatarEnabledStore;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The avatar's ON flag as a store, read with `useSyncExternalStore`: the voice
|
|
24
|
+
* session subscribes and nothing above it re-renders. It must be STABLE, and
|
|
25
|
+
* the flag it reads must be session-only, never persisted (the avatar
|
|
26
|
+
* preferences' `avatarEnabled`), so every page load starts OFF.
|
|
27
|
+
*
|
|
28
|
+
* ★★ ON is only ever the person's own click, on a control the host offers
|
|
29
|
+
* only while `voiceLive` says a voice conversation is live, and a paid session
|
|
30
|
+
* starts only then: no avatar after its conversation has ended. The panel
|
|
31
|
+
* switches the avatar OFF, never ON.
|
|
32
|
+
*/
|
|
33
|
+
export interface LiveAvatarEnabledStore {
|
|
34
|
+
subscribe: (onChange: () => void) => () => void;
|
|
35
|
+
getSnapshot: () => boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Switch the avatar OFF with the SAME action the host's own Avatar control
|
|
38
|
+
* takes, so its paid session ends along the one path the host already has.
|
|
39
|
+
* Called at the end of EVERY voice conversation, even when the avatar is
|
|
40
|
+
* already OFF; again for any ON while a failed one is still on screen; and
|
|
41
|
+
* 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.
|
|
43
|
+
*/
|
|
44
|
+
switchOff: () => void;
|
|
45
|
+
/**
|
|
46
|
+
* Told `true` once a voice conversation is live and `false` when it is over
|
|
47
|
+
* (just before that end's `switchOff`). Outside that window the host offers
|
|
48
|
+
* no ON control and starts no paid session. Idempotent, as `switchOff`.
|
|
49
|
+
*/
|
|
50
|
+
voiceLive: (live: boolean) => void;
|
|
14
51
|
}
|
|
15
52
|
export declare const LiveAvatarSeamProvider: FC<{
|
|
16
53
|
value?: AssistantLiveAvatarSeam;
|
|
@@ -26,3 +63,83 @@ export declare function useLiveAvatarStage(): ComponentType<LiveAvatarStageProps
|
|
|
26
63
|
* is to the store `AssistantSpeechProvider` already subscribes to.
|
|
27
64
|
*/
|
|
28
65
|
export declare function useLiveAvatarSpeechEngine(seam: AssistantLiveAvatarSeam | undefined): SpeechEngine | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* The voice view to SHOW, given the one the user CHOSE; and the avatar kept to
|
|
68
|
+
* the live conversation (`ended`: over, but still on screen to say why it
|
|
69
|
+
* failed; leaving the screen ends it too).
|
|
70
|
+
*
|
|
71
|
+
* ★★ Switching the avatar ON must show it, and every session opens in the
|
|
72
|
+
* compact view (`mini`), which draws no stage. So while the avatar is ON, a
|
|
73
|
+
* session the user left compact shows the in-panel stage: the same session,
|
|
74
|
+
* re-rendered with the stage the size button already leads to, so nothing above
|
|
75
|
+
* the stage remounts. Switched OFF, it shows their choice again.
|
|
76
|
+
*
|
|
77
|
+
* ★★ The user's choice is never written, and nothing is stored: the next session
|
|
78
|
+
* opens compact as before, with the avatar OFF.
|
|
79
|
+
*
|
|
80
|
+
* ★ The moment they choose a view themselves while it is held (the size button,
|
|
81
|
+
* the change chip, an attachment), that view is theirs and stays when the
|
|
82
|
+
* avatar goes off. A conversation that is over lets go at once too: its
|
|
83
|
+
* failure is explained in the user's view, not first on a stage going away.
|
|
84
|
+
*
|
|
85
|
+
* ★ Adjusted DURING RENDER, as `AIAssistantPanel` adjusts its preview flag: no
|
|
86
|
+
* frame shows the stale view, and the update is conditional on the state it
|
|
87
|
+
* replaces, so it cannot loop.
|
|
88
|
+
*/
|
|
89
|
+
export declare function useLiveAvatarVoiceView(chosen: VoiceView, ended: boolean): VoiceView;
|
|
90
|
+
/**
|
|
91
|
+
* @internal Exported for `VoiceMiniController` alone; not part of the module's
|
|
92
|
+
* public surface (the assistant barrel star-exports this module).
|
|
93
|
+
*
|
|
94
|
+
* Whether the compact strip shows `LiveAvatarMiniIndicator`: an avatar that is
|
|
95
|
+
* ON and can be shown. `VoiceMiniController` keeps its bottom band for the
|
|
96
|
+
* chip then, and ONLY then; false, it adds nothing and is the strip it was.
|
|
97
|
+
*/
|
|
98
|
+
export declare function useLiveAvatarMiniIndicatorShown(): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* The compact strip's sign that a live avatar is ON, with its stop.
|
|
101
|
+
*
|
|
102
|
+
* ★★ The strip draws no stage, so a person who chose it while the avatar is on
|
|
103
|
+
* would not see that a session, billed by the minute, is still running.
|
|
104
|
+
* Without an avatar that is ON and can be shown it renders nothing, and the
|
|
105
|
+
* strip is byte for byte what it was.
|
|
106
|
+
*
|
|
107
|
+
* ★★ Stop switches the AVATAR off through `switchOff`, the action the host's
|
|
108
|
+
* own Avatar control takes, called with NO arguments (its contract is
|
|
109
|
+
* `() => void`, so a host's own optional parameter never receives the click
|
|
110
|
+
* event). It never ends the voice conversation and never touches audio: the
|
|
111
|
+
* speech engine stays the only speaker. Focus on it goes to the session's
|
|
112
|
+
* microphone control (End while that is disabled), never to <body>, whoever
|
|
113
|
+
* switched the avatar off; to End when the chip goes with its strip.
|
|
114
|
+
*
|
|
115
|
+
* ★★ It handles no keys: Escape and Tab stay the session's (BOFF-8065). It
|
|
116
|
+
* comes after the microphone control, so Tab meets the strip's own control
|
|
117
|
+
* first.
|
|
118
|
+
*
|
|
119
|
+
* ★ "Avatar on" is a state, not a label. The control is an icon named by
|
|
120
|
+
* `avatar.stop`, so no button is named for the opposite of what it does, and
|
|
121
|
+
* its name is all of its words (WCAG 2.5.3, as the session's End).
|
|
122
|
+
*
|
|
123
|
+
* ★★ In the strip's bottom band, which the strip keeps free while this shows
|
|
124
|
+
* (`VoiceMiniController`: its caption column stops above the band, and shows
|
|
125
|
+
* fewer caption lines when two notices leave no room for three), so nothing
|
|
126
|
+
* on the strip overlaps it. Positioned against the voice session as its
|
|
127
|
+
* corner controls are, under the caption column: the session's `px-3` plus the
|
|
128
|
+
* strip's `px-4`, physical `left-*` as `VoiceStatusLine` explains, mirrored
|
|
129
|
+
* under `rtl:` so it never sits under the microphone. Its padding is logical
|
|
130
|
+
* (`ps-*`/`pe-*`, as `AssistantUiOfferLink`'s), so the label keeps 10px from
|
|
131
|
+
* the rounded start and the stop 2px from the end in RTL too. No wider than
|
|
132
|
+
* that column plus 6px of the gap after it (`max-w-*`: 100% less 3.5rem of
|
|
133
|
+
* paddings, the 0.75rem gap and the microphone's 10rem `w-40`, plus 0.375rem),
|
|
134
|
+
* so its stop's target (below) ends at least 6px before the microphone, in
|
|
135
|
+
* either direction, however long a translation of "Avatar on" is.
|
|
136
|
+
*
|
|
137
|
+
* ★ As tall as End in the strip (`h-8`), its stop drawn at 28px; the stop's
|
|
138
|
+
* TARGET is 44px square (`after:-inset-2`, WCAG 2.5.5), and `bottom-1.5` ends
|
|
139
|
+
* that target at the strip's own edge. It keeps a logical margin of 6px at its
|
|
140
|
+
* end (`after:me-1.5`), taken back at its start (`after:-ms-1.5`) so it stays
|
|
141
|
+
* 44px: it ends 2px past the drawn stop, short of the chip's edge, so a tap at
|
|
142
|
+
* the microphone's edge never lands on the stop. Logical, as the padding: it
|
|
143
|
+
* mirrors in RTL.
|
|
144
|
+
*/
|
|
145
|
+
export declare const LiveAvatarMiniIndicator: FC;
|
|
@@ -1,17 +1,105 @@
|
|
|
1
1
|
import { getSpeechEngine as e, subscribeSpeechEngine as t } from "../speech/createSpeechEngine.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { useAvatarCopy as n } from "../avatar/useAvatarCopy.js";
|
|
3
|
+
import { ASSISTANT_VOICE_SESSION_ATTR as r } from "./panelOverlays.js";
|
|
4
|
+
import { createContext as i, useContext as a, useEffect as o, useLayoutEffect as s, useMemo as c, useRef as l, useState as u, useSyncExternalStore as d } from "react";
|
|
5
|
+
import { VideoOff as f } from "lucide-react";
|
|
6
|
+
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
4
7
|
//#region src/shared/assistant/ui/assistantLiveAvatarSeam.tsx
|
|
5
|
-
var
|
|
8
|
+
var h = i(null), g = ({ value: e, children: t }) => /* @__PURE__ */ p(h.Provider, {
|
|
6
9
|
value: e ?? null,
|
|
7
10
|
children: t
|
|
8
11
|
});
|
|
9
|
-
function
|
|
10
|
-
return
|
|
12
|
+
function _() {
|
|
13
|
+
return a(h)?.Stage ?? null;
|
|
11
14
|
}
|
|
12
|
-
function
|
|
13
|
-
let r =
|
|
14
|
-
return
|
|
15
|
+
function v(n) {
|
|
16
|
+
let r = d(t, e, e), i = n?.decorateSpeechEngine;
|
|
17
|
+
return c(() => i ? i(r) : void 0, [i, r]);
|
|
15
18
|
}
|
|
19
|
+
var y = {
|
|
20
|
+
subscribe: () => () => {},
|
|
21
|
+
getSnapshot: () => !1,
|
|
22
|
+
switchOff: () => {},
|
|
23
|
+
voiceLive: () => {}
|
|
24
|
+
};
|
|
25
|
+
function b(e) {
|
|
26
|
+
return e?.Stage ? e.enabled : void 0;
|
|
27
|
+
}
|
|
28
|
+
function x(e) {
|
|
29
|
+
let t = e ?? y;
|
|
30
|
+
return d(t.subscribe, t.getSnapshot, t.getSnapshot);
|
|
31
|
+
}
|
|
32
|
+
function S(e, t) {
|
|
33
|
+
let n = a(h);
|
|
34
|
+
C(n?.enabled, t);
|
|
35
|
+
let r = b(n), i = x(r), [o, s] = u(() => ({
|
|
36
|
+
on: i,
|
|
37
|
+
chosen: e,
|
|
38
|
+
open: i && e === "mini"
|
|
39
|
+
}));
|
|
40
|
+
if (!r) return e;
|
|
41
|
+
let c = o.open;
|
|
42
|
+
return (o.on !== i || o.chosen !== e) && (c = o.on !== i && i && e === "mini", s({
|
|
43
|
+
on: i,
|
|
44
|
+
chosen: e,
|
|
45
|
+
open: c
|
|
46
|
+
})), c && !t ? "panel" : e;
|
|
47
|
+
}
|
|
48
|
+
function C(e, t) {
|
|
49
|
+
let n = l(0);
|
|
50
|
+
o(() => {
|
|
51
|
+
if (!e) return;
|
|
52
|
+
let r = ++n.current, i = (e) => queueMicrotask(() => {
|
|
53
|
+
n.current === r && e();
|
|
54
|
+
}), a = () => i(() => {
|
|
55
|
+
e.voiceLive(!1), e.switchOff();
|
|
56
|
+
});
|
|
57
|
+
if (!t) return i(() => e.voiceLive(!0)), a;
|
|
58
|
+
a();
|
|
59
|
+
let o = e.getSnapshot();
|
|
60
|
+
return e.subscribe(() => {
|
|
61
|
+
let t = o;
|
|
62
|
+
o = e.getSnapshot(), o && !t && i(() => e.switchOff());
|
|
63
|
+
});
|
|
64
|
+
}, [e, t]);
|
|
65
|
+
}
|
|
66
|
+
function w() {
|
|
67
|
+
return x(b(a(h)));
|
|
68
|
+
}
|
|
69
|
+
var T = () => {
|
|
70
|
+
let e = b(a(h)), t = x(e);
|
|
71
|
+
return e && t ? /* @__PURE__ */ p(O, {
|
|
72
|
+
onStop: e.switchOff,
|
|
73
|
+
isOn: e.getSnapshot
|
|
74
|
+
}) : null;
|
|
75
|
+
}, E = "[data-testid=\"assistant-voice-orb\"]", D = "[data-testid=\"assistant-voice-end\"]", O = ({ onStop: e, isOn: t }) => {
|
|
76
|
+
let i = n(), a = l(null), o = l(t);
|
|
77
|
+
return s(() => {
|
|
78
|
+
let e = a.current, t = o.current;
|
|
79
|
+
return () => {
|
|
80
|
+
if (!e?.contains(e.ownerDocument.activeElement)) return;
|
|
81
|
+
let n = e.closest(`[${r}]`), i = t() ? null : n?.querySelector(E);
|
|
82
|
+
(i && !i.hasAttribute("disabled") ? i : n?.querySelector(D))?.focus();
|
|
83
|
+
};
|
|
84
|
+
}, []), /* @__PURE__ */ m("div", {
|
|
85
|
+
ref: a,
|
|
86
|
+
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
|
+
"data-testid": "assistant-voice-avatar",
|
|
88
|
+
children: [/* @__PURE__ */ p("span", {
|
|
89
|
+
className: "truncate",
|
|
90
|
+
children: i.stateOn
|
|
91
|
+
}), /* @__PURE__ */ p("button", {
|
|
92
|
+
type: "button",
|
|
93
|
+
onClick: () => e(),
|
|
94
|
+
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": i.stop,
|
|
96
|
+
"data-testid": "assistant-voice-avatar-stop",
|
|
97
|
+
children: /* @__PURE__ */ p(f, {
|
|
98
|
+
className: "size-4",
|
|
99
|
+
"aria-hidden": "true"
|
|
100
|
+
})
|
|
101
|
+
})]
|
|
102
|
+
});
|
|
103
|
+
};
|
|
16
104
|
//#endregion
|
|
17
|
-
export {
|
|
105
|
+
export { T as LiveAvatarMiniIndicator, g as LiveAvatarSeamProvider, w as useLiveAvatarMiniIndicatorShown, v as useLiveAvatarSpeechEngine, _ as useLiveAvatarStage, S as useLiveAvatarVoiceView };
|