@athenaintel/react 0.42.1 → 0.42.2
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/README.md +36 -7
- package/dist/agent-runtime/src/index.d.ts +2 -2
- package/dist/agent-runtime/src/messages.cjs +38 -7
- package/dist/agent-runtime/src/messages.cjs.map +1 -1
- package/dist/agent-runtime/src/messages.d.ts +26 -8
- package/dist/agent-runtime/src/messages.js +38 -7
- package/dist/agent-runtime/src/messages.js.map +1 -1
- package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs +10 -0
- package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -1
- package/dist/agent-runtime/src/quota-safe-local-storage-session.d.ts +7 -0
- package/dist/agent-runtime/src/quota-safe-local-storage-session.js +10 -0
- package/dist/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -1
- package/dist/agent-runtime/src/session-lane-scope.cjs +25 -0
- package/dist/agent-runtime/src/session-lane-scope.cjs.map +1 -0
- package/dist/agent-runtime/src/session-lane-scope.d.ts +59 -0
- package/dist/agent-runtime/src/session-lane-scope.js +25 -0
- package/dist/agent-runtime/src/session-lane-scope.js.map +1 -0
- package/dist/agent-runtime/src/subagent-transcripts.cjs +2 -2
- package/dist/agent-runtime/src/subagent-transcripts.cjs.map +1 -1
- package/dist/agent-runtime/src/subagent-transcripts.d.ts +2 -2
- package/dist/agent-runtime/src/subagent-transcripts.js +3 -3
- package/dist/agent-runtime/src/subagent-transcripts.js.map +1 -1
- package/dist/agent-runtime/src/tool-names.cjs +4 -0
- package/dist/agent-runtime/src/tool-names.cjs.map +1 -1
- package/dist/agent-runtime/src/tool-names.d.ts +7 -0
- package/dist/agent-runtime/src/tool-names.js +4 -0
- package/dist/agent-runtime/src/tool-names.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +18 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +19 -10
- package/dist/agent-runtime/src/use-deep-agent-runtime.js +19 -2
- package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-thread.cjs +28 -10
- package/dist/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-thread.d.ts +35 -3
- package/dist/agent-runtime/src/use-deep-agent-thread.js +28 -10
- package/dist/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs +20 -0
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.js +20 -0
- package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
- package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.js +2 -0
- package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
- package/dist/catalog/runtime-catalog.js +1 -1
- package/dist/chat/AthenaChat.cjs +125 -103
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.d.ts +9 -0
- package/dist/chat/AthenaChat.js +126 -104
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/chat-voice-controls.cjs +128 -0
- package/dist/chat/chat-voice-controls.cjs.map +1 -0
- package/dist/chat/chat-voice-controls.d.ts +19 -0
- package/dist/chat/chat-voice-controls.js +128 -0
- package/dist/chat/chat-voice-controls.js.map +1 -0
- package/dist/chat/use-statewire-approval-request.js +1 -1
- package/dist/chat-ui/composer/tiptap-composer.cjs +42 -22
- package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
- package/dist/chat-ui/composer/tiptap-composer.d.ts +1 -0
- package/dist/chat-ui/composer/tiptap-composer.js +43 -23
- package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +2 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +3 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.js +2 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
- package/dist/chat-ui/mentions/use-account-workspace-id.cjs +29 -51
- package/dist/chat-ui/mentions/use-account-workspace-id.cjs.map +1 -1
- package/dist/chat-ui/mentions/use-account-workspace-id.d.ts +2 -2
- package/dist/chat-ui/mentions/use-account-workspace-id.js +30 -52
- package/dist/chat-ui/mentions/use-account-workspace-id.js.map +1 -1
- package/dist/chat-ui/thread/composer-send-or-stop.cjs +2 -1
- package/dist/chat-ui/thread/composer-send-or-stop.cjs.map +1 -1
- package/dist/chat-ui/thread/composer-send-or-stop.d.ts +2 -0
- package/dist/chat-ui/thread/composer-send-or-stop.js +2 -1
- package/dist/chat-ui/thread/composer-send-or-stop.js.map +1 -1
- package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
- package/dist/diagnostics/athena-diagnostics.d.ts +1 -1
- package/dist/diagnostics/athena-diagnostics.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/runtime/statewire-session-key.cjs.map +1 -1
- package/dist/runtime/statewire-session-key.d.ts +8 -5
- package/dist/runtime/statewire-session-key.js.map +1 -1
- package/dist/runtime/useAthenaStatewireRuntime.cjs +5 -2
- package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
- package/dist/runtime/useAthenaStatewireRuntime.js +5 -2
- package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
- package/dist/voice/use-chat-voice-enabled.cjs +43 -0
- package/dist/voice/use-chat-voice-enabled.cjs.map +1 -0
- package/dist/voice/use-chat-voice-enabled.d.ts +9 -0
- package/dist/voice/use-chat-voice-enabled.js +43 -0
- package/dist/voice/use-chat-voice-enabled.js.map +1 -0
- package/package.json +4 -8
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const react = require("@assistant-ui/react");
|
|
5
|
+
const lucideReact = require("lucide-react");
|
|
6
|
+
const react$1 = require("react");
|
|
7
|
+
const athenaDiagnostics = require("../diagnostics/athena-diagnostics.cjs");
|
|
8
|
+
const voice_livekit = require("../voice/livekit.cjs");
|
|
9
|
+
const useDeepAgentVoice = require("../agent-runtime/src/use-deep-agent-voice.cjs");
|
|
10
|
+
const useChatVoiceEnabled = require("../voice/use-chat-voice-enabled.cjs");
|
|
11
|
+
const tooltipIconButton = require("./tooltip-icon-button.cjs");
|
|
12
|
+
const apiClient = require("../chat-ui/mentions/suggestions/data/api-client.cjs");
|
|
13
|
+
const runtimeCatalog = require("../catalog/runtime-catalog.cjs");
|
|
14
|
+
const voicePresentation = require("../agent-runtime/src/voice-presentation.cjs");
|
|
15
|
+
function useComposerChatVoice({ options }) {
|
|
16
|
+
const aui = react.useAui();
|
|
17
|
+
const auth = apiClient.useMentionAuth();
|
|
18
|
+
const principal = runtimeCatalog.useCatalogPrincipal();
|
|
19
|
+
const enabled = useChatVoiceEnabled.useChatVoiceEnabled({ enabled: options !== false });
|
|
20
|
+
const audioRef = react$1.useRef(null);
|
|
21
|
+
const media = react$1.useMemo(
|
|
22
|
+
() => voice_livekit.createDeepAgentVoiceLiveKitAdapter({ audioElement: () => audioRef.current }),
|
|
23
|
+
[]
|
|
24
|
+
);
|
|
25
|
+
const config = typeof options === "object" ? options : void 0;
|
|
26
|
+
const voice = useDeepAgentVoice.useDeepAgentVoice({
|
|
27
|
+
transport: "livekit",
|
|
28
|
+
livekit: (config == null ? void 0 : config.livekit) ?? media,
|
|
29
|
+
model: config == null ? void 0 : config.model,
|
|
30
|
+
onDiagnostic: (config == null ? void 0 : config.onDiagnostic) ?? ((entry) => athenaDiagnostics.athenaDiagnostics.emit("voice.chat", { data: { ...entry } }))
|
|
31
|
+
});
|
|
32
|
+
const active = voice.phase === "connecting" || voice.phase === "live";
|
|
33
|
+
const { end, syncProfile } = voice;
|
|
34
|
+
const identity = JSON.stringify([
|
|
35
|
+
auth.backendUrl,
|
|
36
|
+
principal,
|
|
37
|
+
auth.apiKey ? runtimeCatalog.credentialFingerprint({ credential: auth.apiKey }) : null
|
|
38
|
+
]);
|
|
39
|
+
const previousScope = react$1.useRef({ identity, workspaceId: auth.workspaceId });
|
|
40
|
+
react$1.useEffect(() => {
|
|
41
|
+
const previous = previousScope.current;
|
|
42
|
+
const changed = previous.identity !== identity || Boolean(
|
|
43
|
+
previous.workspaceId && auth.workspaceId && previous.workspaceId !== auth.workspaceId
|
|
44
|
+
);
|
|
45
|
+
previousScope.current = {
|
|
46
|
+
identity,
|
|
47
|
+
workspaceId: previous.identity === identity ? auth.workspaceId ?? previous.workspaceId : auth.workspaceId
|
|
48
|
+
};
|
|
49
|
+
if (active && (changed || !enabled)) end();
|
|
50
|
+
}, [identity, auth.workspaceId, active, enabled, end]);
|
|
51
|
+
const live = voice.phase === "live";
|
|
52
|
+
react$1.useEffect(() => {
|
|
53
|
+
var _a, _b;
|
|
54
|
+
if (!live) return;
|
|
55
|
+
let timer;
|
|
56
|
+
const schedule = () => {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
timer = setTimeout(syncProfile, 800);
|
|
59
|
+
};
|
|
60
|
+
schedule();
|
|
61
|
+
const unsubscribe = (_b = (_a = aui.modelContext).subscribe) == null ? void 0 : _b.call(_a, schedule);
|
|
62
|
+
return () => {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
unsubscribe == null ? void 0 : unsubscribe();
|
|
65
|
+
};
|
|
66
|
+
}, [aui, live, syncProfile]);
|
|
67
|
+
return { voice, enabled, active, audioRef };
|
|
68
|
+
}
|
|
69
|
+
function ChatVoiceButton({
|
|
70
|
+
voice,
|
|
71
|
+
disabled
|
|
72
|
+
}) {
|
|
73
|
+
const live = voice.phase === "live";
|
|
74
|
+
const connecting = voice.phase === "connecting";
|
|
75
|
+
const label = connecting ? "Cancel voice connection" : live ? voice.micMuted ? "Unmute microphone" : "Mute microphone" : "Start voice session";
|
|
76
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
77
|
+
tooltipIconButton.TooltipIconButton,
|
|
78
|
+
{
|
|
79
|
+
type: "button",
|
|
80
|
+
tooltip: label,
|
|
81
|
+
"aria-label": label,
|
|
82
|
+
"aria-pressed": live && !voice.micMuted,
|
|
83
|
+
className: "aui-composer-voice size-8 rounded-full p-0 disabled:opacity-30",
|
|
84
|
+
disabled: !live && !connecting && disabled,
|
|
85
|
+
onClick: () => {
|
|
86
|
+
if (connecting) voice.end();
|
|
87
|
+
else if (live) voice.setMicMuted(!voice.micMuted);
|
|
88
|
+
else void voice.start();
|
|
89
|
+
},
|
|
90
|
+
children: connecting ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2Icon, { className: "size-4 animate-spin" }) : live ? voice.micMuted ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOffIcon, { className: "size-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicIcon, { className: "size-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.HeadphonesIcon, { className: "size-4" })
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
function ChatVoiceStatus({ voice }) {
|
|
95
|
+
const elapsed = useDeepAgentVoice.useVoiceElapsed(voice.liveSince);
|
|
96
|
+
const active = voice.phase === "connecting" || voice.phase === "live";
|
|
97
|
+
if (!active && !voice.error && !voice.handoffTask) return null;
|
|
98
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "aui-chat-voice-status flex flex-col gap-1 px-2 pb-2 text-xs text-muted-foreground", children: [
|
|
99
|
+
active && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", role: "status", children: [
|
|
100
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: voice.phase === "connecting" ? "Connecting voice…" : `Voice ${voice.micMuted ? "muted" : "live"}${elapsed ? ` · ${elapsed}` : ""}` }),
|
|
101
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
102
|
+
tooltipIconButton.TooltipIconButton,
|
|
103
|
+
{
|
|
104
|
+
type: "button",
|
|
105
|
+
tooltip: "End voice session",
|
|
106
|
+
"aria-label": "End voice session",
|
|
107
|
+
onClick: voice.end,
|
|
108
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PhoneOffIcon, { className: "size-3.5" })
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
] }),
|
|
112
|
+
voice.phase === "live" && /* @__PURE__ */ jsxRuntime.jsx("span", { children: voicePresentation.VOICE_LIVE_TYPED_HINT }),
|
|
113
|
+
voice.activeTask && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
114
|
+
"Athena is working: ",
|
|
115
|
+
voice.activeTask.text
|
|
116
|
+
] }),
|
|
117
|
+
voice.handoffTask && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
118
|
+
"Voice ended. Athena is still working: ",
|
|
119
|
+
voice.handoffTask.text
|
|
120
|
+
] }),
|
|
121
|
+
voice.notice && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "status", children: voice.notice }),
|
|
122
|
+
voice.error && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", className: "text-destructive", children: voice.error })
|
|
123
|
+
] });
|
|
124
|
+
}
|
|
125
|
+
exports.ChatVoiceButton = ChatVoiceButton;
|
|
126
|
+
exports.ChatVoiceStatus = ChatVoiceStatus;
|
|
127
|
+
exports.useComposerChatVoice = useComposerChatVoice;
|
|
128
|
+
//# sourceMappingURL=chat-voice-controls.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-voice-controls.cjs","sources":["../../src/chat/chat-voice-controls.tsx"],"sourcesContent":["import { useAui } from '@assistant-ui/react';\nimport { useVoiceElapsed, VOICE_LIVE_TYPED_HINT } from '@athenaintel/agent-runtime';\nimport { HeadphonesIcon, Loader2Icon, MicIcon, MicOffIcon, PhoneOffIcon } from 'lucide-react';\nimport { useEffect, useMemo, useRef } from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { createDeepAgentVoiceLiveKitAdapter } from '../voice/livekit';\nimport {\n useAthenaChatVoice,\n type UseAthenaChatVoiceOptions,\n type UseAthenaChatVoiceReturn,\n} from '../voice/chat';\nimport { useChatVoiceEnabled } from '../voice/use-chat-voice-enabled';\nimport { TooltipIconButton } from './tooltip-icon-button';\nimport { useMentionAuth } from '../chat-ui/mentions/suggestions/data/api-client';\nimport { credentialFingerprint, useCatalogPrincipal } from '../catalog/runtime-catalog';\n\nexport interface AthenaChatVoiceOptions\n extends Pick<UseAthenaChatVoiceOptions, 'model' | 'livekit' | 'onDiagnostic'> {}\n\n/** One controller for this composer's controls, text sends and audio sink. */\nexport function useComposerChatVoice({ options }: { options: boolean | AthenaChatVoiceOptions }) {\n const aui = useAui();\n const auth = useMentionAuth();\n const principal = useCatalogPrincipal();\n const enabled = useChatVoiceEnabled({ enabled: options !== false });\n const audioRef = useRef<HTMLAudioElement | null>(null);\n const media = useMemo(\n () => createDeepAgentVoiceLiveKitAdapter({ audioElement: () => audioRef.current }),\n []\n );\n const config = typeof options === 'object' ? options : undefined;\n const voice = useAthenaChatVoice({\n transport: 'livekit',\n livekit: config?.livekit ?? media,\n model: config?.model,\n onDiagnostic:\n config?.onDiagnostic ??\n ((entry) => athenaDiagnostics.emit('voice.chat', { data: { ...entry } })),\n });\n const active = voice.phase === 'connecting' || voice.phase === 'live';\n const { end, syncProfile } = voice;\n const identity = JSON.stringify([\n auth.backendUrl,\n principal,\n auth.apiKey ? credentialFingerprint({ credential: auth.apiKey }) : null,\n ]);\n const previousScope = useRef({ identity, workspaceId: auth.workspaceId });\n useEffect(() => {\n const previous = previousScope.current;\n const changed =\n previous.identity !== identity ||\n Boolean(\n previous.workspaceId && auth.workspaceId && previous.workspaceId !== auth.workspaceId\n );\n previousScope.current = {\n identity,\n workspaceId:\n previous.identity === identity\n ? auth.workspaceId ?? previous.workspaceId\n : auth.workspaceId,\n };\n if (active && (changed || !enabled)) end();\n }, [identity, auth.workspaceId, active, enabled, end]);\n\n // Model/agent/toolkit/mode picks live in the ambient model context. Read it\n // through the controller at send time, with the same debounce as other hosts.\n const live = voice.phase === 'live';\n useEffect(() => {\n if (!live) return;\n let timer: ReturnType<typeof setTimeout>;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(syncProfile, 800);\n };\n schedule();\n const unsubscribe = aui.modelContext.subscribe?.(schedule);\n return () => {\n clearTimeout(timer);\n unsubscribe?.();\n };\n }, [aui, live, syncProfile]);\n return { voice, enabled, active, audioRef };\n}\n\nexport function ChatVoiceButton({\n voice,\n disabled,\n}: {\n voice: UseAthenaChatVoiceReturn;\n disabled: boolean;\n}) {\n const live = voice.phase === 'live';\n const connecting = voice.phase === 'connecting';\n const label = connecting\n ? 'Cancel voice connection'\n : live\n ? voice.micMuted\n ? 'Unmute microphone'\n : 'Mute microphone'\n : 'Start voice session';\n return (\n <TooltipIconButton\n type=\"button\"\n tooltip={label}\n aria-label={label}\n aria-pressed={live && !voice.micMuted}\n className=\"aui-composer-voice size-8 rounded-full p-0 disabled:opacity-30\"\n disabled={!live && !connecting && disabled}\n onClick={() => {\n if (connecting) voice.end();\n else if (live) voice.setMicMuted(!voice.micMuted);\n else void voice.start();\n }}\n >\n {connecting ? (\n <Loader2Icon className=\"size-4 animate-spin\" />\n ) : live ? (\n voice.micMuted ? (\n <MicOffIcon className=\"size-4\" />\n ) : (\n <MicIcon className=\"size-4\" />\n )\n ) : (\n <HeadphonesIcon className=\"size-4\" />\n )}\n </TooltipIconButton>\n );\n}\n\nexport function ChatVoiceStatus({ voice }: { voice: UseAthenaChatVoiceReturn }) {\n const elapsed = useVoiceElapsed(voice.liveSince);\n const active = voice.phase === 'connecting' || voice.phase === 'live';\n if (!active && !voice.error && !voice.handoffTask) return null;\n return (\n <div className=\"aui-chat-voice-status flex flex-col gap-1 px-2 pb-2 text-xs text-muted-foreground\">\n {active && (\n <div className=\"flex items-center justify-between gap-2\" role=\"status\">\n <span>\n {voice.phase === 'connecting'\n ? 'Connecting voice…'\n : `Voice ${voice.micMuted ? 'muted' : 'live'}${elapsed ? ` · ${elapsed}` : ''}`}\n </span>\n <TooltipIconButton\n type=\"button\"\n tooltip=\"End voice session\"\n aria-label=\"End voice session\"\n onClick={voice.end}\n >\n <PhoneOffIcon className=\"size-3.5\" />\n </TooltipIconButton>\n </div>\n )}\n {voice.phase === 'live' && <span>{VOICE_LIVE_TYPED_HINT}</span>}\n {voice.activeTask && <span>Athena is working: {voice.activeTask.text}</span>}\n {voice.handoffTask && (\n <span>Voice ended. Athena is still working: {voice.handoffTask.text}</span>\n )}\n {voice.notice && <span role=\"status\">{voice.notice}</span>}\n {voice.error && (\n <span role=\"alert\" className=\"text-destructive\">\n {voice.error}\n </span>\n )}\n </div>\n );\n}\n"],"names":["useAui","useMentionAuth","useCatalogPrincipal","useChatVoiceEnabled","useRef","useMemo","createDeepAgentVoiceLiveKitAdapter","useAthenaChatVoice","athenaDiagnostics","credentialFingerprint","useEffect","jsx","TooltipIconButton","Loader2Icon","MicOffIcon","MicIcon","HeadphonesIcon","useVoiceElapsed","jsxs","PhoneOffIcon","VOICE_LIVE_TYPED_HINT"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type UseAthenaChatVoiceOptions, type UseAthenaChatVoiceReturn } from '../voice/chat';
|
|
2
|
+
export interface AthenaChatVoiceOptions extends Pick<UseAthenaChatVoiceOptions, 'model' | 'livekit' | 'onDiagnostic'> {
|
|
3
|
+
}
|
|
4
|
+
/** One controller for this composer's controls, text sends and audio sink. */
|
|
5
|
+
export declare function useComposerChatVoice({ options }: {
|
|
6
|
+
options: boolean | AthenaChatVoiceOptions;
|
|
7
|
+
}): {
|
|
8
|
+
voice: UseAthenaChatVoiceReturn;
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
active: boolean;
|
|
11
|
+
audioRef: import("react").RefObject<HTMLAudioElement | null>;
|
|
12
|
+
};
|
|
13
|
+
export declare function ChatVoiceButton({ voice, disabled, }: {
|
|
14
|
+
voice: UseAthenaChatVoiceReturn;
|
|
15
|
+
disabled: boolean;
|
|
16
|
+
}): import("react").JSX.Element;
|
|
17
|
+
export declare function ChatVoiceStatus({ voice }: {
|
|
18
|
+
voice: UseAthenaChatVoiceReturn;
|
|
19
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAui } from "@assistant-ui/react";
|
|
3
|
+
import { Loader2Icon, MicOffIcon, MicIcon, HeadphonesIcon, PhoneOffIcon } from "lucide-react";
|
|
4
|
+
import { useRef, useMemo, useEffect } from "react";
|
|
5
|
+
import { athenaDiagnostics } from "../diagnostics/athena-diagnostics.js";
|
|
6
|
+
import { createDeepAgentVoiceLiveKitAdapter } from "../voice/livekit.js";
|
|
7
|
+
import { useDeepAgentVoice, useVoiceElapsed } from "../agent-runtime/src/use-deep-agent-voice.js";
|
|
8
|
+
import { useChatVoiceEnabled } from "../voice/use-chat-voice-enabled.js";
|
|
9
|
+
import { TooltipIconButton } from "./tooltip-icon-button.js";
|
|
10
|
+
import { useMentionAuth } from "../chat-ui/mentions/suggestions/data/api-client.js";
|
|
11
|
+
import { useCatalogPrincipal, credentialFingerprint } from "../catalog/runtime-catalog.js";
|
|
12
|
+
import { VOICE_LIVE_TYPED_HINT } from "../agent-runtime/src/voice-presentation.js";
|
|
13
|
+
function useComposerChatVoice({ options }) {
|
|
14
|
+
const aui = useAui();
|
|
15
|
+
const auth = useMentionAuth();
|
|
16
|
+
const principal = useCatalogPrincipal();
|
|
17
|
+
const enabled = useChatVoiceEnabled({ enabled: options !== false });
|
|
18
|
+
const audioRef = useRef(null);
|
|
19
|
+
const media = useMemo(
|
|
20
|
+
() => createDeepAgentVoiceLiveKitAdapter({ audioElement: () => audioRef.current }),
|
|
21
|
+
[]
|
|
22
|
+
);
|
|
23
|
+
const config = typeof options === "object" ? options : void 0;
|
|
24
|
+
const voice = useDeepAgentVoice({
|
|
25
|
+
transport: "livekit",
|
|
26
|
+
livekit: (config == null ? void 0 : config.livekit) ?? media,
|
|
27
|
+
model: config == null ? void 0 : config.model,
|
|
28
|
+
onDiagnostic: (config == null ? void 0 : config.onDiagnostic) ?? ((entry) => athenaDiagnostics.emit("voice.chat", { data: { ...entry } }))
|
|
29
|
+
});
|
|
30
|
+
const active = voice.phase === "connecting" || voice.phase === "live";
|
|
31
|
+
const { end, syncProfile } = voice;
|
|
32
|
+
const identity = JSON.stringify([
|
|
33
|
+
auth.backendUrl,
|
|
34
|
+
principal,
|
|
35
|
+
auth.apiKey ? credentialFingerprint({ credential: auth.apiKey }) : null
|
|
36
|
+
]);
|
|
37
|
+
const previousScope = useRef({ identity, workspaceId: auth.workspaceId });
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const previous = previousScope.current;
|
|
40
|
+
const changed = previous.identity !== identity || Boolean(
|
|
41
|
+
previous.workspaceId && auth.workspaceId && previous.workspaceId !== auth.workspaceId
|
|
42
|
+
);
|
|
43
|
+
previousScope.current = {
|
|
44
|
+
identity,
|
|
45
|
+
workspaceId: previous.identity === identity ? auth.workspaceId ?? previous.workspaceId : auth.workspaceId
|
|
46
|
+
};
|
|
47
|
+
if (active && (changed || !enabled)) end();
|
|
48
|
+
}, [identity, auth.workspaceId, active, enabled, end]);
|
|
49
|
+
const live = voice.phase === "live";
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
var _a, _b;
|
|
52
|
+
if (!live) return;
|
|
53
|
+
let timer;
|
|
54
|
+
const schedule = () => {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
timer = setTimeout(syncProfile, 800);
|
|
57
|
+
};
|
|
58
|
+
schedule();
|
|
59
|
+
const unsubscribe = (_b = (_a = aui.modelContext).subscribe) == null ? void 0 : _b.call(_a, schedule);
|
|
60
|
+
return () => {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
unsubscribe == null ? void 0 : unsubscribe();
|
|
63
|
+
};
|
|
64
|
+
}, [aui, live, syncProfile]);
|
|
65
|
+
return { voice, enabled, active, audioRef };
|
|
66
|
+
}
|
|
67
|
+
function ChatVoiceButton({
|
|
68
|
+
voice,
|
|
69
|
+
disabled
|
|
70
|
+
}) {
|
|
71
|
+
const live = voice.phase === "live";
|
|
72
|
+
const connecting = voice.phase === "connecting";
|
|
73
|
+
const label = connecting ? "Cancel voice connection" : live ? voice.micMuted ? "Unmute microphone" : "Mute microphone" : "Start voice session";
|
|
74
|
+
return /* @__PURE__ */ jsx(
|
|
75
|
+
TooltipIconButton,
|
|
76
|
+
{
|
|
77
|
+
type: "button",
|
|
78
|
+
tooltip: label,
|
|
79
|
+
"aria-label": label,
|
|
80
|
+
"aria-pressed": live && !voice.micMuted,
|
|
81
|
+
className: "aui-composer-voice size-8 rounded-full p-0 disabled:opacity-30",
|
|
82
|
+
disabled: !live && !connecting && disabled,
|
|
83
|
+
onClick: () => {
|
|
84
|
+
if (connecting) voice.end();
|
|
85
|
+
else if (live) voice.setMicMuted(!voice.micMuted);
|
|
86
|
+
else void voice.start();
|
|
87
|
+
},
|
|
88
|
+
children: connecting ? /* @__PURE__ */ jsx(Loader2Icon, { className: "size-4 animate-spin" }) : live ? voice.micMuted ? /* @__PURE__ */ jsx(MicOffIcon, { className: "size-4" }) : /* @__PURE__ */ jsx(MicIcon, { className: "size-4" }) : /* @__PURE__ */ jsx(HeadphonesIcon, { className: "size-4" })
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
function ChatVoiceStatus({ voice }) {
|
|
93
|
+
const elapsed = useVoiceElapsed(voice.liveSince);
|
|
94
|
+
const active = voice.phase === "connecting" || voice.phase === "live";
|
|
95
|
+
if (!active && !voice.error && !voice.handoffTask) return null;
|
|
96
|
+
return /* @__PURE__ */ jsxs("div", { className: "aui-chat-voice-status flex flex-col gap-1 px-2 pb-2 text-xs text-muted-foreground", children: [
|
|
97
|
+
active && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", role: "status", children: [
|
|
98
|
+
/* @__PURE__ */ jsx("span", { children: voice.phase === "connecting" ? "Connecting voice…" : `Voice ${voice.micMuted ? "muted" : "live"}${elapsed ? ` · ${elapsed}` : ""}` }),
|
|
99
|
+
/* @__PURE__ */ jsx(
|
|
100
|
+
TooltipIconButton,
|
|
101
|
+
{
|
|
102
|
+
type: "button",
|
|
103
|
+
tooltip: "End voice session",
|
|
104
|
+
"aria-label": "End voice session",
|
|
105
|
+
onClick: voice.end,
|
|
106
|
+
children: /* @__PURE__ */ jsx(PhoneOffIcon, { className: "size-3.5" })
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
] }),
|
|
110
|
+
voice.phase === "live" && /* @__PURE__ */ jsx("span", { children: VOICE_LIVE_TYPED_HINT }),
|
|
111
|
+
voice.activeTask && /* @__PURE__ */ jsxs("span", { children: [
|
|
112
|
+
"Athena is working: ",
|
|
113
|
+
voice.activeTask.text
|
|
114
|
+
] }),
|
|
115
|
+
voice.handoffTask && /* @__PURE__ */ jsxs("span", { children: [
|
|
116
|
+
"Voice ended. Athena is still working: ",
|
|
117
|
+
voice.handoffTask.text
|
|
118
|
+
] }),
|
|
119
|
+
voice.notice && /* @__PURE__ */ jsx("span", { role: "status", children: voice.notice }),
|
|
120
|
+
voice.error && /* @__PURE__ */ jsx("span", { role: "alert", className: "text-destructive", children: voice.error })
|
|
121
|
+
] });
|
|
122
|
+
}
|
|
123
|
+
export {
|
|
124
|
+
ChatVoiceButton,
|
|
125
|
+
ChatVoiceStatus,
|
|
126
|
+
useComposerChatVoice
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=chat-voice-controls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-voice-controls.js","sources":["../../src/chat/chat-voice-controls.tsx"],"sourcesContent":["import { useAui } from '@assistant-ui/react';\nimport { useVoiceElapsed, VOICE_LIVE_TYPED_HINT } from '@athenaintel/agent-runtime';\nimport { HeadphonesIcon, Loader2Icon, MicIcon, MicOffIcon, PhoneOffIcon } from 'lucide-react';\nimport { useEffect, useMemo, useRef } from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { createDeepAgentVoiceLiveKitAdapter } from '../voice/livekit';\nimport {\n useAthenaChatVoice,\n type UseAthenaChatVoiceOptions,\n type UseAthenaChatVoiceReturn,\n} from '../voice/chat';\nimport { useChatVoiceEnabled } from '../voice/use-chat-voice-enabled';\nimport { TooltipIconButton } from './tooltip-icon-button';\nimport { useMentionAuth } from '../chat-ui/mentions/suggestions/data/api-client';\nimport { credentialFingerprint, useCatalogPrincipal } from '../catalog/runtime-catalog';\n\nexport interface AthenaChatVoiceOptions\n extends Pick<UseAthenaChatVoiceOptions, 'model' | 'livekit' | 'onDiagnostic'> {}\n\n/** One controller for this composer's controls, text sends and audio sink. */\nexport function useComposerChatVoice({ options }: { options: boolean | AthenaChatVoiceOptions }) {\n const aui = useAui();\n const auth = useMentionAuth();\n const principal = useCatalogPrincipal();\n const enabled = useChatVoiceEnabled({ enabled: options !== false });\n const audioRef = useRef<HTMLAudioElement | null>(null);\n const media = useMemo(\n () => createDeepAgentVoiceLiveKitAdapter({ audioElement: () => audioRef.current }),\n []\n );\n const config = typeof options === 'object' ? options : undefined;\n const voice = useAthenaChatVoice({\n transport: 'livekit',\n livekit: config?.livekit ?? media,\n model: config?.model,\n onDiagnostic:\n config?.onDiagnostic ??\n ((entry) => athenaDiagnostics.emit('voice.chat', { data: { ...entry } })),\n });\n const active = voice.phase === 'connecting' || voice.phase === 'live';\n const { end, syncProfile } = voice;\n const identity = JSON.stringify([\n auth.backendUrl,\n principal,\n auth.apiKey ? credentialFingerprint({ credential: auth.apiKey }) : null,\n ]);\n const previousScope = useRef({ identity, workspaceId: auth.workspaceId });\n useEffect(() => {\n const previous = previousScope.current;\n const changed =\n previous.identity !== identity ||\n Boolean(\n previous.workspaceId && auth.workspaceId && previous.workspaceId !== auth.workspaceId\n );\n previousScope.current = {\n identity,\n workspaceId:\n previous.identity === identity\n ? auth.workspaceId ?? previous.workspaceId\n : auth.workspaceId,\n };\n if (active && (changed || !enabled)) end();\n }, [identity, auth.workspaceId, active, enabled, end]);\n\n // Model/agent/toolkit/mode picks live in the ambient model context. Read it\n // through the controller at send time, with the same debounce as other hosts.\n const live = voice.phase === 'live';\n useEffect(() => {\n if (!live) return;\n let timer: ReturnType<typeof setTimeout>;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(syncProfile, 800);\n };\n schedule();\n const unsubscribe = aui.modelContext.subscribe?.(schedule);\n return () => {\n clearTimeout(timer);\n unsubscribe?.();\n };\n }, [aui, live, syncProfile]);\n return { voice, enabled, active, audioRef };\n}\n\nexport function ChatVoiceButton({\n voice,\n disabled,\n}: {\n voice: UseAthenaChatVoiceReturn;\n disabled: boolean;\n}) {\n const live = voice.phase === 'live';\n const connecting = voice.phase === 'connecting';\n const label = connecting\n ? 'Cancel voice connection'\n : live\n ? voice.micMuted\n ? 'Unmute microphone'\n : 'Mute microphone'\n : 'Start voice session';\n return (\n <TooltipIconButton\n type=\"button\"\n tooltip={label}\n aria-label={label}\n aria-pressed={live && !voice.micMuted}\n className=\"aui-composer-voice size-8 rounded-full p-0 disabled:opacity-30\"\n disabled={!live && !connecting && disabled}\n onClick={() => {\n if (connecting) voice.end();\n else if (live) voice.setMicMuted(!voice.micMuted);\n else void voice.start();\n }}\n >\n {connecting ? (\n <Loader2Icon className=\"size-4 animate-spin\" />\n ) : live ? (\n voice.micMuted ? (\n <MicOffIcon className=\"size-4\" />\n ) : (\n <MicIcon className=\"size-4\" />\n )\n ) : (\n <HeadphonesIcon className=\"size-4\" />\n )}\n </TooltipIconButton>\n );\n}\n\nexport function ChatVoiceStatus({ voice }: { voice: UseAthenaChatVoiceReturn }) {\n const elapsed = useVoiceElapsed(voice.liveSince);\n const active = voice.phase === 'connecting' || voice.phase === 'live';\n if (!active && !voice.error && !voice.handoffTask) return null;\n return (\n <div className=\"aui-chat-voice-status flex flex-col gap-1 px-2 pb-2 text-xs text-muted-foreground\">\n {active && (\n <div className=\"flex items-center justify-between gap-2\" role=\"status\">\n <span>\n {voice.phase === 'connecting'\n ? 'Connecting voice…'\n : `Voice ${voice.micMuted ? 'muted' : 'live'}${elapsed ? ` · ${elapsed}` : ''}`}\n </span>\n <TooltipIconButton\n type=\"button\"\n tooltip=\"End voice session\"\n aria-label=\"End voice session\"\n onClick={voice.end}\n >\n <PhoneOffIcon className=\"size-3.5\" />\n </TooltipIconButton>\n </div>\n )}\n {voice.phase === 'live' && <span>{VOICE_LIVE_TYPED_HINT}</span>}\n {voice.activeTask && <span>Athena is working: {voice.activeTask.text}</span>}\n {voice.handoffTask && (\n <span>Voice ended. Athena is still working: {voice.handoffTask.text}</span>\n )}\n {voice.notice && <span role=\"status\">{voice.notice}</span>}\n {voice.error && (\n <span role=\"alert\" className=\"text-destructive\">\n {voice.error}\n </span>\n )}\n </div>\n );\n}\n"],"names":["useAthenaChatVoice"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useStatewireSendCommand, useStatewireRuns, useStatewireState } from "@assistant-ui/react-statewire";
|
|
2
|
-
import { useMemo, useRef, useSyncExternalStore, useEffect, useCallback, useContext,
|
|
2
|
+
import { useMemo, useRef, useSyncExternalStore, useEffect, useCallback, useContext, createContext, createElement } from "react";
|
|
3
3
|
import { useAthenaRunLive } from "./use-athena-run-live.js";
|
|
4
4
|
import { isApprovalCardInterrupt, HITL_PENDING_RESUME_GRACE_MS, readAbandonedInputRequests, findAbandonedInterrupt, asHitlApproval, pendingActionRequests, readInterruptMirror, interruptMirrorPayload, readQueuedLaneDecisions, useApprovalResumeLock, toolCallIdsAwaitingApproval, laneInterruptIds } from "../agent-runtime/src/interrupts.js";
|
|
5
5
|
const NO_QUEUED_LANE_DECISIONS = Object.freeze({});
|
|
@@ -211,6 +211,14 @@ const TiptapComposer = react.forwardRef(
|
|
|
211
211
|
const listRef = react.useRef(null);
|
|
212
212
|
const pendingUploadsRef = react.useRef(new pendingAttachmentUploads.PendingAttachmentUploads());
|
|
213
213
|
const submitInFlightRef = react.useRef(false);
|
|
214
|
+
const interceptionPendingRef = react.useRef(false);
|
|
215
|
+
const disabledRef = react.useRef(disabled);
|
|
216
|
+
disabledRef.current = disabled;
|
|
217
|
+
const setEditorEditable = react.useCallback((ed) => {
|
|
218
|
+
ed.setEditable(!disabledRef.current && !interceptionPendingRef.current, false);
|
|
219
|
+
ed.view.dom.setAttribute("aria-busy", String(interceptionPendingRef.current));
|
|
220
|
+
ed.view.dom.setAttribute("aria-readonly", String(!ed.isEditable));
|
|
221
|
+
}, []);
|
|
214
222
|
const sendBlockedRef = react.useRef(sendBlocked);
|
|
215
223
|
sendBlockedRef.current = sendBlocked;
|
|
216
224
|
const sessionLane = statewireSessionLane.useStatewireSessionLane();
|
|
@@ -287,6 +295,20 @@ const TiptapComposer = react.forwardRef(
|
|
|
287
295
|
const ed = editorRef.current;
|
|
288
296
|
if (!ed) return;
|
|
289
297
|
const { text, draftMarkdown } = composerMessage.captureComposerSend(ed);
|
|
298
|
+
const submittedDocument = ed.state.doc;
|
|
299
|
+
const consumeSubmittedDraft = () => {
|
|
300
|
+
if (ed.state.doc.eq(submittedDocument)) {
|
|
301
|
+
composerHandoff.takeRestoredAttachments({ store: handoff, threadId, assetIdsInDraft: /* @__PURE__ */ new Set() });
|
|
302
|
+
ed.commands.clearContent(false);
|
|
303
|
+
lastEmittedMarkdownRef.current = "";
|
|
304
|
+
composerInput.setText("");
|
|
305
|
+
clearImagePreviews();
|
|
306
|
+
onDraftStateChange == null ? void 0 : onDraftStateChange(false);
|
|
307
|
+
} else {
|
|
308
|
+
composerInput.setText(serializeDraft(ed));
|
|
309
|
+
}
|
|
310
|
+
onSubmitted == null ? void 0 : onSubmitted();
|
|
311
|
+
};
|
|
290
312
|
const waitingOutbox = outboxRef.current;
|
|
291
313
|
const runtimeAttachments = (((_a = aui.composer.getState().attachments) == null ? void 0 : _a.length) ?? 0) > 0;
|
|
292
314
|
const hostExtras = Boolean(hasPendingExtras == null ? void 0 : hasPendingExtras());
|
|
@@ -325,35 +347,34 @@ const TiptapComposer = react.forwardRef(
|
|
|
325
347
|
...attachments.length > 0 && { attachments }
|
|
326
348
|
});
|
|
327
349
|
}
|
|
328
|
-
|
|
329
|
-
lastEmittedMarkdownRef.current = "";
|
|
330
|
-
composerInput.setText("");
|
|
331
|
-
clearImagePreviews();
|
|
332
|
-
onDraftStateChange == null ? void 0 : onDraftStateChange(false);
|
|
333
|
-
onSubmitted == null ? void 0 : onSubmitted();
|
|
350
|
+
consumeSubmittedDraft();
|
|
334
351
|
return;
|
|
335
352
|
}
|
|
336
353
|
if (interceptSubmit) {
|
|
337
354
|
const hasAttachments = (((_b = aui.composer.getState().attachments) == null ? void 0 : _b.length) ?? 0) > 0;
|
|
338
|
-
if (!hasAttachments
|
|
339
|
-
|
|
340
|
-
ed
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
355
|
+
if (!hasAttachments) {
|
|
356
|
+
interceptionPendingRef.current = true;
|
|
357
|
+
setEditorEditable(ed);
|
|
358
|
+
let intercepted;
|
|
359
|
+
try {
|
|
360
|
+
intercepted = await interceptSubmit(text, draftMarkdown);
|
|
361
|
+
} finally {
|
|
362
|
+
interceptionPendingRef.current = false;
|
|
363
|
+
if (!ed.isDestroyed) setEditorEditable(ed);
|
|
364
|
+
}
|
|
365
|
+
if (ed.isDestroyed) return;
|
|
366
|
+
if (intercepted) {
|
|
367
|
+
consumeSubmittedDraft();
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
345
370
|
}
|
|
346
371
|
}
|
|
347
372
|
if (!text) return;
|
|
348
373
|
if (!composerInput.canSend) return;
|
|
349
374
|
composerMessage.prepareComposerSend({ threadId, text, draftMarkdown });
|
|
350
|
-
composerHandoff.takeRestoredAttachments({ store: handoff, threadId, assetIdsInDraft: /* @__PURE__ */ new Set() });
|
|
351
375
|
composerInput.setText(text);
|
|
352
376
|
composerInput.send({ steer: false });
|
|
353
|
-
|
|
354
|
-
clearImagePreviews();
|
|
355
|
-
onDraftStateChange == null ? void 0 : onDraftStateChange(false);
|
|
356
|
-
onSubmitted == null ? void 0 : onSubmitted();
|
|
377
|
+
consumeSubmittedDraft();
|
|
357
378
|
} finally {
|
|
358
379
|
submitInFlightRef.current = false;
|
|
359
380
|
}
|
|
@@ -368,6 +389,7 @@ const TiptapComposer = react.forwardRef(
|
|
|
368
389
|
onAttachmentError,
|
|
369
390
|
onDraftStateChange,
|
|
370
391
|
onSubmitted,
|
|
392
|
+
setEditorEditable,
|
|
371
393
|
threadId,
|
|
372
394
|
waitForHostUploads
|
|
373
395
|
]);
|
|
@@ -461,8 +483,6 @@ const TiptapComposer = react.forwardRef(
|
|
|
461
483
|
},
|
|
462
484
|
[addAttachment, attachAnyFile, fileIntake, onAttachmentError]
|
|
463
485
|
);
|
|
464
|
-
const disabledRef = react.useRef(disabled);
|
|
465
|
-
disabledRef.current = disabled;
|
|
466
486
|
const insertAssetMentionRef = react.useRef(
|
|
467
487
|
() => {
|
|
468
488
|
}
|
|
@@ -560,8 +580,8 @@ const TiptapComposer = react.forwardRef(
|
|
|
560
580
|
});
|
|
561
581
|
editorRef.current = editor;
|
|
562
582
|
react.useEffect(() => {
|
|
563
|
-
editor
|
|
564
|
-
}, [disabled, editor]);
|
|
583
|
+
if (editor && !editor.isDestroyed) setEditorEditable(editor);
|
|
584
|
+
}, [disabled, editor, setEditorEditable]);
|
|
565
585
|
react.useEffect(() => {
|
|
566
586
|
if (!editor || editor.isDestroyed) return;
|
|
567
587
|
editor.view.dispatch(editor.state.tr.setMeta("addToHistory", false));
|