@burdenoff/fe-libs 2026.904.5 → 2026.904.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/shared/assistant/index.d.ts +3 -0
- package/dist/shared/assistant/index.d.ts.map +1 -1
- package/dist/shared/assistant/speech/index.js +5 -0
- package/dist/shared/assistant/ui/AssistantSpeechProvider.d.ts +103 -0
- package/dist/shared/assistant/ui/AssistantSpeechProvider.d.ts.map +1 -0
- package/dist/shared/assistant/ui/AssistantSpeechProvider.js +294 -0
- package/dist/shared/assistant/ui/AssistantToolActivity.d.ts +30 -0
- package/dist/shared/assistant/ui/AssistantToolActivity.d.ts.map +1 -0
- package/dist/shared/assistant/ui/AssistantToolActivity.js +108 -0
- package/dist/shared/assistant/ui/useVoiceSession.d.ts +88 -0
- package/dist/shared/assistant/ui/useVoiceSession.d.ts.map +1 -0
- package/dist/shared/assistant/ui/useVoiceSession.js +162 -0
- package/dist/shared-assistant.js +4 -1
- package/package.json +1 -1
|
@@ -69,4 +69,7 @@ export * from './store';
|
|
|
69
69
|
export * from './turnState';
|
|
70
70
|
export * from './gatewayLimits';
|
|
71
71
|
export * from './documentText';
|
|
72
|
+
export * from './ui/AssistantToolActivity';
|
|
73
|
+
export * from './ui/AssistantSpeechProvider';
|
|
74
|
+
export * from './ui/useVoiceSession';
|
|
72
75
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AAGxB,cAAc,wBAAwB,CAAC;AAKvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,gBAAgB,CAAC;AAK/B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAG3C,cAAc,QAAQ,CAAC;AAEvB,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AAGxB,cAAc,wBAAwB,CAAC;AAKvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,gBAAgB,CAAC;AAK/B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAG3C,cAAc,QAAQ,CAAC;AAEvB,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAG/B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { FC, ReactNode } from 'react';
|
|
2
|
+
import { SpeechEngine, SpeechVoiceLike } from '../speech';
|
|
3
|
+
import { VoiceAvailability } from '../voice/speechVoices';
|
|
4
|
+
export interface AssistantSpeechContextValue {
|
|
5
|
+
/** False when the platform has no speech output — hide the control entirely. */
|
|
6
|
+
isSupported: boolean;
|
|
7
|
+
availability: VoiceAvailability;
|
|
8
|
+
/**
|
|
9
|
+
* True when nothing can be spoken in the app language, so every control that
|
|
10
|
+
* starts playback — the per-message button AND the header toggle — must be
|
|
11
|
+
* inert. `pending` is not blocked: see `speechGate`.
|
|
12
|
+
*/
|
|
13
|
+
isBlocked: boolean;
|
|
14
|
+
/** Explains a disabled control (no installed voice for the app language). */
|
|
15
|
+
disabledReason: string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Why THIS message cannot be spoken, when the app language is fine but the
|
|
18
|
+
* reply is not in it — a Tamil answer in an English UI on a device with no
|
|
19
|
+
* Tamil voice. `isBlocked` cannot see that: it is computed before any reply
|
|
20
|
+
* exists. Without this the control looks enabled and silently refuses, which
|
|
21
|
+
* is the dead-button bug all over again (BOFF-7106).
|
|
22
|
+
*/
|
|
23
|
+
blockedFor: (messageId: string) => string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Every installed voice that can read the language the user speaks, best
|
|
26
|
+
* first. Empty when there is no choice to offer — the picker hides itself
|
|
27
|
+
* rather than showing a one-item dropdown.
|
|
28
|
+
*/
|
|
29
|
+
voiceChoices: readonly SpeechVoiceLike[];
|
|
30
|
+
/** The voice that will actually be used, resolved from the pick + ranking. */
|
|
31
|
+
/**
|
|
32
|
+
* The reply as it is being SPOKEN: every chunk, plus the one sounding now.
|
|
33
|
+
*
|
|
34
|
+
* `null` when nothing is playing. `index: -1` means chunked but not yet
|
|
35
|
+
* audible. Consumers reveal `chunks[0..index]` — anything beyond that is the
|
|
36
|
+
* caption running ahead of the voice.
|
|
37
|
+
*/
|
|
38
|
+
spokenChunks: {
|
|
39
|
+
chunks: string[];
|
|
40
|
+
index: number;
|
|
41
|
+
} | null;
|
|
42
|
+
activeVoiceName: string | null;
|
|
43
|
+
/**
|
|
44
|
+
* The RAW stored pick: `null` means "use the ranked default".
|
|
45
|
+
*
|
|
46
|
+
* The picker must be driven by this rather than by `activeVoiceName`, or the
|
|
47
|
+
* two states collapse — with nothing stored, the ranked winner is already the
|
|
48
|
+
* shown option, so choosing it fires no change event and the pick can never
|
|
49
|
+
* be made. The user's voice would then silently move the next time the device
|
|
50
|
+
* installs a higher-ranked one. Same trap as the language selector.
|
|
51
|
+
*/
|
|
52
|
+
preferredVoiceName: string | null;
|
|
53
|
+
/** `null` restores the ranked default for the current language. */
|
|
54
|
+
setVoice: (voiceName: string | null) => void;
|
|
55
|
+
/** The message currently being spoken, if any. */
|
|
56
|
+
speakingMessageId: string | null;
|
|
57
|
+
isSpeaking: (messageId: string) => boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Speak this message, or stop if it is already the one speaking.
|
|
60
|
+
*
|
|
61
|
+
* `turnLocale` is the language the reply was GENERATED under, from the
|
|
62
|
+
* message's own metadata. It matters because script detection cannot separate
|
|
63
|
+
* Latin-script languages, so without it an old French reply would be read
|
|
64
|
+
* with whatever voice the CURRENT preference selects.
|
|
65
|
+
*/
|
|
66
|
+
toggle: (messageId: string, markdown: string, turnLocale?: string) => void;
|
|
67
|
+
stop: () => void;
|
|
68
|
+
autoSpeakEnabled: boolean;
|
|
69
|
+
setAutoSpeak: (enabled: boolean) => void;
|
|
70
|
+
/**
|
|
71
|
+
* Speak replies for the duration of a live voice session, WITHOUT touching the
|
|
72
|
+
* user's saved preference.
|
|
73
|
+
*
|
|
74
|
+
* ★ Live Voice used to force `autoSpeakEnabled` true and restore it on exit.
|
|
75
|
+
* That field is PERSISTED (assistantStore partialize), and the restore only
|
|
76
|
+
* ran on a clean `end()` — so a reload, a closed tab or a backgrounded webview
|
|
77
|
+
* during a session left auto-speak switched ON for the user permanently, in
|
|
78
|
+
* normal typed chat, with the toggle showing a state they never chose. The
|
|
79
|
+
* default was never wrong; the session was overwriting it.
|
|
80
|
+
*
|
|
81
|
+
* Session-scoped React state instead: it cannot outlive the page, so there is
|
|
82
|
+
* nothing to restore and nothing to leak.
|
|
83
|
+
*/
|
|
84
|
+
sessionSpeaking: boolean;
|
|
85
|
+
setSessionSpeaking: (enabled: boolean) => void;
|
|
86
|
+
/** Play a short sample in a named voice. No model call, no message, no tokens. */
|
|
87
|
+
previewVoice: (voiceName: string) => void;
|
|
88
|
+
}
|
|
89
|
+
export declare function useAssistantSpeech(): AssistantSpeechContextValue;
|
|
90
|
+
interface Props {
|
|
91
|
+
children: ReactNode;
|
|
92
|
+
/** Panel visibility. Hiding the panel stops the voice. */
|
|
93
|
+
visible?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Injectable for tests and local experiments. The default routes between the
|
|
96
|
+
* device engine and the hosted one; with hosted TTS unconfigured it IS the
|
|
97
|
+
* device engine, unchanged.
|
|
98
|
+
*/
|
|
99
|
+
engine?: SpeechEngine;
|
|
100
|
+
}
|
|
101
|
+
export declare const AssistantSpeechProvider: FC<Props>;
|
|
102
|
+
export {};
|
|
103
|
+
//# sourceMappingURL=AssistantSpeechProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AssistantSpeechProvider.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/ui/AssistantSpeechProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EASL,KAAK,EAAE,EACP,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAKf,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AA2B/D,OAAO,EAQL,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAc/B,MAAM,WAAW,2BAA2B;IAC1C,gFAAgF;IAChF,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,iBAAiB,CAAC;IAChC;;;;OAIG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;;;;OAMG;IACH,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACjD;;;;OAIG;IACH,YAAY,EAAE,SAAS,eAAe,EAAE,CAAC;IACzC,8EAA8E;IAC9E;;;;;;OAMG;IACH,YAAY,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzD,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;;;;OAQG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,mEAAmE;IACnE,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7C,kDAAkD;IAClD,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAC3C;;;;;;;OAOG;IACH,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3E,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC;;;;;;;;;;;;;OAaG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB,kBAAkB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/C,kFAAkF;IAClF,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AA2BD,wBAAgB,kBAAkB,IAAI,2BAA2B,CAEhE;AAED,UAAU,KAAK;IACb,QAAQ,EAAE,SAAS,CAAC;IACpB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,eAAO,MAAM,uBAAuB,EAAE,EAAE,CAAC,KAAK,CAmpB7C,CAAC"}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { useI18n as e } from "../../providers/shell/I18nProvider.js";
|
|
2
|
+
import { isVoiceTurn as t } from "../voice/turnVisibility.js";
|
|
3
|
+
import { getSpeechEngine as n } from "../speech/createSpeechEngine.js";
|
|
4
|
+
import { VOICE_LIST_TIMEOUT_MS as r, languageDisplayName as i, selectVoiceForLocale as a, speechGate as o, speechLangForLocale as s, voiceAvailability as c, voicesForLocale as l } from "../voice/speechVoices.js";
|
|
5
|
+
import { chunkForSpeech as ee, toSpeakableText as u } from "../voice/speakableText.js";
|
|
6
|
+
import { voicePreviewText as d } from "../voice/voicePreview.js";
|
|
7
|
+
import { findAssistantLanguage as f } from "../voice/languages.js";
|
|
8
|
+
import { resolveLanguagePair as te } from "../voice/languagePair.js";
|
|
9
|
+
import { useAssistantTr as ne } from "../i18n.js";
|
|
10
|
+
import { speechLabelsFor as re } from "../voice/speechLabels.js";
|
|
11
|
+
import { resolveSpeechLocale as ie } from "../voice/replyLanguage.js";
|
|
12
|
+
import { IDLE_PLAYBACK as ae, isSpeakingMessage as p, speechPlaybackReducer as oe } from "../voice/speechPlayback.js";
|
|
13
|
+
import { useAssistantStore as m } from "../store.js";
|
|
14
|
+
import { isTurnInFlight as se } from "../turnState.js";
|
|
15
|
+
import { parseToolActivity as ce } from "./AssistantToolActivity.js";
|
|
16
|
+
import "../speech/index.js";
|
|
17
|
+
import { createContext as h, useCallback as g, useContext as _, useEffect as v, useMemo as y, useReducer as le, useRef as b, useState as x } from "react";
|
|
18
|
+
import { jsx as ue } from "react/jsx-runtime";
|
|
19
|
+
//#region src/shared/assistant/ui/AssistantSpeechProvider.tsx
|
|
20
|
+
var de = {
|
|
21
|
+
codeBlock: "",
|
|
22
|
+
image: "",
|
|
23
|
+
link: "",
|
|
24
|
+
largeTable: "",
|
|
25
|
+
truncated: "",
|
|
26
|
+
emailAt: "at",
|
|
27
|
+
emailDot: "dot",
|
|
28
|
+
nestedItem: ""
|
|
29
|
+
}, S = h({
|
|
30
|
+
isSupported: !1,
|
|
31
|
+
availability: "unsupported",
|
|
32
|
+
isBlocked: !0,
|
|
33
|
+
disabledReason: null,
|
|
34
|
+
blockedFor: () => null,
|
|
35
|
+
voiceChoices: [],
|
|
36
|
+
spokenChunks: null,
|
|
37
|
+
activeVoiceName: null,
|
|
38
|
+
preferredVoiceName: null,
|
|
39
|
+
setVoice: () => {},
|
|
40
|
+
speakingMessageId: null,
|
|
41
|
+
isSpeaking: () => !1,
|
|
42
|
+
toggle: () => {},
|
|
43
|
+
stop: () => {},
|
|
44
|
+
autoSpeakEnabled: !1,
|
|
45
|
+
setAutoSpeak: () => {},
|
|
46
|
+
sessionSpeaking: !1,
|
|
47
|
+
setSessionSpeaking: () => {},
|
|
48
|
+
previewVoice: () => {}
|
|
49
|
+
});
|
|
50
|
+
function C() {
|
|
51
|
+
return _(S);
|
|
52
|
+
}
|
|
53
|
+
var w = ({ children: h, visible: _ = !0, engine: C = n() }) => {
|
|
54
|
+
let w = ne(), { locale: T } = e(), [E, D] = le(oe, ae), [O, k] = x(() => C.listVoices()), [A, fe] = x(!1), [pe, j] = x(() => /* @__PURE__ */ new Map()), M = b(0), [N, P] = x(null), F = b(null), I = b(() => {}), L = g(() => {
|
|
55
|
+
F.current?.(), F.current = null;
|
|
56
|
+
}, []), R = m((e) => e.autoSpeakEnabled), z = m((e) => e.setAutoSpeak), [B, me] = x(!1), he = m((e) => e.activeConversationId), V = y(() => C.isSupported(), [C]);
|
|
57
|
+
v(() => {
|
|
58
|
+
if (!V) return;
|
|
59
|
+
let e = () => {
|
|
60
|
+
k(C.listVoices()), j((e) => e.size === 0 ? e : /* @__PURE__ */ new Map());
|
|
61
|
+
};
|
|
62
|
+
return e(), C.subscribeVoices(e);
|
|
63
|
+
}, [C, V]);
|
|
64
|
+
let H = m((e) => e.assistantLanguage), U = m((e) => e.replyLanguage), W = m((e) => e.preferredVoices), G = m((e) => e.setPreferredVoice), K = y(() => te(H, U, T).reply, [
|
|
65
|
+
H,
|
|
66
|
+
U,
|
|
67
|
+
T
|
|
68
|
+
]), q = K.code, J = g((e) => f(e)?.tag ?? s(e), []), ge = g((e) => f(e)?.rate ?? .9, []), Y = y(() => c(V, O, K.tag, A), [
|
|
69
|
+
V,
|
|
70
|
+
O,
|
|
71
|
+
K,
|
|
72
|
+
A
|
|
73
|
+
]), _e = y(() => ({
|
|
74
|
+
codeBlock: w("speech.codeBlock", "Code block"),
|
|
75
|
+
image: w("speech.image", "image"),
|
|
76
|
+
link: w("speech.link", "link"),
|
|
77
|
+
largeTable: w("speech.largeTable", "Table with {{rows}} rows and {{columns}} columns"),
|
|
78
|
+
truncated: w("speech.truncated", "The rest of this message is not read aloud"),
|
|
79
|
+
nestedItem: w("speech.nestedItem", "Sub-item {{ordinal}}"),
|
|
80
|
+
emailAt: w("speech.emailAt", "at"),
|
|
81
|
+
emailDot: w("speech.emailDot", "dot")
|
|
82
|
+
}), [w]), X = g(() => {
|
|
83
|
+
L(), C.cancel(), D({ type: "stop" });
|
|
84
|
+
}, [L, C]), ve = g((e) => {
|
|
85
|
+
let t = C.listVoices().find((t) => t.name === e) ?? null;
|
|
86
|
+
t && (X(), C.speak([d(q)], {
|
|
87
|
+
voice: t,
|
|
88
|
+
lang: J(q)
|
|
89
|
+
}));
|
|
90
|
+
}, [
|
|
91
|
+
C,
|
|
92
|
+
q,
|
|
93
|
+
X,
|
|
94
|
+
J
|
|
95
|
+
]), ye = g((e) => pe.get(e) ?? null, [pe]), be = g((e, t, n) => {
|
|
96
|
+
L();
|
|
97
|
+
let i = !1, a = () => {}, o = (r) => {
|
|
98
|
+
if (!i) {
|
|
99
|
+
if (i = !0, F.current = null, clearTimeout(s), a(), r.length === 0) {
|
|
100
|
+
fe(!0);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
k(r), I.current(e, t, r, n);
|
|
104
|
+
}
|
|
105
|
+
}, s = setTimeout(() => o(C.listVoices()), r);
|
|
106
|
+
a = C.subscribeVoices(() => o(C.listVoices())), F.current = () => {
|
|
107
|
+
i || (i = !0, clearTimeout(s), a(), F.current = null);
|
|
108
|
+
};
|
|
109
|
+
}, [L, C]), Z = g((e, t, n, r) => {
|
|
110
|
+
let s = n;
|
|
111
|
+
if (s.length === 0) {
|
|
112
|
+
let e = C.listVoices();
|
|
113
|
+
e.length > 0 && (s = e, k(e));
|
|
114
|
+
}
|
|
115
|
+
let l = J(r), u = o(c(!0, s, r, A));
|
|
116
|
+
if (u === "block") {
|
|
117
|
+
let t = w("speech.noVoiceForReply", "This reply is in {{language}}, and no {{language}} voice is installed on this device.", { language: i(l, T) });
|
|
118
|
+
j((n) => new Map(n).set(e, t));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (u === "wait-for-voices") {
|
|
122
|
+
be(e, t, r);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
j((t) => {
|
|
126
|
+
if (!t.has(e)) return t;
|
|
127
|
+
let n = new Map(t);
|
|
128
|
+
return n.delete(e), n;
|
|
129
|
+
});
|
|
130
|
+
let d = M.current + 1;
|
|
131
|
+
M.current = d, D({
|
|
132
|
+
type: "start",
|
|
133
|
+
messageId: e,
|
|
134
|
+
requestId: d
|
|
135
|
+
});
|
|
136
|
+
let f = ee(t);
|
|
137
|
+
P({
|
|
138
|
+
requestId: d,
|
|
139
|
+
chunks: f,
|
|
140
|
+
index: -1
|
|
141
|
+
}), C.speak(f, {
|
|
142
|
+
voice: a(s, l, W[r]),
|
|
143
|
+
lang: l,
|
|
144
|
+
rate: ge(r)
|
|
145
|
+
}, {
|
|
146
|
+
onChunkStart: (e) => P((t) => t?.requestId === d ? {
|
|
147
|
+
...t,
|
|
148
|
+
index: e
|
|
149
|
+
} : t),
|
|
150
|
+
onEnd: () => D({
|
|
151
|
+
type: "finished",
|
|
152
|
+
requestId: d
|
|
153
|
+
}),
|
|
154
|
+
onError: () => D({
|
|
155
|
+
type: "failed",
|
|
156
|
+
requestId: d
|
|
157
|
+
})
|
|
158
|
+
});
|
|
159
|
+
}, [
|
|
160
|
+
C,
|
|
161
|
+
T,
|
|
162
|
+
W,
|
|
163
|
+
ge,
|
|
164
|
+
J,
|
|
165
|
+
w,
|
|
166
|
+
A,
|
|
167
|
+
be
|
|
168
|
+
]);
|
|
169
|
+
v(() => {
|
|
170
|
+
I.current = Z;
|
|
171
|
+
}, [Z]);
|
|
172
|
+
let Q = g((e, t, n) => {
|
|
173
|
+
if (!V) return;
|
|
174
|
+
let r = ie(u(t, { labels: de }), n ?? q), i = u(t, { labels: re(r, _e) });
|
|
175
|
+
i && Z(e, i, O, r);
|
|
176
|
+
}, [
|
|
177
|
+
V,
|
|
178
|
+
_e,
|
|
179
|
+
q,
|
|
180
|
+
Z,
|
|
181
|
+
O
|
|
182
|
+
]), xe = g((e, t, n) => {
|
|
183
|
+
if (p(E, e)) {
|
|
184
|
+
X();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
Q(e, t, n);
|
|
188
|
+
}, [
|
|
189
|
+
E,
|
|
190
|
+
Q,
|
|
191
|
+
X
|
|
192
|
+
]);
|
|
193
|
+
v(() => {
|
|
194
|
+
_ || X();
|
|
195
|
+
}, [_, X]), v(() => {
|
|
196
|
+
X(), j(/* @__PURE__ */ new Map());
|
|
197
|
+
}, [he, X]), v(() => {
|
|
198
|
+
j((e) => e.size === 0 ? e : /* @__PURE__ */ new Map()), X();
|
|
199
|
+
}, [q, X]), v(() => () => {
|
|
200
|
+
F.current?.(), F.current = null, C.cancel();
|
|
201
|
+
}, [C]), v(() => {
|
|
202
|
+
if (typeof window > "u") return;
|
|
203
|
+
let e = () => C.cancel();
|
|
204
|
+
return window.addEventListener("pagehide", e), () => window.removeEventListener("pagehide", e);
|
|
205
|
+
}, [C]), v(() => {
|
|
206
|
+
if (typeof document > "u" || !V) return;
|
|
207
|
+
let e = () => {
|
|
208
|
+
if (document.visibilityState !== "visible") return;
|
|
209
|
+
let e = C.listVoices();
|
|
210
|
+
e.length > 0 && L(), k(e), fe(!1), j((e) => e.size === 0 ? e : /* @__PURE__ */ new Map());
|
|
211
|
+
};
|
|
212
|
+
return document.addEventListener("visibilitychange", e), window.addEventListener("focus", e), () => {
|
|
213
|
+
document.removeEventListener("visibilitychange", e), window.removeEventListener("focus", e);
|
|
214
|
+
};
|
|
215
|
+
}, [
|
|
216
|
+
L,
|
|
217
|
+
C,
|
|
218
|
+
V
|
|
219
|
+
]);
|
|
220
|
+
let Se = b(Q);
|
|
221
|
+
v(() => {
|
|
222
|
+
Se.current = Q;
|
|
223
|
+
}, [Q]), v(() => {
|
|
224
|
+
if (!(!R && !B || !_ || !V)) return m.subscribe((e, n) => {
|
|
225
|
+
let r = e.activeConversationId;
|
|
226
|
+
if (!r || r !== n.activeConversationId) return;
|
|
227
|
+
let i = e.processingByConversationId[r], a = n.processingByConversationId[r];
|
|
228
|
+
if (i?.status !== "complete" || !se(a?.status)) return;
|
|
229
|
+
let o = i.messageId ?? a?.messageId ?? null;
|
|
230
|
+
if (!o) return;
|
|
231
|
+
let s = e.conversations.find((e) => e.id === r)?.messages.find((e) => e.id === o);
|
|
232
|
+
if (!s || s.role !== "assistant" || t(s) !== B) return;
|
|
233
|
+
let { text: c } = ce(s.content);
|
|
234
|
+
c.trim() && Se.current(o, c, s.metadata?.lang);
|
|
235
|
+
});
|
|
236
|
+
}, [
|
|
237
|
+
R,
|
|
238
|
+
B,
|
|
239
|
+
V,
|
|
240
|
+
_
|
|
241
|
+
]);
|
|
242
|
+
let Ce = y(() => l(O, K.tag), [O, K]), $ = y(() => a(O, K.tag, W[q]), [
|
|
243
|
+
O,
|
|
244
|
+
K,
|
|
245
|
+
W,
|
|
246
|
+
q
|
|
247
|
+
]), we = y(() => ({
|
|
248
|
+
isSupported: V,
|
|
249
|
+
availability: Y,
|
|
250
|
+
isBlocked: Y === "no-voices",
|
|
251
|
+
disabledReason: Y === "no-voices" ? w("speech.noVoicesOnDevice", "This device has no speech voices installed. Add one in your device speech or accessibility settings.") : null,
|
|
252
|
+
blockedFor: ye,
|
|
253
|
+
voiceChoices: Ce,
|
|
254
|
+
spokenChunks: N ? {
|
|
255
|
+
chunks: N.chunks,
|
|
256
|
+
index: N.index
|
|
257
|
+
} : null,
|
|
258
|
+
activeVoiceName: $?.name ?? null,
|
|
259
|
+
preferredVoiceName: W[q] ?? null,
|
|
260
|
+
setVoice: (e) => G(q, e),
|
|
261
|
+
speakingMessageId: E.status === "speaking" ? E.messageId : null,
|
|
262
|
+
isSpeaking: (e) => p(E, e),
|
|
263
|
+
toggle: xe,
|
|
264
|
+
stop: X,
|
|
265
|
+
autoSpeakEnabled: R,
|
|
266
|
+
setAutoSpeak: z,
|
|
267
|
+
sessionSpeaking: B,
|
|
268
|
+
setSessionSpeaking: me,
|
|
269
|
+
previewVoice: ve
|
|
270
|
+
}), [
|
|
271
|
+
V,
|
|
272
|
+
Y,
|
|
273
|
+
ye,
|
|
274
|
+
Ce,
|
|
275
|
+
$,
|
|
276
|
+
W,
|
|
277
|
+
G,
|
|
278
|
+
q,
|
|
279
|
+
E,
|
|
280
|
+
xe,
|
|
281
|
+
X,
|
|
282
|
+
R,
|
|
283
|
+
z,
|
|
284
|
+
B,
|
|
285
|
+
ve,
|
|
286
|
+
w
|
|
287
|
+
]);
|
|
288
|
+
return /* @__PURE__ */ ue(S.Provider, {
|
|
289
|
+
value: we,
|
|
290
|
+
children: h
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
//#endregion
|
|
294
|
+
export { w as AssistantSpeechProvider, C as useAssistantSpeech };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
export type AssistantToolStepStatus = "queued" | "running" | "completed" | "failed";
|
|
3
|
+
export interface AssistantToolStep {
|
|
4
|
+
/** Stable key within the message. */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Humanised label shown in the step row. */
|
|
7
|
+
label: string;
|
|
8
|
+
/** Raw tool name (surfaced in the collapsed "Details"). */
|
|
9
|
+
tool: string;
|
|
10
|
+
status: AssistantToolStepStatus;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Split streamed assistant content into its tool-progress steps and the
|
|
14
|
+
* remaining prose. Called by AssistantMessageItem so the tool lines render as an
|
|
15
|
+
* AssistantToolActivity card while the rest renders as markdown.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseToolActivity(content: string): {
|
|
18
|
+
steps: AssistantToolStep[];
|
|
19
|
+
text: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* AssistantToolActivity — compact progress card for a turn's tool run (per the
|
|
23
|
+
* ai-assistant-v2 "tool-progress" mockup). Renders each step in one of four
|
|
24
|
+
* states (queued / running / completed / failed) with a hollow circle, spinner,
|
|
25
|
+
* green check or red x, plus a collapsed "Details" listing the raw tool names.
|
|
26
|
+
*/
|
|
27
|
+
export declare const AssistantToolActivity: FC<{
|
|
28
|
+
steps: AssistantToolStep[];
|
|
29
|
+
}>;
|
|
30
|
+
//# sourceMappingURL=AssistantToolActivity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AssistantToolActivity.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/ui/AssistantToolActivity.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAIhC,MAAM,MAAM,uBAAuB,GAC/B,QAAQ,GACR,SAAS,GACT,WAAW,GACX,QAAQ,CAAC;AAEb,MAAM,WAAW,iBAAiB;IAChC,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,uBAAuB,CAAC;CACjC;AAsBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG;IAClD,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd,CAsBA;AA2BD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,EAAE,CAAC;IAAE,KAAK,EAAE,iBAAiB,EAAE,CAAA;CAAE,CAiEpE,CAAC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useAssistantTr as e } from "../i18n.js";
|
|
2
|
+
import { Check as t, Circle as n, Loader2 as r, Wrench as i, X as a } from "lucide-react";
|
|
3
|
+
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
4
|
+
//#region src/shared/assistant/ui/AssistantToolActivity.tsx
|
|
5
|
+
var c = /^(Running|Completed|Failed):\s*(.+)$/, l = {
|
|
6
|
+
Running: "running",
|
|
7
|
+
Completed: "completed",
|
|
8
|
+
Failed: "failed"
|
|
9
|
+
};
|
|
10
|
+
function u(e) {
|
|
11
|
+
let t = e.replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
12
|
+
return t ? t.charAt(0).toUpperCase() + t.slice(1) : e;
|
|
13
|
+
}
|
|
14
|
+
function d(e) {
|
|
15
|
+
if (!e.includes(":")) return {
|
|
16
|
+
steps: [],
|
|
17
|
+
text: e
|
|
18
|
+
};
|
|
19
|
+
let t = [], n = [];
|
|
20
|
+
return e.split("\n").forEach((e, r) => {
|
|
21
|
+
let i = e.match(c);
|
|
22
|
+
if (i) {
|
|
23
|
+
let e = i[2].trim();
|
|
24
|
+
t.push({
|
|
25
|
+
id: `${r}-${e}`,
|
|
26
|
+
label: u(e),
|
|
27
|
+
tool: e,
|
|
28
|
+
status: l[i[1]] ?? "running"
|
|
29
|
+
});
|
|
30
|
+
} else n.push(e);
|
|
31
|
+
}), {
|
|
32
|
+
steps: t,
|
|
33
|
+
text: n.join("\n").trim()
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function f({ status: e }) {
|
|
37
|
+
switch (e) {
|
|
38
|
+
case "completed": return /* @__PURE__ */ o(t, {
|
|
39
|
+
className: "size-3.5 text-status-success-text",
|
|
40
|
+
"aria-hidden": "true"
|
|
41
|
+
});
|
|
42
|
+
case "failed": return /* @__PURE__ */ o(a, {
|
|
43
|
+
className: "size-3.5 text-status-error-text",
|
|
44
|
+
"aria-hidden": "true"
|
|
45
|
+
});
|
|
46
|
+
case "running": return /* @__PURE__ */ o(r, {
|
|
47
|
+
className: "size-3.5 animate-spin text-action-primary-bg",
|
|
48
|
+
"aria-hidden": "true"
|
|
49
|
+
});
|
|
50
|
+
default: return /* @__PURE__ */ o(n, {
|
|
51
|
+
className: "size-3.5 text-text-muted",
|
|
52
|
+
"aria-hidden": "true"
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
var p = ({ steps: t }) => {
|
|
57
|
+
let n = e();
|
|
58
|
+
if (t.length === 0) return null;
|
|
59
|
+
let r = t.some((e) => e.status === "failed"), a = t.some((e) => e.status === "running" || e.status === "queued"), c = r ? n("tool.failed", "Some steps failed") : a ? n("tool.working", "Working on it…") : n("tool.done", "Done");
|
|
60
|
+
return /* @__PURE__ */ s("div", {
|
|
61
|
+
className: "my-1.5 rounded-xl border border-border-subtle bg-bg-sunken px-3 py-2.5",
|
|
62
|
+
"data-testid": "assistant-tool-activity",
|
|
63
|
+
children: [
|
|
64
|
+
/* @__PURE__ */ s("div", {
|
|
65
|
+
className: "flex items-center gap-1.5 text-[12px] font-medium text-text-secondary",
|
|
66
|
+
children: [/* @__PURE__ */ o(i, {
|
|
67
|
+
className: "size-3.5 text-text-muted",
|
|
68
|
+
"aria-hidden": "true"
|
|
69
|
+
}), /* @__PURE__ */ o("span", { children: c })]
|
|
70
|
+
}),
|
|
71
|
+
/* @__PURE__ */ o("ul", {
|
|
72
|
+
className: "mt-2 space-y-1.5",
|
|
73
|
+
children: t.map((e) => /* @__PURE__ */ s("li", {
|
|
74
|
+
className: "flex items-center gap-2 text-[13px] text-text-primary",
|
|
75
|
+
children: [/* @__PURE__ */ o("span", {
|
|
76
|
+
className: "flex size-3.5 flex-shrink-0 items-center justify-center",
|
|
77
|
+
children: /* @__PURE__ */ o(f, { status: e.status })
|
|
78
|
+
}), /* @__PURE__ */ o("span", {
|
|
79
|
+
className: e.status === "completed" ? "text-text-secondary" : "",
|
|
80
|
+
children: e.label
|
|
81
|
+
})]
|
|
82
|
+
}, e.id))
|
|
83
|
+
}),
|
|
84
|
+
/* @__PURE__ */ s("details", {
|
|
85
|
+
className: "group mt-2",
|
|
86
|
+
children: [/* @__PURE__ */ o("summary", {
|
|
87
|
+
className: "cursor-pointer list-none text-[11px] text-text-muted transition-colors hover:text-text-secondary",
|
|
88
|
+
children: n("tool.details", "Details")
|
|
89
|
+
}), /* @__PURE__ */ o("ul", {
|
|
90
|
+
className: "mt-1.5 space-y-0.5 border-t border-border-subtle pt-1.5",
|
|
91
|
+
children: t.map((e) => /* @__PURE__ */ s("li", {
|
|
92
|
+
className: "font-mono text-[11px] text-text-muted",
|
|
93
|
+
children: [
|
|
94
|
+
e.status,
|
|
95
|
+
/* @__PURE__ */ o("span", {
|
|
96
|
+
"aria-hidden": "true",
|
|
97
|
+
children: " · "
|
|
98
|
+
}),
|
|
99
|
+
e.tool
|
|
100
|
+
]
|
|
101
|
+
}, `raw-${e.id}`))
|
|
102
|
+
})]
|
|
103
|
+
})
|
|
104
|
+
]
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
//#endregion
|
|
108
|
+
export { p as AssistantToolActivity, d as parseToolActivity };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { VoiceSessionSnapshot } from '../voice/voiceSession';
|
|
2
|
+
/**
|
|
3
|
+
* A problem worth TELLING the user about that does not warrant killing the
|
|
4
|
+
* session.
|
|
5
|
+
*
|
|
6
|
+
* These exist because the previous behaviour was the failure mode this whole
|
|
7
|
+
* feature keeps hitting: the overlay said "Listening…" while the recogniser was
|
|
8
|
+
* dead, and nothing anywhere said otherwise. `useVoiceInput` reports `network`
|
|
9
|
+
* and `unknown` and the session ignored both.
|
|
10
|
+
*/
|
|
11
|
+
export type VoiceSessionNotice = "network" | "not-capturing" | "nothing-heard" | "recognition-failed";
|
|
12
|
+
interface Options {
|
|
13
|
+
/** Send a turn. The reply is observed via `latestReply`, not returned here. */
|
|
14
|
+
/**
|
|
15
|
+
* Send a turn.
|
|
16
|
+
*
|
|
17
|
+
* ★ The session id is handed over HERE rather than exposed for the parent to
|
|
18
|
+
* read, so the value that stamps the turn is the one live at the instant it
|
|
19
|
+
* was spoken. A parent reading an exposed id would sample it a render later,
|
|
20
|
+
* which is a race the very first turn of a session is most likely to lose.
|
|
21
|
+
*/
|
|
22
|
+
onSubmit: (text: string, voiceSessionId: string) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Id + text of the newest assistant reply. A CHANGE of id is what advances
|
|
25
|
+
* the session out of `thinking` — the text alone is not enough, because two
|
|
26
|
+
* consecutive replies can be identical.
|
|
27
|
+
*/
|
|
28
|
+
latestReply: {
|
|
29
|
+
id: string;
|
|
30
|
+
text: string;
|
|
31
|
+
} | null;
|
|
32
|
+
/** Language the user is speaking, so the recogniser is pointed correctly. */
|
|
33
|
+
language: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Drives a live voice conversation (BOFF-7120).
|
|
37
|
+
*
|
|
38
|
+
* Deliberately thin: the loop itself is `voiceSessionReducer`, which is pure and
|
|
39
|
+
* tested, and playback is the assistant's EXISTING auto-speak path rather than a
|
|
40
|
+
* second speaking mechanism. Reusing auto-speak is what keeps the "never speak a
|
|
41
|
+
* reply in a language this device has no voice for" guarantee from BOFF-7106/7107
|
|
42
|
+
* true inside the session too — a bespoke playback path here would have quietly
|
|
43
|
+
* bypassed all of it.
|
|
44
|
+
*
|
|
45
|
+
* What this hook owns is only the parts a reducer cannot: the silence timer, the
|
|
46
|
+
* microphone's lifetime, and turning "auto-speak is currently on" back off again
|
|
47
|
+
* when the session ends.
|
|
48
|
+
*/
|
|
49
|
+
export declare function useVoiceSession({ onSubmit, latestReply, language }: Options): {
|
|
50
|
+
session: Readonly<VoiceSessionSnapshot>;
|
|
51
|
+
/** A recoverable problem worth showing, or null when all is well. */
|
|
52
|
+
notice: VoiceSessionNotice | null;
|
|
53
|
+
/**
|
|
54
|
+
* The reply as it is being spoken, chunk by chunk — for the live caption.
|
|
55
|
+
*
|
|
56
|
+
* Relayed from the speech provider rather than read there directly by the
|
|
57
|
+
* UI, because the voice session is already this hook's contract with the
|
|
58
|
+
* component and a second speech consumer would be a second way for the two
|
|
59
|
+
* to disagree about what is playing.
|
|
60
|
+
*/
|
|
61
|
+
spokenChunks: {
|
|
62
|
+
chunks: string[];
|
|
63
|
+
index: number;
|
|
64
|
+
} | null;
|
|
65
|
+
/** Whether the microphone is genuinely capturing right now. */
|
|
66
|
+
capturing: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* True once started and not yet ended — what decides whether to render.
|
|
69
|
+
*
|
|
70
|
+
* ★ A failure keeps it true. `fail` sets phase `ended`, so this used to go
|
|
71
|
+
* false the instant the microphone was refused and the panel vanished
|
|
72
|
+
* without ever saying why. `end` clears the error, which is how the user
|
|
73
|
+
* dismisses it.
|
|
74
|
+
*/
|
|
75
|
+
active: boolean;
|
|
76
|
+
/** The user has cut the assistant off and capture has not resumed yet. */
|
|
77
|
+
interrupting: boolean;
|
|
78
|
+
/** Quiet has fallen and the correction window is running. */
|
|
79
|
+
awaitingEndpoint: boolean;
|
|
80
|
+
/** Can a session be offered at all on this device? */
|
|
81
|
+
available: boolean;
|
|
82
|
+
start: () => void;
|
|
83
|
+
end: () => void;
|
|
84
|
+
toggleMute: () => void;
|
|
85
|
+
interrupt: () => void;
|
|
86
|
+
};
|
|
87
|
+
export {};
|
|
88
|
+
//# sourceMappingURL=useVoiceSession.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useVoiceSession.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/ui/useVoiceSession.ts"],"names":[],"mappings":"AAUA,OAAO,EAWL,KAAK,oBAAoB,EAE1B,MAAM,uBAAuB,CAAC;AAsC/B;;;;;;;;GAQG;AACH,MAAM,MAAM,kBAAkB,GAC1B,SAAS,GACT,eAAe,GACf,eAAe,GACf,oBAAoB,CAAC;AAMzB,UAAU,OAAO;IACf,+EAA+E;IAC/E;;;;;;;OAOG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD;;;;OAIG;IACH,WAAW,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,OAAO;;IAwctE,qEAAqE;;IAErE;;;;;;;OAOG;;;;;IAEH,+DAA+D;;IAE/D;;;;;;;OAOG;;IAIH,0EAA0E;;IAE1E,6DAA6D;;IAE7D,sDAAsD;;;;;;EA4B3D"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { useVoiceInput as e } from "../voice/useVoiceInput.js";
|
|
2
|
+
import { INITIAL_SESSION as t, INTERRUPT_MAX_MS as n, SILENCE_SUBMIT_MS as r, isCapturing as i, isSubmittable as a, planReplyConsumption as o, spokenSoFar as s, voiceSessionReducer as c } from "../voice/voiceSession.js";
|
|
3
|
+
import { useAssistantSpeech as l } from "./AssistantSpeechProvider.js";
|
|
4
|
+
import { useCallback as u, useEffect as d, useMemo as f, useReducer as p, useRef as m, useState as h } from "react";
|
|
5
|
+
//#region src/shared/assistant/ui/useVoiceSession.ts
|
|
6
|
+
var g = 4e3, _ = 2500, v = 5e3, y = 12e3, b = "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQAAAAA=";
|
|
7
|
+
function x({ onSubmit: x, latestReply: S, language: C }) {
|
|
8
|
+
let w = m(null), [T, E] = p(u((e, t) => c(e, t, w.current), []), t), D = l(), { setSessionSpeaking: O, speakingMessageId: k, stop: A } = D, j = m(null), M = m(null), N = m(!1), [P, F] = h(null), [I, L] = h(!1), [R, z] = h(!1), B = m(T);
|
|
9
|
+
B.current = T;
|
|
10
|
+
let V = m(S);
|
|
11
|
+
V.current = S, d(() => {
|
|
12
|
+
if (T.phase === "speaking") {
|
|
13
|
+
w.current = S?.text ?? null;
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (w.current === null) return;
|
|
17
|
+
let e = setTimeout(() => {
|
|
18
|
+
w.current = null;
|
|
19
|
+
}, _);
|
|
20
|
+
return () => clearTimeout(e);
|
|
21
|
+
}, [T.phase, S]);
|
|
22
|
+
let H = u((e, t) => {
|
|
23
|
+
E({
|
|
24
|
+
type: "speech",
|
|
25
|
+
transcript: e,
|
|
26
|
+
final: t
|
|
27
|
+
});
|
|
28
|
+
}, []), U = m(0), W = u(() => {
|
|
29
|
+
A(), U.current = Date.now(), L(!0), E({ type: "interrupt" });
|
|
30
|
+
}, [A]), G = e({
|
|
31
|
+
onFinalize: u((e) => H(e, !0), [H]),
|
|
32
|
+
language: C
|
|
33
|
+
});
|
|
34
|
+
d(() => {
|
|
35
|
+
G.interim && H(G.interim, !1);
|
|
36
|
+
}, [G.interim, H]), d(() => {
|
|
37
|
+
if (j.current && clearTimeout(j.current), z(!1), T.phase !== "listening" || T.muted || !a(s(T))) return;
|
|
38
|
+
let e = setTimeout(() => z(!0), 800);
|
|
39
|
+
return j.current = setTimeout(() => E({ type: "silence" }), r), () => {
|
|
40
|
+
clearTimeout(e), j.current && clearTimeout(j.current);
|
|
41
|
+
};
|
|
42
|
+
}, [T]), d(() => {
|
|
43
|
+
if (!I) return;
|
|
44
|
+
let e = Date.now() - U.current, t = G.isListening ? Math.max(0, 500 - e) : Math.max(0, n - e), r = setTimeout(() => L(!1), t);
|
|
45
|
+
return () => clearTimeout(r);
|
|
46
|
+
}, [I, G.isListening]);
|
|
47
|
+
let K = m("");
|
|
48
|
+
T.phase === "listening" && (K.current = s(T)), d(() => {
|
|
49
|
+
if (T.phase !== "thinking") return;
|
|
50
|
+
let e = K.current.trim();
|
|
51
|
+
e && x(e, q.current ?? `vs-orphan-${Date.now().toString(36)}`);
|
|
52
|
+
}, [T.phase]), d(() => {
|
|
53
|
+
let e = o(S?.id, T.phase, M.current);
|
|
54
|
+
e.claim && (M.current = S?.id ?? null, e.advance && S && E({
|
|
55
|
+
type: "reply",
|
|
56
|
+
text: S.text
|
|
57
|
+
}));
|
|
58
|
+
}, [S, T.phase]), d(() => {
|
|
59
|
+
let e = k !== null;
|
|
60
|
+
N.current && !e && E({ type: "speech-ended" }), N.current = e;
|
|
61
|
+
}, [k]), d(() => {
|
|
62
|
+
T.phase !== "listening" && F((e) => e === "nothing-heard" ? null : e);
|
|
63
|
+
}, [T.phase]), d(() => {
|
|
64
|
+
if (T.phase !== "speaking" || k !== null) return;
|
|
65
|
+
let e = setTimeout(() => {
|
|
66
|
+
N.current || E({ type: "speech-ended" });
|
|
67
|
+
}, g);
|
|
68
|
+
return () => clearTimeout(e);
|
|
69
|
+
}, [T.phase, k]), d(() => {
|
|
70
|
+
i(T) ? G.isListening || G.start() : G.isListening && G.stop();
|
|
71
|
+
}, [T, G]), d(() => {
|
|
72
|
+
G.error === "not-allowed" || G.error === "audio-capture" ? E({
|
|
73
|
+
type: "fail",
|
|
74
|
+
error: "mic-denied"
|
|
75
|
+
}) : G.error === "language-not-supported" ? E({
|
|
76
|
+
type: "fail",
|
|
77
|
+
error: "no-speech-support"
|
|
78
|
+
}) : G.error === "network" ? F("network") : G.error === "unknown" && F("recognition-failed");
|
|
79
|
+
}, [G.error]), d(() => {
|
|
80
|
+
if (!i(T) || G.isListening) return;
|
|
81
|
+
let e = setTimeout(() => F("not-capturing"), v);
|
|
82
|
+
return () => clearTimeout(e);
|
|
83
|
+
}, [T, G.isListening]), d(() => {
|
|
84
|
+
if (T.phase !== "listening" || T.muted || !G.isListening || s(T) !== "") return;
|
|
85
|
+
let e = setTimeout(() => F("nothing-heard"), y);
|
|
86
|
+
return () => clearTimeout(e);
|
|
87
|
+
}, [T, G.isListening]), d(() => {
|
|
88
|
+
s(T) !== "" && F(null);
|
|
89
|
+
}, [T]);
|
|
90
|
+
let q = m(null), J = u(() => {
|
|
91
|
+
if (L(!1), z(!1), !G.isSupported) {
|
|
92
|
+
E({
|
|
93
|
+
type: "fail",
|
|
94
|
+
error: "no-speech-support"
|
|
95
|
+
});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (D.isBlocked) {
|
|
99
|
+
E({
|
|
100
|
+
type: "fail",
|
|
101
|
+
error: "no-voice-support"
|
|
102
|
+
});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
let e = typeof window < "u" ? window.speechSynthesis : null;
|
|
107
|
+
if (e && typeof window.SpeechSynthesisUtterance == "function") {
|
|
108
|
+
let t = new window.SpeechSynthesisUtterance("");
|
|
109
|
+
t.volume = 0, e.speak(t);
|
|
110
|
+
}
|
|
111
|
+
} catch {}
|
|
112
|
+
try {
|
|
113
|
+
if (typeof window < "u" && typeof window.Audio == "function") {
|
|
114
|
+
let e = new window.Audio(b);
|
|
115
|
+
e.volume = 0, e.play().catch(() => {});
|
|
116
|
+
}
|
|
117
|
+
} catch {}
|
|
118
|
+
O(!0), F(null), M.current = V.current?.id ?? null, q.current = `vs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`, E({ type: "start" });
|
|
119
|
+
}, [
|
|
120
|
+
G.isSupported,
|
|
121
|
+
D.isBlocked,
|
|
122
|
+
O
|
|
123
|
+
]), Y = u(() => {
|
|
124
|
+
L(!1), z(!1), q.current = null, E({ type: "end" }), A(), O(!1);
|
|
125
|
+
}, [A, O]), X = u(() => {
|
|
126
|
+
E({
|
|
127
|
+
type: "mute",
|
|
128
|
+
muted: !B.current.muted
|
|
129
|
+
});
|
|
130
|
+
}, []);
|
|
131
|
+
return d(() => () => {
|
|
132
|
+
O(!1);
|
|
133
|
+
}, [O]), f(() => ({
|
|
134
|
+
session: T,
|
|
135
|
+
notice: P,
|
|
136
|
+
spokenChunks: D.spokenChunks,
|
|
137
|
+
capturing: G.isListening,
|
|
138
|
+
active: T.phase !== "idle" && (T.phase !== "ended" || T.error !== null),
|
|
139
|
+
interrupting: I,
|
|
140
|
+
awaitingEndpoint: R,
|
|
141
|
+
available: G.isSupported && !D.isBlocked,
|
|
142
|
+
start: J,
|
|
143
|
+
end: Y,
|
|
144
|
+
toggleMute: X,
|
|
145
|
+
interrupt: W
|
|
146
|
+
}), [
|
|
147
|
+
T,
|
|
148
|
+
P,
|
|
149
|
+
I,
|
|
150
|
+
R,
|
|
151
|
+
G.isListening,
|
|
152
|
+
G.isSupported,
|
|
153
|
+
D.isBlocked,
|
|
154
|
+
D.spokenChunks,
|
|
155
|
+
J,
|
|
156
|
+
Y,
|
|
157
|
+
X,
|
|
158
|
+
W
|
|
159
|
+
]);
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
export { x as useVoiceSession };
|
package/dist/shared-assistant.js
CHANGED
|
@@ -35,4 +35,7 @@ import { BOILERPLATE_PAGE_SHARE as Qt, MAX_BOILERPLATE_LINE_CHARS as $t, MAX_EXT
|
|
|
35
35
|
import { ATTACHMENT_ACCEPT as yn, AttachmentRejectedError as bn, INLINE_IMAGE_MAX_DIM as xn, INLINE_IMAGE_QUALITY as Sn, MAX_ATTACHMENTS as Cn, MAX_FILE_BYTES as wn, MAX_INLINE_CHARS_PER_FILE as Tn, MAX_INLINE_CHARS_TOTAL as En, MAX_INLINE_IMAGE_BASE64_CHARS as Dn, MAX_INLINE_IMAGE_PARTS as On, MAX_INLINE_TURN_BODY_BYTES as kn, MAX_TURN_BODY_BYTES as An, PROMPT_ENVELOPE_RESERVE_BYTES as jn, TURN_BODY_HEADROOM_BYTES as Mn, buildAttachmentPromptBlock as Nn, buildPromptParts as Pn, buildPromptWithAttachments as Fn, classifyAttachment as In, countInlineImageCandidates as Ln, countInlinedImageParts as Rn, detectDocumentFormat as zn, estimateTurnBodyBytes as Bn, formatBytes as Vn, getAttachmentSendBlocker as Hn, getExtension as Un, getSendableAttachments as Wn, hasAttachmentWorkInFlight as Gn, parseDataUrl as Kn, toMessageAttachment as qn, truncateForInline as Jn, validateFile as Yn } from "./shared/assistant/attachments.js";
|
|
36
36
|
import { useAssistantStore as Xn } from "./shared/assistant/store.js";
|
|
37
37
|
import { isTurnInFlight as Zn } from "./shared/assistant/turnState.js";
|
|
38
|
-
|
|
38
|
+
import { AssistantToolActivity as Qn, parseToolActivity as $n } from "./shared/assistant/ui/AssistantToolActivity.js";
|
|
39
|
+
import { AssistantSpeechProvider as er, useAssistantSpeech as tr } from "./shared/assistant/ui/AssistantSpeechProvider.js";
|
|
40
|
+
import { useVoiceSession as nr } from "./shared/assistant/ui/useVoiceSession.js";
|
|
41
|
+
export { p as ASSISTANT_API_AUTH_STRATEGY, At as ASSISTANT_LANGUAGES, yn as ATTACHMENT_ACCEPT, Rt as AssistantI18nProvider, U as AssistantRuntimeManager, er as AssistantSpeechProvider, Qn as AssistantToolActivity, bn as AttachmentRejectedError, Qt as BOILERPLATE_PAGE_SHARE, i as CHECK_AI_CREDITS, a as CREATE_SANDBOX, dt as DEFAULT_SPEAKABLE_LABELS, jt as DEFAULT_SPEECH_RATE, Re as ENDPOINT_GRACE_MS, o as EXTEND_SANDBOX_TTL, s as GET_ACTIVE_SANDBOXES, c as GET_SANDBOX, me as HOSTED_TTS_MAX_CHARS, ue as HOSTED_TTS_TIER, de as HOSTED_VOICES, Kt as IDLE_PLAYBACK, ze as INITIAL_SESSION, xn as INLINE_IMAGE_MAX_DIM, Sn as INLINE_IMAGE_QUALITY, Be as INTERRUPT_MAX_MS, Ve as INTERRUPT_MIN_MS, Cn as MAX_ATTACHMENTS, $t as MAX_BOILERPLATE_LINE_CHARS, en as MAX_EXTRACTED_CHARS, wn as MAX_FILE_BYTES, Tn as MAX_INLINE_CHARS_PER_FILE, En as MAX_INLINE_CHARS_TOTAL, Xt as MAX_INLINE_IMAGE_BASE64_BYTES, Dn as MAX_INLINE_IMAGE_BASE64_CHARS, On as MAX_INLINE_IMAGE_PARTS, kn as MAX_INLINE_TURN_BODY_BYTES, tn as MAX_PDF_PAGES_SCANNED, Zt as MAX_REQUEST_BODY_BYTES, ft as MAX_SPEAKABLE_CHARS, pt as MAX_SPOKEN_TABLE_ROWS, An as MAX_TURN_BODY_BYTES, nn as MIN_PAGE_TEXT_CHARS, rn as MIN_TEXT_PAGE_SHARE, Ct as POLL_FAST_MS, wt as POLL_FAST_UNTIL_MS, Tt as POLL_SLOW_MS, Et as POLL_STEADY_MS, Dt as POLL_STEADY_UNTIL_MS, xt as PREVIEW_LANGUAGES, jn as PROMPT_ENVELOPE_RESERVE_BYTES, l as PROXY_SANDBOX_REQUEST, u as REPORT_AI_TOKEN_USAGE, He as SILENCE_SUBMIT_MS, he as SPEAKING_RATE_MAX, ge as SPEAKING_RATE_MAX_STREAMING, _e as SPEAKING_RATE_MAX_SYNC, ve as SPEAKING_RATE_MIN, mt as SPEECH_CHUNK_CHARS, ht as SPEECH_CHUNK_CJK_CHARS, Xe as SPEECH_LANG, Ht as SPOKEN_LABEL_LANGUAGES, d as STOP_ASSISTANT_SANDBOX, Mn as TURN_BODY_HEADROOM_BYTES, Ze as VOICE_LIST_TIMEOUT_MS, f as WAIT_FOR_SANDBOX_RUNNING, zt as assistantTranslationKey, ye as base64ToBytes, le as browserSpeechEngine, C as buildAssistantSandboxEnv, Nn as buildAttachmentPromptBlock, ct as buildContinuedFromVoiceNote, W as buildProgress, Pn as buildPromptParts, w as buildPromptRequestBody, Fn as buildPromptWithAttachments, be as buildRequest, t as buildSandboxContextInput, lt as buildVoiceTranscriptMarkdown, ne as chatTurns, T as checkAssistantAiCredits, gt as chunkForSpeech, In as classifyAttachment, xe as classifyHttpFailure, an as columnLetterToIndex, De as configureSpeechEngine, _t as containsCjk, Ln as countInlineImageCandidates, Rn as countInlinedImageParts, E as createAssistantSandbox, D as createAssistantSession, Te as createHostedTtsEngine, Ee as createRoutingSpeechEngine, ee as createSandboxAssistantTransport, Oe as createSpeechEngines, Se as decodeAudioResponse, on as decodeXmlEntities, zn as detectDocumentFormat, Wt as detectScriptLocale, sn as docxXmlToText, O as downloadAssistantArtifact, Bn as estimateTurnBodyBytes, n as executeSandboxGraphQL, k as extendAssistantSandboxTTL, Mt as findAssistantLanguage, A as findExistingSandbox, It as findPendingWrite, cn as fitPdfPages, Vn as formatBytes, ln as formatPageRanges, j as getAssistantArtifacts, G as getAssistantMedia, M as getAssistantMessages, N as getAssistantSession, P as getAssistantStreamAccess, K as getAssistantText, Hn as getAttachmentSendBlocker, y as getCapability, Un as getExtension, m as getImageForMode, h as getImageTagForMode, q as getRawMessageCreatedAt, g as getResourcesForMode, _ as getSandboxImageRegistry, Wn as getSendableAttachments, ke as getSpeechEngine, J as getToolProgress, re as groupBySession, ie as groupConsecutiveBySession, Gn as hasAttachmentWorkInFlight, fe as hostedLocaleFor, pe as hostedSupportsLanguage, Ue as isCapturing, We as isEcho, un as isMostlyImagePages, r as isQuotaExhaustedError, Y as isReapedSandboxConnectionError, X as isRecoverable, qt as isSpeakingMessage, Qe as isSpeechBlocked, Ge as isSubmittable, Zn as isTurnInFlight, b as isUnknownFieldError, ae as isVoiceTurn, dn as joinSheets, $e as languageDisplayName, Z as mapSessionToHistory, x as markSupported, S as markUnsupported, Q as mergeMessagesById, Me as micIsInterrupt, F as modeRequiresConfirmWrites, et as normalizeLangTag, Ke as orbMode, fn as pageMarker, Lt as parseConfirmationRefusal, Kn as parseDataUrl, pn as parseSharedStrings, $n as parseToolActivity, mn as parseWorkbookSheets, hn as pdfTextItemsToPage, qe as planReplyConsumption, Ot as pollDelayMs, tt as primaryLanguageSubtag, Ae as readSpeechEngineConfig, Pt as replyFollowsInput, I as reportAssistantAiTokenUsage, Jt as requiresCancel, je as resetSpeechEngine, Nt as resolveAssistantLanguage, Ft as resolveLanguagePair, v as resolveSandboxAssistantMode, Gt as resolveSpeechLocale, $ as sanitize, L as selectExistingAssistantSandbox, nt as selectVoiceForLocale, R as sendAssistantPromptAsync, z as sendHeartbeat, gn as sheetXmlToRows, Ce as shouldDisableEngine, Ne as showsAssistantCaption, Pe as showsUserCaption, vt as speakableEmail, rt as speechGate, Ut as speechLabelsFor, it as speechLangForLocale, Yt as speechPlaybackReducer, we as splitForVendor, yt as splitTableRow, Je as spokenSoFar, B as stopReapedAssistantSandbox, _n as summarisePageText, kt as toCaptionText, qn as toMessageAttachment, bt as toSpeakableText, Jn as truncateForInline, vn as truncatePagesForInline, Bt as useAssistantI18nNamespace, tr as useAssistantSpeech, Xn as useAssistantStore, Vt as useAssistantTr, te as useSandboxAssistantTransport, e as useVoiceInput, nr as useVoiceSession, Yn as validateFile, at as voiceAvailability, oe as voiceMarker, St as voicePreviewText, ot as voiceQualityRank, se as voiceSessionIdOf, Ye as voiceSessionReducer, Fe as voiceStageState, ut as voiceTranscriptFilename, ce as voiceTurnsOnly, st as voicesForLocale, V as waitForAssistantServiceReady, H as waitForSandboxRunning, Ie as waveformIsActive, Le as waveformIsSettled };
|