@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.
Files changed (96) hide show
  1. package/README.md +36 -7
  2. package/dist/agent-runtime/src/index.d.ts +2 -2
  3. package/dist/agent-runtime/src/messages.cjs +38 -7
  4. package/dist/agent-runtime/src/messages.cjs.map +1 -1
  5. package/dist/agent-runtime/src/messages.d.ts +26 -8
  6. package/dist/agent-runtime/src/messages.js +38 -7
  7. package/dist/agent-runtime/src/messages.js.map +1 -1
  8. package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs +10 -0
  9. package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -1
  10. package/dist/agent-runtime/src/quota-safe-local-storage-session.d.ts +7 -0
  11. package/dist/agent-runtime/src/quota-safe-local-storage-session.js +10 -0
  12. package/dist/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -1
  13. package/dist/agent-runtime/src/session-lane-scope.cjs +25 -0
  14. package/dist/agent-runtime/src/session-lane-scope.cjs.map +1 -0
  15. package/dist/agent-runtime/src/session-lane-scope.d.ts +59 -0
  16. package/dist/agent-runtime/src/session-lane-scope.js +25 -0
  17. package/dist/agent-runtime/src/session-lane-scope.js.map +1 -0
  18. package/dist/agent-runtime/src/subagent-transcripts.cjs +2 -2
  19. package/dist/agent-runtime/src/subagent-transcripts.cjs.map +1 -1
  20. package/dist/agent-runtime/src/subagent-transcripts.d.ts +2 -2
  21. package/dist/agent-runtime/src/subagent-transcripts.js +3 -3
  22. package/dist/agent-runtime/src/subagent-transcripts.js.map +1 -1
  23. package/dist/agent-runtime/src/tool-names.cjs +4 -0
  24. package/dist/agent-runtime/src/tool-names.cjs.map +1 -1
  25. package/dist/agent-runtime/src/tool-names.d.ts +7 -0
  26. package/dist/agent-runtime/src/tool-names.js +4 -0
  27. package/dist/agent-runtime/src/tool-names.js.map +1 -1
  28. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +18 -1
  29. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
  30. package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +19 -10
  31. package/dist/agent-runtime/src/use-deep-agent-runtime.js +19 -2
  32. package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
  33. package/dist/agent-runtime/src/use-deep-agent-thread.cjs +28 -10
  34. package/dist/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
  35. package/dist/agent-runtime/src/use-deep-agent-thread.d.ts +35 -3
  36. package/dist/agent-runtime/src/use-deep-agent-thread.js +28 -10
  37. package/dist/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
  38. package/dist/agent-runtime/src/use-deep-agent-voice.cjs +20 -0
  39. package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
  40. package/dist/agent-runtime/src/use-deep-agent-voice.js +20 -0
  41. package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
  42. package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
  43. package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
  44. package/dist/agent-runtime/src/voice-presentation.js +2 -0
  45. package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
  46. package/dist/catalog/runtime-catalog.js +1 -1
  47. package/dist/chat/AthenaChat.cjs +125 -103
  48. package/dist/chat/AthenaChat.cjs.map +1 -1
  49. package/dist/chat/AthenaChat.d.ts +9 -0
  50. package/dist/chat/AthenaChat.js +126 -104
  51. package/dist/chat/AthenaChat.js.map +1 -1
  52. package/dist/chat/chat-voice-controls.cjs +128 -0
  53. package/dist/chat/chat-voice-controls.cjs.map +1 -0
  54. package/dist/chat/chat-voice-controls.d.ts +19 -0
  55. package/dist/chat/chat-voice-controls.js +128 -0
  56. package/dist/chat/chat-voice-controls.js.map +1 -0
  57. package/dist/chat/use-statewire-approval-request.js +1 -1
  58. package/dist/chat-ui/composer/tiptap-composer.cjs +42 -22
  59. package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
  60. package/dist/chat-ui/composer/tiptap-composer.d.ts +1 -0
  61. package/dist/chat-ui/composer/tiptap-composer.js +43 -23
  62. package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
  63. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +2 -1
  64. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
  65. package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +3 -1
  66. package/dist/chat-ui/mentions/suggestions/data/api-client.js +2 -1
  67. package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
  68. package/dist/chat-ui/mentions/use-account-workspace-id.cjs +29 -51
  69. package/dist/chat-ui/mentions/use-account-workspace-id.cjs.map +1 -1
  70. package/dist/chat-ui/mentions/use-account-workspace-id.d.ts +2 -2
  71. package/dist/chat-ui/mentions/use-account-workspace-id.js +30 -52
  72. package/dist/chat-ui/mentions/use-account-workspace-id.js.map +1 -1
  73. package/dist/chat-ui/thread/composer-send-or-stop.cjs +2 -1
  74. package/dist/chat-ui/thread/composer-send-or-stop.cjs.map +1 -1
  75. package/dist/chat-ui/thread/composer-send-or-stop.d.ts +2 -0
  76. package/dist/chat-ui/thread/composer-send-or-stop.js +2 -1
  77. package/dist/chat-ui/thread/composer-send-or-stop.js.map +1 -1
  78. package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
  79. package/dist/diagnostics/athena-diagnostics.d.ts +1 -1
  80. package/dist/diagnostics/athena-diagnostics.js.map +1 -1
  81. package/dist/index.d.ts +1 -1
  82. package/dist/react/package.json.cjs +1 -1
  83. package/dist/react/package.json.js +1 -1
  84. package/dist/runtime/statewire-session-key.cjs.map +1 -1
  85. package/dist/runtime/statewire-session-key.d.ts +8 -5
  86. package/dist/runtime/statewire-session-key.js.map +1 -1
  87. package/dist/runtime/useAthenaStatewireRuntime.cjs +5 -2
  88. package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
  89. package/dist/runtime/useAthenaStatewireRuntime.js +5 -2
  90. package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
  91. package/dist/voice/use-chat-voice-enabled.cjs +43 -0
  92. package/dist/voice/use-chat-voice-enabled.cjs.map +1 -0
  93. package/dist/voice/use-chat-voice-enabled.d.ts +9 -0
  94. package/dist/voice/use-chat-voice-enabled.js +43 -0
  95. package/dist/voice/use-chat-voice-enabled.js.map +1 -0
  96. 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":";;;;;;;;;;;;;;AAoBO,SAAS,qBAAqB,EAAE,WAA0D;AAC/F,QAAM,MAAMA,MAAAA,OAAA;AACZ,QAAM,OAAOC,UAAAA,eAAA;AACb,QAAM,YAAYC,eAAAA,oBAAA;AAClB,QAAM,UAAUC,oBAAAA,oBAAoB,EAAE,SAAS,YAAY,OAAO;AAClE,QAAM,WAAWC,QAAAA,OAAgC,IAAI;AACrD,QAAM,QAAQC,QAAAA;AAAAA,IACZ,MAAMC,cAAAA,mCAAmC,EAAE,cAAc,MAAM,SAAS,SAAS;AAAA,IACjF,CAAA;AAAA,EAAC;AAEH,QAAM,SAAS,OAAO,YAAY,WAAW,UAAU;AACvD,QAAM,QAAQC,kBAAAA,kBAAmB;AAAA,IAC/B,WAAW;AAAA,IACX,UAAS,iCAAQ,YAAW;AAAA,IAC5B,OAAO,iCAAQ;AAAA,IACf,eACE,iCAAQ,kBACP,CAAC,UAAUC,kBAAAA,kBAAkB,KAAK,cAAc,EAAE,MAAM,EAAE,GAAG,MAAA,GAAS;AAAA,EAAA,CAC1E;AACD,QAAM,SAAS,MAAM,UAAU,gBAAgB,MAAM,UAAU;AAC/D,QAAM,EAAE,KAAK,YAAA,IAAgB;AAC7B,QAAM,WAAW,KAAK,UAAU;AAAA,IAC9B,KAAK;AAAA,IACL;AAAA,IACA,KAAK,SAASC,qCAAsB,EAAE,YAAY,KAAK,OAAA,CAAQ,IAAI;AAAA,EAAA,CACpE;AACD,QAAM,gBAAgBL,QAAAA,OAAO,EAAE,UAAU,aAAa,KAAK,aAAa;AACxEM,UAAAA,UAAU,MAAM;AACd,UAAM,WAAW,cAAc;AAC/B,UAAM,UACJ,SAAS,aAAa,YACtB;AAAA,MACE,SAAS,eAAe,KAAK,eAAe,SAAS,gBAAgB,KAAK;AAAA,IAAA;AAE9E,kBAAc,UAAU;AAAA,MACtB;AAAA,MACA,aACE,SAAS,aAAa,WAClB,KAAK,eAAe,SAAS,cAC7B,KAAK;AAAA,IAAA;AAEb,QAAI,WAAW,WAAW,CAAC,SAAU,KAAA;AAAA,EACvC,GAAG,CAAC,UAAU,KAAK,aAAa,QAAQ,SAAS,GAAG,CAAC;AAIrD,QAAM,OAAO,MAAM,UAAU;AAC7BA,UAAAA,UAAU,MAAM;;AACd,QAAI,CAAC,KAAM;AACX,QAAI;AACJ,UAAM,WAAW,MAAM;AACrB,mBAAa,KAAK;AAClB,cAAQ,WAAW,aAAa,GAAG;AAAA,IACrC;AACA,aAAA;AACA,UAAM,eAAc,eAAI,cAAa,cAAjB,4BAA6B;AACjD,WAAO,MAAM;AACX,mBAAa,KAAK;AAClB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,KAAK,MAAM,WAAW,CAAC;AAC3B,SAAO,EAAE,OAAO,SAAS,QAAQ,SAAA;AACnC;AAEO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AACF,GAGG;AACD,QAAM,OAAO,MAAM,UAAU;AAC7B,QAAM,aAAa,MAAM,UAAU;AACnC,QAAM,QAAQ,aACV,4BACA,OACA,MAAM,WACJ,sBACA,oBACF;AACJ,SACEC,2BAAAA;AAAAA,IAACC,kBAAAA;AAAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,SAAS;AAAA,MACT,cAAY;AAAA,MACZ,gBAAc,QAAQ,CAAC,MAAM;AAAA,MAC7B,WAAU;AAAA,MACV,UAAU,CAAC,QAAQ,CAAC,cAAc;AAAA,MAClC,SAAS,MAAM;AACb,YAAI,kBAAkB,IAAA;AAAA,iBACb,KAAM,OAAM,YAAY,CAAC,MAAM,QAAQ;AAAA,YAC3C,MAAK,MAAM,MAAA;AAAA,MAClB;AAAA,MAEC,UAAA,4CACEC,YAAAA,aAAA,EAAY,WAAU,uBAAsB,IAC3C,OACF,MAAM,WACJF,2BAAAA,IAACG,YAAAA,YAAA,EAAW,WAAU,SAAA,CAAS,mCAE9BC,YAAAA,SAAA,EAAQ,WAAU,UAAS,IAG9BJ,2BAAAA,IAACK,YAAAA,gBAAA,EAAe,WAAU,SAAA,CAAS;AAAA,IAAA;AAAA,EAAA;AAI3C;AAEO,SAAS,gBAAgB,EAAE,SAA8C;AAC9E,QAAM,UAAUC,kBAAAA,gBAAgB,MAAM,SAAS;AAC/C,QAAM,SAAS,MAAM,UAAU,gBAAgB,MAAM,UAAU;AAC/D,MAAI,CAAC,UAAU,CAAC,MAAM,SAAS,CAAC,MAAM,YAAa,QAAO;AAC1D,SACEC,2BAAAA,KAAC,OAAA,EAAI,WAAU,qFACZ,UAAA;AAAA,IAAA,UACCA,2BAAAA,KAAC,OAAA,EAAI,WAAU,2CAA0C,MAAK,UAC5D,UAAA;AAAA,MAAAP,+BAAC,UACE,UAAA,MAAM,UAAU,eACb,sBACA,SAAS,MAAM,WAAW,UAAU,MAAM,GAAG,UAAU,MAAM,OAAO,KAAK,EAAE,IACjF;AAAA,MACAA,2BAAAA;AAAAA,QAACC,kBAAAA;AAAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,cAAW;AAAA,UACX,SAAS,MAAM;AAAA,UAEf,UAAAD,2BAAAA,IAACQ,YAAAA,cAAA,EAAa,WAAU,WAAA,CAAW;AAAA,QAAA;AAAA,MAAA;AAAA,IACrC,GACF;AAAA,IAED,MAAM,UAAU,UAAUR,2BAAAA,IAAC,UAAM,UAAAS,kBAAAA,uBAAsB;AAAA,IACvD,MAAM,cAAcF,2BAAAA,KAAC,QAAA,EAAK,UAAA;AAAA,MAAA;AAAA,MAAoB,MAAM,WAAW;AAAA,IAAA,GAAK;AAAA,IACpE,MAAM,eACLA,2BAAAA,KAAC,QAAA,EAAK,UAAA;AAAA,MAAA;AAAA,MAAuC,MAAM,YAAY;AAAA,IAAA,GAAK;AAAA,IAErE,MAAM,UAAUP,2BAAAA,IAAC,UAAK,MAAK,UAAU,gBAAM,QAAO;AAAA,IAClD,MAAM,SACLA,+BAAC,QAAA,EAAK,MAAK,SAAQ,WAAU,oBAC1B,UAAA,MAAM,MAAA,CACT;AAAA,EAAA,GAEJ;AAEJ;;;;"}
@@ -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, createElement, createContext } from "react";
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
- ed.commands.clearContent(false);
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 && await interceptSubmit(text, draftMarkdown)) {
339
- composerHandoff.takeRestoredAttachments({ store: handoff, threadId, assetIdsInDraft: /* @__PURE__ */ new Set() });
340
- ed.commands.clearContent(false);
341
- clearImagePreviews();
342
- onDraftStateChange == null ? void 0 : onDraftStateChange(false);
343
- onSubmitted == null ? void 0 : onSubmitted();
344
- return;
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
- ed.commands.clearContent(false);
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 == null ? void 0 : editor.setEditable(!disabled);
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));