@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,36 +1,83 @@
|
|
|
1
1
|
import { StoreApi, UseBoundStore } from 'zustand';
|
|
2
2
|
import { PersistStorage } from 'zustand/middleware';
|
|
3
|
+
import { HostedSpeaker } from '../speech/hostedVoiceCatalogue';
|
|
3
4
|
/** The base storage name; the profile prefix is added by the profile-scoped storage. */
|
|
4
5
|
export declare const AVATAR_PREFERENCES_STORAGE_NAME = "hb-ai-assistant-avatar";
|
|
5
6
|
/**
|
|
6
|
-
* Which voice the avatar speaks with
|
|
7
|
-
* the assistant
|
|
8
|
-
*
|
|
7
|
+
* Which voice the avatar speaks with (BOFF-9341). Only replies the avatar takes
|
|
8
|
+
* use it; the assistant's own voice, the one Chat reads aloud with, is never
|
|
9
|
+
* changed by it.
|
|
10
|
+
* - `mine`: the voice the person already picked for the assistant ("Keep my
|
|
11
|
+
* current voice");
|
|
12
|
+
* - `paired`: the speaker the HOST configured for the avatar on screen
|
|
13
|
+
* ("Default for this avatar"), so it follows the face when the face is
|
|
14
|
+
* switched; a face with none configured keeps the assistant's voice;
|
|
15
|
+
* - `speaker`: one of the hosted speakers (`avatarSpeaker`), in the language
|
|
16
|
+
* of each reply.
|
|
17
|
+
*/
|
|
18
|
+
export type AvatarVoiceMode = 'mine' | 'paired' | 'speaker';
|
|
19
|
+
/** A mode a host may make the default (`createLiveAvatarSeam`'s `defaultAvatarVoiceMode`): a speaker is the person's own pick. */
|
|
20
|
+
export type AvatarVoiceDefault = Exclude<AvatarVoiceMode, 'speaker'>;
|
|
21
|
+
/** What applies when neither the person nor the host chose: their current voice is kept. */
|
|
22
|
+
export declare const DEFAULT_AVATAR_VOICE_MODE: AvatarVoiceDefault;
|
|
23
|
+
/**
|
|
24
|
+
* What is written. `v` is this file's own format number (see the header); it
|
|
25
|
+
* stays 1, because every field is validated on merge and an older build reads
|
|
26
|
+
* the two it knows (`avatarId`, and `voiceMode`, where anything it does not
|
|
27
|
+
* know reads as `mine`).
|
|
28
|
+
*
|
|
29
|
+
* ★★ `voiceChosen` marks a voice the PERSON chose. Builds before BOFF-9341 had
|
|
30
|
+
* no voice control and wrote `voiceMode: 'mine'` as a default; read as a
|
|
31
|
+
* choice, it would hide the host's default (`defaultAvatarVoiceMode`) from
|
|
32
|
+
* everyone who ever used the avatar. Without the mark, a stored voice is no
|
|
33
|
+
* choice at all.
|
|
9
34
|
*/
|
|
10
|
-
export type AvatarVoiceMode = 'paired' | 'mine';
|
|
11
|
-
export declare const DEFAULT_AVATAR_VOICE_MODE: AvatarVoiceMode;
|
|
12
|
-
/** What is written. `v` is this file's own format number (see the header). */
|
|
13
35
|
export interface AvatarPreferencesPersisted {
|
|
14
36
|
v: 1;
|
|
15
37
|
avatarId: string | null;
|
|
16
|
-
voiceMode: AvatarVoiceMode;
|
|
38
|
+
voiceMode: AvatarVoiceMode | null;
|
|
39
|
+
avatarSpeaker: HostedSpeaker | null;
|
|
40
|
+
voiceChosen: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** The person's voice choice for the avatar, as the seam reads it. */
|
|
43
|
+
export interface AvatarVoicePreference {
|
|
44
|
+
/** Null until the person chooses: the host's default applies, else `DEFAULT_AVATAR_VOICE_MODE`. */
|
|
45
|
+
readonly voiceMode: AvatarVoiceMode | null;
|
|
46
|
+
/** The speaker of `voiceMode: 'speaker'`, and null in every other mode. */
|
|
47
|
+
readonly avatarSpeaker: HostedSpeaker | null;
|
|
17
48
|
}
|
|
18
|
-
export interface AvatarPreferencesState {
|
|
49
|
+
export interface AvatarPreferencesState extends AvatarVoicePreference {
|
|
19
50
|
/** Never persisted: every page load starts with the avatar OFF. */
|
|
20
51
|
avatarEnabled: boolean;
|
|
21
52
|
/** The chosen avatar, or null before one is chosen. */
|
|
22
53
|
avatarId: string | null;
|
|
23
|
-
voiceMode: AvatarVoiceMode;
|
|
24
54
|
setAvatarEnabled: (enabled: boolean) => void;
|
|
25
55
|
setAvatarId: (avatarId: string | null) => void;
|
|
26
|
-
|
|
56
|
+
/** The person picks "Keep my current voice" or "Default for this avatar". Anything else is ignored. */
|
|
57
|
+
setVoiceMode: (mode: AvatarVoiceDefault) => void;
|
|
58
|
+
/** The person picks one of the hosted speakers. Anything not in `HOSTED_SPEAKERS` is ignored. */
|
|
59
|
+
setAvatarSpeaker: (speaker: HostedSpeaker) => void;
|
|
27
60
|
}
|
|
28
61
|
export declare function resolveStoredAvatarId(value: unknown): string | null;
|
|
29
|
-
|
|
30
|
-
export declare function
|
|
62
|
+
/** A stored voice mode this build knows, or null (no choice): never a guess. */
|
|
63
|
+
export declare function resolveStoredAvatarVoiceMode(value: unknown): AvatarVoiceMode | null;
|
|
64
|
+
/**
|
|
65
|
+
* The voice choice a stored snapshot holds: a mode and, for `speaker`, a speaker
|
|
66
|
+
* this build offers. Not marked as the person's (`voiceChosen`), or a speaker
|
|
67
|
+
* mode without a valid speaker (a hand edit, or a speaker a later build added):
|
|
68
|
+
* no choice, so the host's default applies again rather than a voice nobody
|
|
69
|
+
* picked.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveStoredAvatarVoice(stored: Record<string, unknown>): AvatarVoicePreference;
|
|
72
|
+
/**
|
|
73
|
+
* The mode that applies: the person's choice ALWAYS wins; then the host's
|
|
74
|
+
* default (`createLiveAvatarSeam`'s `defaultAvatarVoiceMode`); then `mine`.
|
|
75
|
+
*/
|
|
76
|
+
export declare function resolveAvatarVoiceMode(preference: AvatarVoicePreference, hostDefault?: AvatarVoiceDefault): AvatarVoiceMode;
|
|
77
|
+
export declare function avatarPreferencesToPersist(state: Pick<AvatarPreferencesState, 'avatarId' | 'voiceMode' | 'avatarSpeaker'>): AvatarPreferencesPersisted;
|
|
31
78
|
/**
|
|
32
|
-
* Fold what storage holds into the live state. Only the
|
|
33
|
-
*
|
|
79
|
+
* Fold what storage holds into the live state. Only the preference fields are
|
|
80
|
+
* taken, each validated; `avatarEnabled` and the actions always come from
|
|
34
81
|
* `current`, whatever the stored value says.
|
|
35
82
|
*/
|
|
36
83
|
export declare function mergePersistedAvatarPreferences(persisted: unknown, current: AvatarPreferencesState): AvatarPreferencesState;
|
|
@@ -1,46 +1,79 @@
|
|
|
1
1
|
import { createProfileScopedStorage as e } from "../../utils/profileScopedStorage.js";
|
|
2
2
|
import "../../../shared-utils.js";
|
|
3
3
|
import { createGuardedAssistantStorage as t } from "../guardedStorage.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { isHostedSpeaker as n } from "../speech/hostedVoiceCatalogue.js";
|
|
5
|
+
import { create as r } from "zustand";
|
|
6
|
+
import { persist as i } from "zustand/middleware";
|
|
6
7
|
//#region src/shared/assistant/avatar/avatarPreferences.ts
|
|
7
|
-
var
|
|
8
|
-
function s(e) {
|
|
9
|
-
return typeof e == "string" && o.test(e) ? e : null;
|
|
10
|
-
}
|
|
8
|
+
var a = "hb-ai-assistant-avatar", o = "mine", s = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
11
9
|
function c(e) {
|
|
12
|
-
return e
|
|
10
|
+
return typeof e == "string" && s.test(e) ? e : null;
|
|
13
11
|
}
|
|
14
12
|
function l(e) {
|
|
13
|
+
return e === "mine" || e === "paired" || e === "speaker" ? e : null;
|
|
14
|
+
}
|
|
15
|
+
function u(e) {
|
|
16
|
+
let t = {
|
|
17
|
+
voiceMode: null,
|
|
18
|
+
avatarSpeaker: null
|
|
19
|
+
};
|
|
20
|
+
if (e.voiceChosen !== !0) return t;
|
|
21
|
+
let r = l(e.voiceMode);
|
|
22
|
+
return r === null ? t : r === "speaker" ? n(e.avatarSpeaker) ? {
|
|
23
|
+
voiceMode: r,
|
|
24
|
+
avatarSpeaker: e.avatarSpeaker
|
|
25
|
+
} : t : {
|
|
26
|
+
voiceMode: r,
|
|
27
|
+
avatarSpeaker: null
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function d(e, t = o) {
|
|
31
|
+
return e.voiceMode ?? t;
|
|
32
|
+
}
|
|
33
|
+
function f(e) {
|
|
15
34
|
return {
|
|
16
35
|
v: 1,
|
|
17
36
|
avatarId: e.avatarId,
|
|
18
|
-
voiceMode: e.voiceMode
|
|
37
|
+
voiceMode: e.voiceMode,
|
|
38
|
+
avatarSpeaker: e.voiceMode === "speaker" ? e.avatarSpeaker : null,
|
|
39
|
+
voiceChosen: e.voiceMode !== null
|
|
19
40
|
};
|
|
20
41
|
}
|
|
21
|
-
function
|
|
42
|
+
function p(e, t) {
|
|
22
43
|
let n = typeof e == "object" && e ? e : {};
|
|
23
44
|
return {
|
|
24
45
|
...t,
|
|
25
|
-
avatarId:
|
|
26
|
-
|
|
46
|
+
avatarId: c(n.avatarId),
|
|
47
|
+
...u(n)
|
|
27
48
|
};
|
|
28
49
|
}
|
|
29
|
-
function
|
|
30
|
-
return
|
|
50
|
+
function m(e) {
|
|
51
|
+
return r()(i((e) => ({
|
|
31
52
|
avatarEnabled: !1,
|
|
32
53
|
avatarId: null,
|
|
33
|
-
voiceMode:
|
|
54
|
+
voiceMode: null,
|
|
55
|
+
avatarSpeaker: null,
|
|
34
56
|
setAvatarEnabled: (t) => e({ avatarEnabled: t }),
|
|
35
|
-
setAvatarId: (t) => e({ avatarId:
|
|
36
|
-
setVoiceMode: (t) =>
|
|
57
|
+
setAvatarId: (t) => e({ avatarId: c(t) }),
|
|
58
|
+
setVoiceMode: (t) => {
|
|
59
|
+
t !== "mine" && t !== "paired" || e({
|
|
60
|
+
voiceMode: t,
|
|
61
|
+
avatarSpeaker: null
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
setAvatarSpeaker: (t) => {
|
|
65
|
+
n(t) && e({
|
|
66
|
+
voiceMode: "speaker",
|
|
67
|
+
avatarSpeaker: t
|
|
68
|
+
});
|
|
69
|
+
}
|
|
37
70
|
}), {
|
|
38
|
-
name:
|
|
71
|
+
name: a,
|
|
39
72
|
storage: e,
|
|
40
|
-
partialize:
|
|
41
|
-
merge:
|
|
73
|
+
partialize: f,
|
|
74
|
+
merge: p
|
|
42
75
|
}));
|
|
43
76
|
}
|
|
44
|
-
var
|
|
77
|
+
var h = m(t(e()));
|
|
45
78
|
//#endregion
|
|
46
|
-
export {
|
|
79
|
+
export { a as AVATAR_PREFERENCES_STORAGE_NAME, o as DEFAULT_AVATAR_VOICE_MODE, f as avatarPreferencesToPersist, m as createAvatarPreferencesStore, p as mergePersistedAvatarPreferences, d as resolveAvatarVoiceMode, c as resolveStoredAvatarId, u as resolveStoredAvatarVoice, l as resolveStoredAvatarVoiceMode, h as useAvatarPreferencesStore };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { HostedSpeaker } from '../../speech/hostedVoiceCatalogue';
|
|
3
|
+
import { SpeechVoiceLike } from '../../speech/types';
|
|
4
|
+
import { LiveAvatarTabProps } from '../../ui/assistantLiveAvatarSeam';
|
|
5
|
+
import { AvatarVoiceDefault, AvatarVoicePreference } from '../avatarPreferences';
|
|
6
|
+
import { LiveAvatarVoices } from './avatarVoice';
|
|
7
|
+
import { LiveAvatarController } from './liveAvatarController';
|
|
8
|
+
/** The person's avatar voice choice: read by the engine decorator per reply, written by the tab's select. STABLE. */
|
|
9
|
+
export interface LiveAvatarVoiceStore {
|
|
10
|
+
getState(): AvatarVoicePreference;
|
|
11
|
+
subscribe(listener: () => void): () => void;
|
|
12
|
+
setVoiceMode(mode: AvatarVoiceDefault): void;
|
|
13
|
+
setAvatarSpeaker(speaker: HostedSpeaker): void;
|
|
14
|
+
}
|
|
15
|
+
export interface LiveAvatarTabViewProps extends LiveAvatarTabProps {
|
|
16
|
+
readonly controller: LiveAvatarController;
|
|
17
|
+
/** Display names for the avatar ids the server offers; without one the chooser numbers them. */
|
|
18
|
+
readonly avatarNames?: Readonly<Record<string, string>>;
|
|
19
|
+
/** The host's speaker per avatar ("Default for this avatar"). */
|
|
20
|
+
readonly avatarVoices: LiveAvatarVoices;
|
|
21
|
+
readonly defaultAvatarVoiceMode?: AvatarVoiceDefault;
|
|
22
|
+
readonly voiceStore: LiveAvatarVoiceStore;
|
|
23
|
+
/** Wall-clock milliseconds: the retry time and the Turn off guard. Tests pass their own. */
|
|
24
|
+
readonly now?: () => number;
|
|
25
|
+
}
|
|
26
|
+
/** A voice name as the person reads it: a hosted voice by its speaker ("Kore"), any other by its own name. */
|
|
27
|
+
export declare function avatarVoiceDisplayName(name: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* `speaker` in the language the person speaks, as the engine lists it for that language (`voiceChoices`); null when it is not offered
|
|
30
|
+
* there (no hosted voice for the language, or a hosted engine latched off, which lists none).
|
|
31
|
+
*/
|
|
32
|
+
export declare function avatarPreviewVoice(speaker: HostedSpeaker, voiceChoices: readonly SpeechVoiceLike[]): SpeechVoiceLike | null;
|
|
33
|
+
export declare const LiveAvatarTabView: FC<LiveAvatarTabViewProps>;
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { HOSTED_SPEAKERS as e, isHostedSpeaker as t } from "../../speech/hostedVoiceCatalogue.js";
|
|
2
|
+
import { useAvatarCopy as n } from "../useAvatarCopy.js";
|
|
3
|
+
import { useAssistantSpeech as ee } from "../../ui/AssistantSpeechProvider.js";
|
|
4
|
+
import { resolveAvatarVoiceMode as te } from "../avatarPreferences.js";
|
|
5
|
+
import "./liveAvatarController.js";
|
|
6
|
+
import { liveAvatarStatusText as ne } from "./liveAvatarStatus.js";
|
|
7
|
+
import { hostedAvatarVoice as r } from "./avatarVoice.js";
|
|
8
|
+
import { useCallback as re, useEffect as i, useId as a, useLayoutEffect as ie, useRef as o, useState as s, useSyncExternalStore as c } from "react";
|
|
9
|
+
import { Volume2 as l } from "lucide-react";
|
|
10
|
+
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
11
|
+
//#region src/shared/assistant/avatar/live/LiveAvatarTabView.tsx
|
|
12
|
+
var f = "mine", p = "paired", m = "speaker:", h = /-Chirp3-HD-([A-Za-z]+)$/;
|
|
13
|
+
function g(e) {
|
|
14
|
+
let n = h.exec(e)?.[1];
|
|
15
|
+
return n !== void 0 && t(n) ? n : e;
|
|
16
|
+
}
|
|
17
|
+
function _(e, t) {
|
|
18
|
+
let n = t.find((e) => e.source === "hosted")?.lang;
|
|
19
|
+
return n === void 0 ? null : r(e, n, t);
|
|
20
|
+
}
|
|
21
|
+
var v = "text-xs font-medium text-text-secondary", y = "text-xs text-text-muted", b = "focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong", x = `inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-xl border border-border-default bg-bg-elevated px-4 text-sm font-medium text-text-primary transition-colors hover:bg-bg-raised aria-disabled:cursor-default aria-disabled:opacity-60 aria-disabled:hover:bg-bg-elevated ${b}`, ae = `inline-flex min-h-11 w-full items-center justify-center rounded-xl bg-action-primary-bg px-4 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover aria-disabled:cursor-default aria-disabled:opacity-60 aria-disabled:hover:bg-action-primary-bg ${b}`, oe = "relative inline-flex min-h-11 min-w-0 max-w-full cursor-pointer items-center rounded-full border px-4 text-sm font-medium transition-colors focus-within:ring-2 focus-within:ring-border-strong", se = "absolute inset-0 m-0 size-full cursor-pointer appearance-none rounded-full opacity-0", S = ({ controller: r, shown: h, voice: S, handsFreeOff: C, leaveHintId: ce, startVoiceRef: w, avatarNames: le, avatarVoices: ue, defaultAvatarVoiceMode: de, voiceStore: T, now: E = Date.now }) => {
|
|
22
|
+
let D = n(), O = ee(), k = a(), A = a(), j = a(), M = a(), N = a(), P = o(null), F = c(r.subscribe, r.getSnapshot, r.getSnapshot), I = c(T.subscribe, T.getState, T.getState);
|
|
23
|
+
i(() => {
|
|
24
|
+
if (h) return r.setStageMounted(!0), () => r.setStageMounted(!1);
|
|
25
|
+
}, [r, h]), i(() => {
|
|
26
|
+
let e = r.getSnapshot();
|
|
27
|
+
e.phase === "off" && e.avatars.length === 0 && r.requestOn({ start: !1 });
|
|
28
|
+
}, [r]);
|
|
29
|
+
let { phase: L } = F, R = L === "starting" || L === "connecting" || L === "ready" || L === "ending", [z, B] = s(!1);
|
|
30
|
+
z && (L === "off" || L === "choosing" || L === "ready" || L === "ending") && B(!1);
|
|
31
|
+
let V = E(), fe = F.retryAt !== null && F.retryAt > V, H = ne(F, D, V), U = F.selectedAvatarId, W = (e) => {
|
|
32
|
+
let t = F.avatars.indexOf(e);
|
|
33
|
+
return le?.[e] ?? (t === -1 ? D.chooserNone : `${D.chooserLabel} ${t + 1}`);
|
|
34
|
+
}, G = L === "ready" ? { kind: "turn-off" } : L === "ending" ? { kind: "ending" } : z || L === "starting" || L === "connecting" ? { kind: "starting" } : F.offered ? {
|
|
35
|
+
kind: "turn-on",
|
|
36
|
+
ready: !fe && U !== null && (L === "choosing" || L === "off" && F.avatars.includes(U))
|
|
37
|
+
} : null, K = o(null), q = G?.kind === "turn-off";
|
|
38
|
+
ie(() => {
|
|
39
|
+
K.current = q ? E() : null;
|
|
40
|
+
}, [q, E]);
|
|
41
|
+
let pe = re((e) => {
|
|
42
|
+
if (e !== null) return () => {
|
|
43
|
+
e.ownerDocument.activeElement === e && P.current?.focus();
|
|
44
|
+
};
|
|
45
|
+
}, []), me = () => {
|
|
46
|
+
if (G !== null) switch (G.kind) {
|
|
47
|
+
case "turn-on":
|
|
48
|
+
if (!G.ready || U === null) return;
|
|
49
|
+
B(!0), L === "choosing" ? r.confirm() : r.requestOn({
|
|
50
|
+
start: !0,
|
|
51
|
+
avatarId: U
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
case "turn-off": {
|
|
55
|
+
let e = K.current;
|
|
56
|
+
if (e !== null && E() - e < 1e3) return;
|
|
57
|
+
r.switchOff();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
case "starting":
|
|
61
|
+
case "ending": return;
|
|
62
|
+
}
|
|
63
|
+
}, J = te(I, de), Y = U === null ? null : ue.get(U) ?? null, X = J === "speaker" && I.avatarSpeaker !== null ? `${m}${I.avatarSpeaker}` : J === "paired" && Y !== null ? p : f, Z = X === p ? Y : X.startsWith(m) ? I.avatarSpeaker : null, Q = X === f ? O.activeVoiceName : Z === null ? null : _(Z, O.voiceChoices)?.name ?? null, he = O.speakingMessageId !== null || O.spokenChunks !== null, $ = Q !== null && !S.live && !he, ge = O.activeVoiceName ? D.voiceModeMineNamed(g(O.activeVoiceName)) : D.voiceModeMine, _e = (e) => {
|
|
64
|
+
if (e === f || e === p) {
|
|
65
|
+
T.setVoiceMode(e);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let n = e.slice(8);
|
|
69
|
+
e.startsWith(m) && t(n) && T.setAvatarSpeaker(n);
|
|
70
|
+
}, ve = L === "ending" ? D.stateEnding : L === "connecting" || L === "ready" ? D.stageWaiting : L === "starting" ? D.stateStarting : D.stateOff, ye = L === "off" && F.avatars.length === 0 && F.problem !== null && F.offered;
|
|
71
|
+
return /* @__PURE__ */ d("div", {
|
|
72
|
+
ref: P,
|
|
73
|
+
tabIndex: -1,
|
|
74
|
+
className: "flex min-h-full shrink-0 flex-col gap-3 focus:outline-none",
|
|
75
|
+
"data-testid": "assistant-avatar-tab-view",
|
|
76
|
+
children: [
|
|
77
|
+
/* @__PURE__ */ u("div", {
|
|
78
|
+
className: "flex max-h-80 min-h-[7.5rem] grow basis-30 justify-center",
|
|
79
|
+
children: /* @__PURE__ */ d("div", {
|
|
80
|
+
className: "relative aspect-square h-full max-w-full overflow-hidden rounded-2xl border border-border-subtle bg-bg-sunken",
|
|
81
|
+
"data-testid": "assistant-avatar-tab-frame",
|
|
82
|
+
"data-phase": L,
|
|
83
|
+
children: [
|
|
84
|
+
R ? /* @__PURE__ */ u("video", {
|
|
85
|
+
ref: r.attachVideo,
|
|
86
|
+
className: `size-full object-cover ${F.videoReady ? "" : "invisible"}`,
|
|
87
|
+
"aria-label": D.stageLabel,
|
|
88
|
+
autoPlay: !0,
|
|
89
|
+
muted: !0,
|
|
90
|
+
playsInline: !0,
|
|
91
|
+
"data-testid": "assistant-avatar-tab-video"
|
|
92
|
+
}) : null,
|
|
93
|
+
R && F.videoReady ? null : /* @__PURE__ */ u("p", {
|
|
94
|
+
className: "absolute inset-0 flex items-center justify-center p-3 text-center text-sm text-text-muted",
|
|
95
|
+
children: ve
|
|
96
|
+
}),
|
|
97
|
+
F.audioBlocked ? /* @__PURE__ */ d("button", {
|
|
98
|
+
type: "button",
|
|
99
|
+
onClick: () => r.resumeAudio(),
|
|
100
|
+
className: `absolute inset-x-2 bottom-2 inline-flex min-h-11 items-center justify-center gap-1.5 rounded-xl bg-bg-elevated px-3 text-xs font-medium text-text-primary ${b}`,
|
|
101
|
+
"data-testid": "assistant-avatar-tab-sound",
|
|
102
|
+
children: [/* @__PURE__ */ u(l, {
|
|
103
|
+
className: "size-4 shrink-0",
|
|
104
|
+
"aria-hidden": "true"
|
|
105
|
+
}), /* @__PURE__ */ u("span", { children: D.problem("playback-blocked") })]
|
|
106
|
+
}) : null
|
|
107
|
+
]
|
|
108
|
+
})
|
|
109
|
+
}),
|
|
110
|
+
/* @__PURE__ */ u("p", {
|
|
111
|
+
className: H === null ? "sr-only" : "text-center text-sm text-text-secondary",
|
|
112
|
+
role: "status",
|
|
113
|
+
"data-testid": "assistant-avatar-tab-status",
|
|
114
|
+
children: H ?? ""
|
|
115
|
+
}),
|
|
116
|
+
F.avatars.length > 1 && !R ? /* @__PURE__ */ d("fieldset", {
|
|
117
|
+
className: "flex min-w-0 flex-col gap-1.5",
|
|
118
|
+
"data-testid": "assistant-avatar-tab-chooser",
|
|
119
|
+
children: [/* @__PURE__ */ u("legend", {
|
|
120
|
+
className: v,
|
|
121
|
+
children: D.chooserTitle
|
|
122
|
+
}), /* @__PURE__ */ u("div", {
|
|
123
|
+
className: "flex flex-wrap gap-2",
|
|
124
|
+
children: F.avatars.map((e) => {
|
|
125
|
+
let t = e === U, n = W(e);
|
|
126
|
+
return /* @__PURE__ */ d("label", {
|
|
127
|
+
className: `${oe} ${t ? "border-transparent bg-action-primary-bg text-action-primary-text" : "border-border-default bg-bg-elevated text-text-secondary hover:bg-bg-raised hover:text-text-primary"}`,
|
|
128
|
+
title: t ? D.chooserSelected(n) : D.chooserOption(n),
|
|
129
|
+
children: [/* @__PURE__ */ u("input", {
|
|
130
|
+
type: "radio",
|
|
131
|
+
name: A,
|
|
132
|
+
value: e,
|
|
133
|
+
checked: t,
|
|
134
|
+
onChange: () => r.selectAvatar(e),
|
|
135
|
+
className: se,
|
|
136
|
+
"data-avatar-id": e
|
|
137
|
+
}), /* @__PURE__ */ u("span", {
|
|
138
|
+
className: "truncate",
|
|
139
|
+
children: n
|
|
140
|
+
})]
|
|
141
|
+
}, e);
|
|
142
|
+
})
|
|
143
|
+
})]
|
|
144
|
+
}) : F.avatars.length > 0 && U !== null ? /* @__PURE__ */ d("div", {
|
|
145
|
+
className: "flex flex-col gap-0.5",
|
|
146
|
+
"data-testid": "assistant-avatar-tab-chosen",
|
|
147
|
+
children: [/* @__PURE__ */ d("p", {
|
|
148
|
+
className: "flex min-w-0 items-baseline gap-2 text-sm",
|
|
149
|
+
children: [/* @__PURE__ */ u("span", {
|
|
150
|
+
className: v,
|
|
151
|
+
children: D.chooserLabel
|
|
152
|
+
}), /* @__PURE__ */ u("span", {
|
|
153
|
+
className: "truncate text-text-primary",
|
|
154
|
+
children: W(U)
|
|
155
|
+
})]
|
|
156
|
+
}), R && F.avatars.length > 1 ? /* @__PURE__ */ u("p", {
|
|
157
|
+
className: y,
|
|
158
|
+
children: D.chooserLocked
|
|
159
|
+
}) : null]
|
|
160
|
+
}) : L === "choosing" ? /* @__PURE__ */ u("p", {
|
|
161
|
+
className: y,
|
|
162
|
+
"data-testid": "assistant-avatar-tab-empty",
|
|
163
|
+
children: D.chooserEmpty
|
|
164
|
+
}) : null,
|
|
165
|
+
/* @__PURE__ */ d("div", {
|
|
166
|
+
className: "flex flex-col gap-1",
|
|
167
|
+
children: [
|
|
168
|
+
/* @__PURE__ */ u("label", {
|
|
169
|
+
htmlFor: j,
|
|
170
|
+
className: v,
|
|
171
|
+
children: D.voiceModeLabel
|
|
172
|
+
}),
|
|
173
|
+
/* @__PURE__ */ d("div", {
|
|
174
|
+
className: "flex items-center gap-2",
|
|
175
|
+
children: [/* @__PURE__ */ d("select", {
|
|
176
|
+
id: j,
|
|
177
|
+
value: X,
|
|
178
|
+
onChange: (e) => _e(e.target.value),
|
|
179
|
+
"aria-describedby": M,
|
|
180
|
+
className: `min-h-11 min-w-0 flex-1 rounded-xl border border-border-default bg-bg-elevated px-3 text-sm text-text-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary ${b}`,
|
|
181
|
+
"data-testid": "assistant-avatar-tab-voice",
|
|
182
|
+
children: [
|
|
183
|
+
/* @__PURE__ */ u("option", {
|
|
184
|
+
value: f,
|
|
185
|
+
children: ge
|
|
186
|
+
}),
|
|
187
|
+
Y === null ? null : /* @__PURE__ */ u("option", {
|
|
188
|
+
value: p,
|
|
189
|
+
children: D.voiceModeDefaultNamed(Y)
|
|
190
|
+
}),
|
|
191
|
+
e.map((e) => /* @__PURE__ */ u("option", {
|
|
192
|
+
value: `${m}${e}`,
|
|
193
|
+
children: e
|
|
194
|
+
}, e))
|
|
195
|
+
]
|
|
196
|
+
}), /* @__PURE__ */ u("button", {
|
|
197
|
+
type: "button",
|
|
198
|
+
onClick: () => {
|
|
199
|
+
$ && Q !== null && O.previewVoice(Q);
|
|
200
|
+
},
|
|
201
|
+
"aria-disabled": !$,
|
|
202
|
+
"aria-label": D.hearVoice,
|
|
203
|
+
title: D.hearVoice,
|
|
204
|
+
className: `inline-flex size-11 shrink-0 items-center justify-center rounded-xl border border-border-default bg-bg-elevated text-text-secondary transition-colors hover:bg-bg-raised hover:text-text-primary aria-disabled:cursor-default aria-disabled:opacity-60 ${b}`,
|
|
205
|
+
"data-testid": "assistant-avatar-tab-hear",
|
|
206
|
+
children: /* @__PURE__ */ u(l, {
|
|
207
|
+
className: "size-5",
|
|
208
|
+
"aria-hidden": "true"
|
|
209
|
+
})
|
|
210
|
+
})]
|
|
211
|
+
}),
|
|
212
|
+
/* @__PURE__ */ u("p", {
|
|
213
|
+
id: M,
|
|
214
|
+
className: y,
|
|
215
|
+
children: D.voiceModeHint
|
|
216
|
+
})
|
|
217
|
+
]
|
|
218
|
+
}),
|
|
219
|
+
R ? null : /* @__PURE__ */ u("p", {
|
|
220
|
+
id: k,
|
|
221
|
+
className: "text-xs text-text-secondary",
|
|
222
|
+
"data-testid": "assistant-avatar-tab-disclosure",
|
|
223
|
+
children: D.consent
|
|
224
|
+
}),
|
|
225
|
+
G === null ? null : /* @__PURE__ */ u("button", {
|
|
226
|
+
ref: pe,
|
|
227
|
+
type: "button",
|
|
228
|
+
onClick: me,
|
|
229
|
+
"aria-disabled": G.kind === "turn-on" ? !G.ready : G.kind !== "turn-off",
|
|
230
|
+
"aria-describedby": G.kind === "turn-on" ? k : void 0,
|
|
231
|
+
className: G.kind === "turn-off" ? x : ae,
|
|
232
|
+
"data-testid": "assistant-avatar-tab-switch",
|
|
233
|
+
"data-slot": G.kind,
|
|
234
|
+
children: G.kind === "turn-off" ? D.stop : G.kind === "starting" ? D.turnOnStarting : G.kind === "ending" ? D.stateEnding : D.consentConfirm
|
|
235
|
+
}),
|
|
236
|
+
ye ? /* @__PURE__ */ u("button", {
|
|
237
|
+
type: "button",
|
|
238
|
+
onClick: () => r.requestOn({ start: !1 }),
|
|
239
|
+
className: x,
|
|
240
|
+
"data-testid": "assistant-avatar-tab-retry",
|
|
241
|
+
children: D.chooserRetry
|
|
242
|
+
}) : null,
|
|
243
|
+
S.live ? null : /* @__PURE__ */ d("div", {
|
|
244
|
+
className: "flex flex-col gap-1",
|
|
245
|
+
children: [/* @__PURE__ */ u("button", {
|
|
246
|
+
ref: w,
|
|
247
|
+
type: "button",
|
|
248
|
+
onClick: () => {
|
|
249
|
+
S.blocked === null && S.start();
|
|
250
|
+
},
|
|
251
|
+
"aria-disabled": S.blocked !== null,
|
|
252
|
+
"aria-describedby": S.blocked === null ? void 0 : N,
|
|
253
|
+
className: x,
|
|
254
|
+
"data-testid": "assistant-avatar-tab-start-voice",
|
|
255
|
+
children: D.startVoice
|
|
256
|
+
}), S.blocked === null ? null : /* @__PURE__ */ u("p", {
|
|
257
|
+
id: N,
|
|
258
|
+
className: y,
|
|
259
|
+
"data-testid": "assistant-avatar-tab-voice-blocked",
|
|
260
|
+
children: S.impossible && !R ? `${S.blocked} ${D.askNeedsVoice}` : S.blocked
|
|
261
|
+
})]
|
|
262
|
+
}),
|
|
263
|
+
C && !F.voiceLive ? /* @__PURE__ */ u("p", {
|
|
264
|
+
className: y,
|
|
265
|
+
"data-testid": "assistant-avatar-tab-hands-free",
|
|
266
|
+
children: D.handsFreeOff
|
|
267
|
+
}) : null,
|
|
268
|
+
R && !F.heldByVoice ? /* @__PURE__ */ u("p", {
|
|
269
|
+
id: ce,
|
|
270
|
+
className: y,
|
|
271
|
+
"data-testid": "assistant-avatar-tab-leave-hint",
|
|
272
|
+
children: D.tabLeaveHint
|
|
273
|
+
}) : null
|
|
274
|
+
]
|
|
275
|
+
});
|
|
276
|
+
};
|
|
277
|
+
//#endregion
|
|
278
|
+
export { S as LiveAvatarTabView, _ as avatarPreviewVoice, g as avatarVoiceDisplayName };
|
|
@@ -99,6 +99,11 @@ export interface AvatarRefusal {
|
|
|
99
99
|
readonly hide: boolean;
|
|
100
100
|
/** The server's disclosure version changed (`consent_required`): ask availability again, show the disclosure to confirm again. */
|
|
101
101
|
readonly showDisclosure?: true;
|
|
102
|
+
/**
|
|
103
|
+
* A "not now" refusal (busy, started too quickly, the avatar time used up): when the server said when to try again (its Retry-After),
|
|
104
|
+
* in seconds. Absent when it said nothing: the person is then told no time, and nothing waits for one.
|
|
105
|
+
*/
|
|
106
|
+
readonly retryAfterSeconds?: number;
|
|
102
107
|
}
|
|
103
108
|
/**
|
|
104
109
|
* The person's side of every failure (§3.4): the normal voice always continues; this says what to show. 404 means "not here" (the module
|
|
@@ -224,8 +224,24 @@ function y(e) {
|
|
|
224
224
|
}
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
var b = new Set([
|
|
228
|
+
"capacity",
|
|
229
|
+
"rate-limited",
|
|
230
|
+
"budget-exhausted"
|
|
231
|
+
]);
|
|
232
|
+
function x(e) {
|
|
233
|
+
if (!(e instanceof s)) return {
|
|
234
|
+
problem: "connection",
|
|
235
|
+
hide: !1
|
|
236
|
+
};
|
|
237
|
+
let t = S(e);
|
|
238
|
+
return t.problem !== null && b.has(t.problem) && e.retryAfterSeconds !== null ? {
|
|
239
|
+
...t,
|
|
240
|
+
retryAfterSeconds: e.retryAfterSeconds
|
|
241
|
+
} : t;
|
|
242
|
+
}
|
|
243
|
+
function S(e) {
|
|
244
|
+
if (e.status === 0) return {
|
|
229
245
|
problem: "connection",
|
|
230
246
|
hide: !1
|
|
231
247
|
};
|
|
@@ -274,11 +290,14 @@ function b(e) {
|
|
|
274
290
|
problem: "start-timeout",
|
|
275
291
|
hide: !1
|
|
276
292
|
};
|
|
277
|
-
case "avatar_paused":
|
|
278
|
-
case "budget_exhausted": return {
|
|
293
|
+
case "avatar_paused": return {
|
|
279
294
|
problem: "paused",
|
|
280
295
|
hide: !1
|
|
281
296
|
};
|
|
297
|
+
case "budget_exhausted": return {
|
|
298
|
+
problem: "budget-exhausted",
|
|
299
|
+
hide: !1
|
|
300
|
+
};
|
|
282
301
|
case "avatar_unavailable": return {
|
|
283
302
|
problem: "unavailable",
|
|
284
303
|
hide: !1
|
|
@@ -309,7 +328,7 @@ function b(e) {
|
|
|
309
328
|
};
|
|
310
329
|
}
|
|
311
330
|
}
|
|
312
|
-
function
|
|
331
|
+
function C(e) {
|
|
313
332
|
return e === "unavailable" ? {
|
|
314
333
|
problem: "capacity",
|
|
315
334
|
hide: !1
|
|
@@ -319,4 +338,4 @@ function x(e) {
|
|
|
319
338
|
};
|
|
320
339
|
}
|
|
321
340
|
//#endregion
|
|
322
|
-
export { e as AVATAR_API_PATH, s as AvatarApiError, a as avatarApiBaseFromTtsEndpoint,
|
|
341
|
+
export { e as AVATAR_API_PATH, s as AvatarApiError, a as avatarApiBaseFromTtsEndpoint, C as avatarAvailabilityRefusal, c as avatarIdempotencyKey, x as avatarRefusalFor, y as createAvatarSessionClient };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { HostedSpeaker } from '../../speech/hostedVoiceCatalogue';
|
|
2
|
+
import { SpeechAudioOutput, SpeechVoiceLike } from '../../speech/types';
|
|
3
|
+
import { AvatarVoiceDefault, AvatarVoicePreference } from '../avatarPreferences';
|
|
4
|
+
/** The host's speaker per avatar id, validated (`liveAvatarVoicesFrom`). */
|
|
5
|
+
export type LiveAvatarVoices = ReadonlyMap<string, HostedSpeaker>;
|
|
6
|
+
/**
|
|
7
|
+
* The host's `avatarVoices` as this module reads them: only plausible avatar ids, each with a speaker the hosted tier offers. A host's
|
|
8
|
+
* JavaScript can pass anything, and a Map keeps an id such as `constructor` from reading an object's own machinery.
|
|
9
|
+
*/
|
|
10
|
+
export declare function liveAvatarVoicesFrom(input: unknown): LiveAvatarVoices;
|
|
11
|
+
/** Everything the speaker depends on besides the person's choice. */
|
|
12
|
+
export interface AvatarSpeakerContext {
|
|
13
|
+
/** The host's default for a person who has not chosen (`createLiveAvatarSeam`'s `defaultAvatarVoiceMode`). */
|
|
14
|
+
readonly hostDefault?: AvatarVoiceDefault;
|
|
15
|
+
readonly avatarVoices: LiveAvatarVoices;
|
|
16
|
+
/** The avatar the reply goes to: the one the person started. */
|
|
17
|
+
readonly avatarId: string | null;
|
|
18
|
+
}
|
|
19
|
+
/** The speaker the avatar's replies use now, or null: the assistant's own voice. */
|
|
20
|
+
export declare function avatarSpeakerFor(preference: AvatarVoicePreference, context: AvatarSpeakerContext): HostedSpeaker | null;
|
|
21
|
+
/**
|
|
22
|
+
* `speaker` in the language of a reply (`lang`, the app's tag), as the engine lists it; null when the vendor has no voice for that
|
|
23
|
+
* language, or the engine does not offer it now (a hosted engine latched off lists no hosted voice). Null keeps the assistant's voice.
|
|
24
|
+
*/
|
|
25
|
+
export declare function hostedAvatarVoice(speaker: HostedSpeaker, lang: string, voices: readonly SpeechVoiceLike[]): SpeechVoiceLike | null;
|
|
26
|
+
/**
|
|
27
|
+
* ★★ The avatar left mid-reply (switched off, its session ended, or its output failed): the rest of that reply plays on the speakers
|
|
28
|
+
* (`SpeechAudioOutput`'s leave rule), and must not go on in the AVATAR's voice there. `restore` is called once, the moment the avatar
|
|
29
|
+
* stops being the reply's destination, so every piece synthesised AFTER it is asked for in the assistant's voice. That moment is the
|
|
30
|
+
* first of:
|
|
31
|
+
* - `leaves` reporting it: the session the reply went to is no longer ready (the controller's own state, which changes in the same
|
|
32
|
+
* call that ends the session, before the output answers anything);
|
|
33
|
+
* - the output rejecting a `write` or `drain` (a leave, a failure, or a refusal, after which nothing plays at all).
|
|
34
|
+
*
|
|
35
|
+
* What cannot be changed: the pieces already synthesised when the avatar left. The hosted engine never synthesises a piece twice
|
|
36
|
+
* (a fallback replays the bytes it has) and keeps one piece ahead, so at most two pieces are in the avatar's voice: the one handed to the
|
|
37
|
+
* avatar (not played again when it may have been heard there, R4) and the one fetched after it, which plays on the speakers. Every later
|
|
38
|
+
* piece is in the assistant's voice.
|
|
39
|
+
*
|
|
40
|
+
* The reply over (its last `drain` resolved, or `cancel()`), nothing is watched any more. The output's calls are passed through
|
|
41
|
+
* untouched, their promises included: the engine sees exactly what the output said.
|
|
42
|
+
*/
|
|
43
|
+
export declare function restoreVoiceWhenOutputLeaves(output: SpeechAudioOutput, restore: () => void, leaves?: (onLeave: () => void) => () => void): SpeechAudioOutput;
|