@athenaintel/react 0.48.0 → 0.52.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -10
- package/dist/agent-runtime/src/call-recap.cjs +319 -0
- package/dist/agent-runtime/src/call-recap.cjs.map +1 -0
- package/dist/agent-runtime/src/call-recap.d.ts +142 -0
- package/dist/agent-runtime/src/call-recap.js +319 -0
- package/dist/agent-runtime/src/call-recap.js.map +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.cjs +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.cjs.map +1 -1
- package/dist/agent-runtime/src/chat-outbox-store.d.ts +7 -0
- package/dist/agent-runtime/src/chat-outbox-store.js +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.js.map +1 -1
- package/dist/agent-runtime/src/chat-outbox.cjs +46 -5
- package/dist/agent-runtime/src/chat-outbox.cjs.map +1 -1
- package/dist/agent-runtime/src/chat-outbox.d.ts +29 -0
- package/dist/agent-runtime/src/chat-outbox.js +46 -5
- package/dist/agent-runtime/src/chat-outbox.js.map +1 -1
- package/dist/agent-runtime/src/conversation-index.cjs.map +1 -1
- package/dist/agent-runtime/src/conversation-index.d.ts +7 -0
- package/dist/agent-runtime/src/conversation-index.js.map +1 -1
- package/dist/agent-runtime/src/conversation-list.cjs +2 -1
- package/dist/agent-runtime/src/conversation-list.cjs.map +1 -1
- package/dist/agent-runtime/src/conversation-list.d.ts +19 -3
- package/dist/agent-runtime/src/conversation-list.js +2 -1
- package/dist/agent-runtime/src/conversation-list.js.map +1 -1
- package/dist/agent-runtime/src/desktop-handoff.d.ts +10 -0
- package/dist/agent-runtime/src/edit-command.cjs +21 -0
- package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
- package/dist/agent-runtime/src/edit-command.d.ts +8 -0
- package/dist/agent-runtime/src/edit-command.js +21 -0
- package/dist/agent-runtime/src/edit-command.js.map +1 -1
- package/dist/agent-runtime/src/handoff-arrival.cjs +19 -0
- package/dist/agent-runtime/src/handoff-arrival.cjs.map +1 -1
- package/dist/agent-runtime/src/handoff-arrival.d.ts +18 -3
- package/dist/agent-runtime/src/handoff-arrival.js +20 -1
- package/dist/agent-runtime/src/handoff-arrival.js.map +1 -1
- package/dist/agent-runtime/src/handoff-cursor-id.cjs +6 -0
- package/dist/agent-runtime/src/handoff-cursor-id.cjs.map +1 -0
- package/dist/agent-runtime/src/handoff-cursor-id.d.ts +12 -0
- package/dist/agent-runtime/src/handoff-cursor-id.js +6 -0
- package/dist/agent-runtime/src/handoff-cursor-id.js.map +1 -0
- package/dist/agent-runtime/src/handoff-cursor.cjs +88 -0
- package/dist/agent-runtime/src/handoff-cursor.cjs.map +1 -0
- package/dist/agent-runtime/src/handoff-cursor.d.ts +120 -0
- package/dist/agent-runtime/src/handoff-cursor.js +88 -0
- package/dist/agent-runtime/src/handoff-cursor.js.map +1 -0
- package/dist/agent-runtime/src/index.d.ts +9 -7
- package/dist/agent-runtime/src/interrupts.cjs +28 -1
- package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
- package/dist/agent-runtime/src/interrupts.d.ts +21 -1
- package/dist/agent-runtime/src/interrupts.js +28 -1
- package/dist/agent-runtime/src/interrupts.js.map +1 -1
- package/dist/agent-runtime/src/model-context.cjs +3 -2
- package/dist/agent-runtime/src/model-context.cjs.map +1 -1
- package/dist/agent-runtime/src/model-context.d.ts +6 -1
- package/dist/agent-runtime/src/model-context.js +3 -2
- package/dist/agent-runtime/src/model-context.js.map +1 -1
- package/dist/agent-runtime/src/runtime.d.ts +3 -2
- package/dist/agent-runtime/src/send-recovery.cjs +37 -3
- package/dist/agent-runtime/src/send-recovery.cjs.map +1 -1
- package/dist/agent-runtime/src/send-recovery.d.ts +19 -0
- package/dist/agent-runtime/src/send-recovery.js +37 -3
- package/dist/agent-runtime/src/send-recovery.js.map +1 -1
- package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +14 -8
- package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +227 -5
- package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
- package/dist/agent-runtime/src/use-conversation-list.cjs +3 -2
- package/dist/agent-runtime/src/use-conversation-list.cjs.map +1 -1
- package/dist/agent-runtime/src/use-conversation-list.d.ts +6 -0
- package/dist/agent-runtime/src/use-conversation-list.js +3 -2
- package/dist/agent-runtime/src/use-conversation-list.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs +21 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.d.ts +8 -2
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js +21 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +24 -6
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +17 -0
- package/dist/agent-runtime/src/use-deep-agent-runtime.js +24 -6
- package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs +44 -9
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.d.ts +34 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.js +44 -9
- package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs +8 -3
- package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-window-sync.d.ts +7 -0
- package/dist/agent-runtime/src/use-deep-agent-window-sync.js +9 -4
- package/dist/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -1
- package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +13 -2
- package/dist/agent-runtime/src/voice-earcon.cjs +139 -0
- package/dist/agent-runtime/src/voice-earcon.cjs.map +1 -0
- package/dist/agent-runtime/src/voice-earcon.d.ts +38 -0
- package/dist/agent-runtime/src/voice-earcon.js +139 -0
- package/dist/agent-runtime/src/voice-earcon.js.map +1 -0
- package/dist/agent-runtime/src/voice-media.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-media.d.ts +14 -0
- package/dist/agent-runtime/src/voice-media.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/assets/AthenaAssetEmbed.cjs.map +1 -1
- package/dist/assets/AthenaAssetEmbed.d.ts +11 -5
- package/dist/assets/AthenaAssetEmbed.js.map +1 -1
- package/dist/assets/use-asset-embed.cjs.map +1 -1
- package/dist/assets/use-asset-embed.d.ts +12 -5
- package/dist/assets/use-asset-embed.js.map +1 -1
- package/dist/chat/AthenaChat.cjs +27 -12
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.d.ts +13 -0
- package/dist/chat/AthenaChat.js +27 -12
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/AthenaPlanStrip.cjs +97 -0
- package/dist/chat/AthenaPlanStrip.cjs.map +1 -0
- package/dist/chat/AthenaPlanStrip.d.ts +15 -0
- package/dist/chat/AthenaPlanStrip.js +97 -0
- package/dist/chat/AthenaPlanStrip.js.map +1 -0
- package/dist/chat/StatewireApprovalCard.cjs +41 -6
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.d.ts +18 -1
- package/dist/chat/StatewireApprovalCard.js +42 -7
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat/StatewireQueuedMessages.cjs +56 -6
- package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
- package/dist/chat/StatewireQueuedMessages.d.ts +14 -0
- package/dist/chat/StatewireQueuedMessages.js +58 -8
- package/dist/chat/StatewireQueuedMessages.js.map +1 -1
- package/dist/chat/WaitingOnYouBanner.cjs +6 -2
- package/dist/chat/WaitingOnYouBanner.cjs.map +1 -1
- package/dist/chat/WaitingOnYouBanner.js +7 -3
- package/dist/chat/WaitingOnYouBanner.js.map +1 -1
- package/dist/chat/chat-voice-controls.cjs +3 -1
- package/dist/chat/chat-voice-controls.cjs.map +1 -1
- package/dist/chat/chat-voice-controls.d.ts +6 -1
- package/dist/chat/chat-voice-controls.js +3 -1
- package/dist/chat/chat-voice-controls.js.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingCard.cjs +4 -2
- package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingCard.js +4 -2
- package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingTranscript.cjs +4 -2
- package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingTranscript.js +4 -2
- package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.cjs +6 -4
- package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.d.ts +9 -1
- package/dist/chat/super-grouping/activity-timeline.js +6 -4
- package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
- package/dist/chat/super-grouping/index.d.ts +1 -1
- package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs +17 -0
- package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs.map +1 -0
- package/dist/chat/super-grouping/use-earlier-steps-unloaded.d.ts +8 -0
- package/dist/chat/super-grouping/use-earlier-steps-unloaded.js +17 -0
- package/dist/chat/super-grouping/use-earlier-steps-unloaded.js.map +1 -0
- package/dist/chat-ui/index.cjs +11 -0
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.d.ts +3 -1
- package/dist/chat-ui/index.js +12 -1
- package/dist/chat-ui/index.js.map +1 -1
- package/dist/chat-ui/messages/from-the-call-card.cjs +124 -0
- package/dist/chat-ui/messages/from-the-call-card.cjs.map +1 -0
- package/dist/chat-ui/messages/from-the-call-card.d.ts +17 -0
- package/dist/chat-ui/messages/from-the-call-card.js +124 -0
- package/dist/chat-ui/messages/from-the-call-card.js.map +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-link.d.ts +4 -0
- package/dist/chat-ui/rich-text/chat-link.js +4 -3
- package/dist/chat-ui/rich-text/chat-link.js.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.cjs +1 -0
- package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.d.ts +12 -0
- package/dist/chat-ui/rich-text/chat-message-body.js +2 -1
- package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.cjs +4 -0
- package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.d.ts +4 -0
- package/dist/chat-ui/thread/chat-thread-shell.js +4 -0
- package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
- package/dist/chat-ui/thread/handoff-cursor.cjs +216 -0
- package/dist/chat-ui/thread/handoff-cursor.cjs.map +1 -0
- package/dist/chat-ui/thread/handoff-cursor.d.ts +57 -0
- package/dist/chat-ui/thread/handoff-cursor.js +216 -0
- package/dist/chat-ui/thread/handoff-cursor.js.map +1 -0
- package/dist/chat-ui/thread/statewire-history-edge.cjs +2 -0
- package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
- package/dist/chat-ui/thread/statewire-history-edge.d.ts +2 -0
- package/dist/chat-ui/thread/statewire-history-edge.js +3 -1
- package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
- package/dist/collab/client.cjs +15 -3
- package/dist/collab/client.cjs.map +1 -1
- package/dist/collab/client.d.ts +4 -2
- package/dist/collab/client.js +16 -4
- package/dist/collab/client.js.map +1 -1
- package/dist/composer/index.d.ts +1 -1
- package/dist/composer/useReadComposerDraft.cjs +26 -7
- package/dist/composer/useReadComposerDraft.cjs.map +1 -1
- package/dist/composer/useReadComposerDraft.d.ts +23 -1
- package/dist/composer/useReadComposerDraft.js +27 -8
- package/dist/composer/useReadComposerDraft.js.map +1 -1
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +207 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +141 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js +207 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -0
- package/dist/keryx-http/src/update-attributions.cjs +34 -4
- package/dist/keryx-http/src/update-attributions.cjs.map +1 -1
- package/dist/keryx-http/src/update-attributions.d.ts +36 -4
- package/dist/keryx-http/src/update-attributions.js +35 -5
- package/dist/keryx-http/src/update-attributions.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +4 -0
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +11 -1
- package/dist/provider/AthenaProvider.js +4 -0
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/styles.css +1 -1
- package/dist/threads/api.cjs.map +1 -1
- package/dist/threads/api.d.ts +15 -0
- package/dist/threads/api.js.map +1 -1
- package/dist/threads/conversation-list-fetcher.cjs +13 -5
- package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
- package/dist/threads/conversation-list-fetcher.d.ts +4 -1
- package/dist/threads/conversation-list-fetcher.js +13 -5
- package/dist/threads/conversation-list-fetcher.js.map +1 -1
- package/dist/threads/statewire-thread-list.cjs +28 -6
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.d.ts +20 -8
- package/dist/threads/statewire-thread-list.js +28 -6
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/threads/use-athena-sessions.cjs +27 -5
- package/dist/threads/use-athena-sessions.cjs.map +1 -1
- package/dist/threads/use-athena-sessions.d.ts +10 -5
- package/dist/threads/use-athena-sessions.js +28 -6
- package/dist/threads/use-athena-sessions.js.map +1 -1
- package/dist/threads/use-rest-conversation-history.d.ts +1 -0
- package/dist/tools/write-todos-tool-ui.cjs +8 -0
- package/dist/tools/write-todos-tool-ui.cjs.map +1 -1
- package/dist/tools/write-todos-tool-ui.d.ts +2 -0
- package/dist/tools/write-todos-tool-ui.js +8 -0
- package/dist/tools/write-todos-tool-ui.js.map +1 -1
- package/dist/workbench/session-workbench-panes.cjs +1 -8
- package/dist/workbench/session-workbench-panes.cjs.map +1 -1
- package/dist/workbench/session-workbench-panes.js +2 -9
- package/dist/workbench/session-workbench-panes.js.map +1 -1
- package/package.json +2 -2
|
@@ -6,7 +6,7 @@ import { THREAD_VIEWPORT_ATTR } from "../chat-ui/thread/statewire-history-edge.j
|
|
|
6
6
|
import { useAthenaSessionAttention } from "../threads/use-athena-session-attention.js";
|
|
7
7
|
import { useStatewireApprovalRequest } from "./use-statewire-approval-request.js";
|
|
8
8
|
import { hasStatewireThreadExtras } from "../agent-runtime/src/thread-extras.js";
|
|
9
|
-
import { readInterruptMessage } from "../agent-runtime/src/interrupts.js";
|
|
9
|
+
import { pendingApprovalTitle, readInterruptMessage } from "../agent-runtime/src/interrupts.js";
|
|
10
10
|
import { cn } from "cnfast";
|
|
11
11
|
const APPROVAL_CARD_SELECTOR = ".aui-approval-card";
|
|
12
12
|
const WaitingOnYouBanner = (props) => {
|
|
@@ -19,12 +19,16 @@ const WaitingOnYouBanner = (props) => {
|
|
|
19
19
|
};
|
|
20
20
|
const WaitingOnYouBannerWithApproval = ({ attention, fallbackMessage, ...props }) => {
|
|
21
21
|
var _a, _b, _c;
|
|
22
|
-
const { request, hitl, liveHitl } = useStatewireApprovalRequest();
|
|
22
|
+
const { request, hitl, actionRequests, liveHitl, liveActionRequests } = useStatewireApprovalRequest();
|
|
23
23
|
const pending = request !== void 0 || liveHitl !== null;
|
|
24
24
|
const payload = hitl ?? liveHitl;
|
|
25
25
|
const toolName = ((_a = payload == null ? void 0 : payload.context) == null ? void 0 : _a.tool_name) ?? ((_b = payload == null ? void 0 : payload.context) == null ? void 0 : _b.tool_id);
|
|
26
26
|
const pendingCount = ((_c = payload == null ? void 0 : payload.context) == null ? void 0 : _c.pending_action_count) ?? 1;
|
|
27
|
-
const
|
|
27
|
+
const title = pendingApprovalTitle({
|
|
28
|
+
hitl: payload,
|
|
29
|
+
actionRequests: hitl ? actionRequests : liveActionRequests
|
|
30
|
+
});
|
|
31
|
+
const approvalSummary = !pending ? null : title ? title.headline : payload && pendingCount === 1 && toolName ? `${toolName} requires approval before it runs.` : readInterruptMessage(request ? request.payload : liveHitl, fallbackMessage);
|
|
28
32
|
return /* @__PURE__ */ jsx(WaitingOnYouBannerView, { attention, approvalSummary, ...props });
|
|
29
33
|
};
|
|
30
34
|
const WaitingOnYouBannerView = ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WaitingOnYouBanner.js","sources":["../../src/chat/WaitingOnYouBanner.tsx"],"sourcesContent":["import { useAuiState } from '@assistant-ui/react';\nimport {
|
|
1
|
+
{"version":3,"file":"WaitingOnYouBanner.js","sources":["../../src/chat/WaitingOnYouBanner.tsx"],"sourcesContent":["import { useAuiState } from '@assistant-ui/react';\nimport {\n hasStatewireThreadExtras,\n pendingApprovalTitle,\n readInterruptMessage,\n} from '@athenaintel/agent-runtime';\nimport { HandIcon } from 'lucide-react';\nimport { type FC, useCallback, useRef } from 'react';\nimport { THREAD_VIEWPORT_ATTR } from '../chat-ui/thread/statewire-history-edge';\nimport { cn } from '../lib/utils';\nimport {\n type AthenaSessionAttention,\n useAthenaSessionAttention,\n} from '../threads/use-athena-session-attention';\nimport { useStatewireApprovalRequest } from './use-statewire-approval-request';\n\n/** The CSS class `StatewireApprovalCard` puts on its section; the banner scrolls to it. */\nconst APPROVAL_CARD_SELECTOR = '.aui-approval-card';\n\nexport interface WaitingOnYouBannerProps {\n /** Applied to the outer wrapper (host padding); the banner itself is width-capped inside it. */\n className?: string;\n /** Copy shown when a parked interrupt carries no message. */\n fallbackMessage?: string;\n}\n\n/**\n * Persistent top-of-thread banner for a session that is waiting on the\n * viewer: shown while the active thread's attention is `needs_you` (from the\n * thread list) or while the approval hook reports a pending request. The text\n * is the server's next action, else a summary of the pending request; a\n * click scrolls to the approval card (or to the composer when there is no\n * card). It never duplicates the card's decision buttons — the card stays the\n * one place a request is answered. Renders nothing otherwise.\n */\nexport const WaitingOnYouBanner: FC<WaitingOnYouBannerProps> = (props) => {\n const extras = useAuiState((s) => s.thread.extras);\n const attention = useAthenaSessionAttention();\n // `useStatewireApprovalRequest` needs a statewire aui scope (it throws\n // outside one), so the approval leg mounts only once the thread extras are.\n if (!hasStatewireThreadExtras(extras)) {\n return <WaitingOnYouBannerView attention={attention} approvalSummary={null} {...props} />;\n }\n return <WaitingOnYouBannerWithApproval attention={attention} {...props} />;\n};\n\nconst WaitingOnYouBannerWithApproval: FC<\n WaitingOnYouBannerProps & { attention: AthenaSessionAttention | null }\n> = ({ attention, fallbackMessage, ...props }) => {\n const { request, hitl, actionRequests, liveHitl, liveActionRequests } =\n useStatewireApprovalRequest();\n const pending = request !== undefined || liveHitl !== null;\n const payload = hitl ?? liveHitl;\n const toolName = payload?.context?.tool_name ?? payload?.context?.tool_id;\n const pendingCount = payload?.context?.pending_action_count ?? 1;\n // The card's own title, when its lone call's envelope gives it one.\n const title = pendingApprovalTitle({\n hitl: payload,\n actionRequests: hitl ? actionRequests : liveActionRequests,\n });\n const approvalSummary = !pending\n ? null\n : title\n ? title.headline\n : payload && pendingCount === 1 && toolName\n ? `${toolName} requires approval before it runs.`\n : readInterruptMessage(request ? request.payload : liveHitl, fallbackMessage);\n return <WaitingOnYouBannerView attention={attention} approvalSummary={approvalSummary} {...props} />;\n};\n\nexport interface WaitingOnYouBannerViewProps {\n attention: Pick<AthenaSessionAttention, 'attention' | 'nextAction'> | null;\n /** Summary of the pending approval, or `null` when nothing is parked. */\n approvalSummary: string | null;\n className?: string;\n}\n\n/**\n * The banner itself, over already-resolved inputs — the pure presenter\n * `WaitingOnYouBanner` binds to the thread list and the approval hook.\n */\nexport const WaitingOnYouBannerView: FC<WaitingOnYouBannerViewProps> = ({\n attention,\n approvalSummary,\n className,\n}) => {\n const rootRef = useRef<HTMLDivElement>(null);\n const scrollToApproval = useCallback(() => {\n const root = rootRef.current;\n if (!root) return;\n // The nearest ancestor that holds the thread viewport is the chat panel;\n // the card lives in that viewport's sticky footer.\n let scope: HTMLElement | null = root.parentElement;\n while (scope && scope.querySelector(`[${THREAD_VIEWPORT_ATTR}]`) === null) {\n scope = scope.parentElement;\n }\n const container: ParentNode = scope ?? root.ownerDocument;\n const card = container.querySelector<HTMLElement>(APPROVAL_CARD_SELECTOR);\n if (card) {\n card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n return;\n }\n const viewport = container.querySelector<HTMLElement>(`[${THREAD_VIEWPORT_ATTR}]`);\n viewport?.scrollTo({ top: viewport.scrollHeight, behavior: 'smooth' });\n }, []);\n\n const needsYou = attention?.attention === 'needs_you';\n const pending = approvalSummary !== null;\n if (!needsYou && !pending) return null;\n const text =\n attention?.nextAction?.trim() || approvalSummary || 'This session is waiting on you.';\n\n return (\n <div ref={rootRef} className={cn('aui-waiting-on-you', className)}>\n <div\n role=\"status\"\n data-testid=\"waiting-on-you-banner\"\n data-pending-approval={pending || undefined}\n className=\"mx-auto w-full max-w-(--thread-max-width) rounded-xl border border-amber-300 bg-amber-50 text-amber-900\"\n >\n <button\n type=\"button\"\n onClick={scrollToApproval}\n className=\"flex w-full items-center gap-2 px-3 py-2 text-left text-xs transition-colors hover:bg-amber-100/70\"\n >\n <HandIcon className=\"size-4 shrink-0\" aria-hidden=\"true\" />\n <span className=\"shrink-0 font-semibold\">Waiting on you</span>\n <span className=\"min-w-0 flex-1 truncate text-amber-800\">{text}</span>\n {pending ? (\n <span className=\"shrink-0 font-medium underline-offset-2 hover:underline\">\n Review\n </span>\n ) : null}\n </button>\n </div>\n </div>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;AAiBA,MAAM,yBAAyB;AAkBxB,MAAM,qBAAkD,CAAC,UAAU;AACxE,QAAM,SAAS,YAAY,CAAC,MAAM,EAAE,OAAO,MAAM;AACjD,QAAM,YAAY,0BAAA;AAGlB,MAAI,CAAC,yBAAyB,MAAM,GAAG;AACrC,+BAAQ,wBAAA,EAAuB,WAAsB,iBAAiB,MAAO,GAAG,OAAO;AAAA,EACzF;AACA,SAAO,oBAAC,gCAAA,EAA+B,WAAuB,GAAG,MAAA,CAAO;AAC1E;AAEA,MAAM,iCAEF,CAAC,EAAE,WAAW,iBAAiB,GAAG,YAAY;;AAChD,QAAM,EAAE,SAAS,MAAM,gBAAgB,UAAU,mBAAA,IAC/C,4BAAA;AACF,QAAM,UAAU,YAAY,UAAa,aAAa;AACtD,QAAM,UAAU,QAAQ;AACxB,QAAM,aAAW,wCAAS,YAAT,mBAAkB,gBAAa,wCAAS,YAAT,mBAAkB;AAClE,QAAM,iBAAe,wCAAS,YAAT,mBAAkB,yBAAwB;AAE/D,QAAM,QAAQ,qBAAqB;AAAA,IACjC,MAAM;AAAA,IACN,gBAAgB,OAAO,iBAAiB;AAAA,EAAA,CACzC;AACD,QAAM,kBAAkB,CAAC,UACrB,OACA,QACE,MAAM,WACN,WAAW,iBAAiB,KAAK,WAC/B,GAAG,QAAQ,uCACX,qBAAqB,UAAU,QAAQ,UAAU,UAAU,eAAe;AAClF,SAAO,oBAAC,wBAAA,EAAuB,WAAsB,iBAAmC,GAAG,OAAO;AACpG;AAaO,MAAM,yBAA0D,CAAC;AAAA,EACtE;AAAA,EACA;AAAA,EACA;AACF,MAAM;;AACJ,QAAM,UAAU,OAAuB,IAAI;AAC3C,QAAM,mBAAmB,YAAY,MAAM;AACzC,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AAGX,QAAI,QAA4B,KAAK;AACrC,WAAO,SAAS,MAAM,cAAc,IAAI,oBAAoB,GAAG,MAAM,MAAM;AACzE,cAAQ,MAAM;AAAA,IAChB;AACA,UAAM,YAAwB,SAAS,KAAK;AAC5C,UAAM,OAAO,UAAU,cAA2B,sBAAsB;AACxE,QAAI,MAAM;AACR,WAAK,eAAe,EAAE,UAAU,UAAU,OAAO,WAAW;AAC5D;AAAA,IACF;AACA,UAAM,WAAW,UAAU,cAA2B,IAAI,oBAAoB,GAAG;AACjF,yCAAU,SAAS,EAAE,KAAK,SAAS,cAAc,UAAU;EAC7D,GAAG,CAAA,CAAE;AAEL,QAAM,YAAW,uCAAW,eAAc;AAC1C,QAAM,UAAU,oBAAoB;AACpC,MAAI,CAAC,YAAY,CAAC,QAAS,QAAO;AAClC,QAAM,SACJ,4CAAW,eAAX,mBAAuB,WAAU,mBAAmB;AAEtD,SACE,oBAAC,SAAI,KAAK,SAAS,WAAW,GAAG,sBAAsB,SAAS,GAC9D,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,eAAY;AAAA,MACZ,yBAAuB,WAAW;AAAA,MAClC,WAAU;AAAA,MAEV,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAU;AAAA,UAEV,UAAA;AAAA,YAAA,oBAAC,UAAA,EAAS,WAAU,mBAAkB,eAAY,QAAO;AAAA,YACzD,oBAAC,QAAA,EAAK,WAAU,0BAAyB,UAAA,kBAAc;AAAA,YACvD,oBAAC,QAAA,EAAK,WAAU,0CAA0C,UAAA,MAAK;AAAA,YAC9D,UACC,oBAAC,QAAA,EAAK,WAAU,2DAA0D,oBAE1E,IACE;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IACN;AAAA,EAAA,GAEJ;AAEJ;"}
|
|
@@ -12,6 +12,7 @@ const button = require("../ui/button.cjs");
|
|
|
12
12
|
const tooltipIconButton = require("./tooltip-icon-button.cjs");
|
|
13
13
|
const apiClient = require("../chat-ui/mentions/suggestions/data/api-client.cjs");
|
|
14
14
|
const runtimeCatalog = require("../catalog/runtime-catalog.cjs");
|
|
15
|
+
const voiceEarcon = require("../agent-runtime/src/voice-earcon.cjs");
|
|
15
16
|
const voicePresentation = require("../agent-runtime/src/voice-presentation.cjs");
|
|
16
17
|
const cnfast = require("cnfast");
|
|
17
18
|
function useComposerChatVoice({ options }) {
|
|
@@ -29,7 +30,8 @@ function useComposerChatVoice({ options }) {
|
|
|
29
30
|
transport: "livekit",
|
|
30
31
|
livekit: (config == null ? void 0 : config.livekit) ?? media,
|
|
31
32
|
model: config == null ? void 0 : config.model,
|
|
32
|
-
onDiagnostic: (config == null ? void 0 : config.onDiagnostic) ?? ((entry) => athenaDiagnostics.athenaDiagnostics.emit("voice.chat", { data: { ...entry } }))
|
|
33
|
+
onDiagnostic: (config == null ? void 0 : config.onDiagnostic) ?? ((entry) => athenaDiagnostics.athenaDiagnostics.emit("voice.chat", { data: { ...entry } })),
|
|
34
|
+
onCallMoved: (config == null ? void 0 : config.onCallMoved) ?? voiceEarcon.playVoiceMoveEarcon
|
|
33
35
|
});
|
|
34
36
|
const active = voice.phase === "connecting" || voice.phase === "live";
|
|
35
37
|
const { end, syncProfile } = voice;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-voice-controls.cjs","sources":["../../src/chat/chat-voice-controls.tsx"],"sourcesContent":["import { useAui } from '@assistant-ui/react';\nimport {\n describeVoiceLiveElsewhere,\n useVoiceElapsed,\n useVoiceMovedNotice,\n VOICE_CONTINUE_BY_VOICE_LABEL,\n VOICE_LIVE_TYPED_HINT,\n VOICE_MOVE_CALL_HERE_LABEL,\n VOICE_MOVE_HERE_ACTION,\n} from '@athenaintel/agent-runtime';\nimport { HeadphonesIcon, Loader2Icon, MicIcon, MicOffIcon, PhoneIcon, XIcon } 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 { cn } from '../lib/utils';\nimport { Button } from '../ui/button';\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 disabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n disabled: boolean;\n /**\n * Why a voice session can't start now (Athena can't be reached): shown as the tooltip of a\n * control that stays focusable and starts nothing. A session already connecting or live keeps its\n * Cancel and Mute.\n */\n disabledReason?: string | null;\n}) {\n const live = voice.phase === 'live';\n const connecting = voice.phase === 'connecting';\n const unavailable = Boolean(disabledReason) && !live && !connecting;\n // The conversation's call is live on another of the person's devices.\n const moveHere = !live && !connecting && voice.liveElsewhere !== null;\n const label = connecting\n ? 'Cancel voice connection'\n : live\n ? voice.micMuted\n ? 'Unmute microphone'\n : 'Mute microphone'\n : moveHere\n ? VOICE_MOVE_CALL_HERE_LABEL\n : 'Start voice session';\n return (\n <TooltipIconButton\n type=\"button\"\n tooltip={unavailable && disabledReason ? disabledReason : label}\n aria-label={label}\n aria-pressed={live && !voice.micMuted}\n aria-disabled={unavailable || undefined}\n data-unavailable={unavailable || undefined}\n className={cn(\n 'aui-composer-voice size-8 rounded-full p-0 disabled:opacity-30',\n unavailable && 'cursor-not-allowed opacity-30 hover:bg-transparent',\n )}\n disabled={!unavailable && !live && !connecting && disabled}\n onClick={() => {\n if (unavailable) return;\n if (connecting) voice.end();\n else if (live) voice.setMicMuted(!voice.micMuted);\n else if (moveHere) void voice.transferHere();\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 ) : moveHere ? (\n <PhoneIcon className=\"size-4\" />\n ) : (\n <HeadphonesIcon className=\"size-4\" />\n )}\n </TooltipIconButton>\n );\n}\n\n/**\n * A host's \"Continue by voice\" offer for the open conversation: a handover the\n * person sent in voice mode landed here. `onDone` runs once they answer it —\n * took it, dismissed it, or typed instead — so the host clears the offer.\n */\nexport interface AthenaChatContinueByVoice {\n onDone: () => void;\n}\n\n/**\n * The one-tap \"Continue by voice\" above the composer: it starts a call whose\n * model opens by saying where things stand. Shown while voice is idle here and\n * no call is live on another of the person's devices (\"Move here\" covers that).\n * Nothing starts the microphone without the tap.\n */\nexport function ChatVoiceContinueOffer({\n voice,\n offer,\n disabled = false,\n disabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n offer: AthenaChatContinueByVoice | null;\n /** Starting would capture the microphone now (dictation, an upload, no channel). */\n disabled?: boolean;\n /** Why voice can't start now (Athena can't be reached), as the action's tooltip. */\n disabledReason?: string | null;\n}) {\n if (!offer || voice.phase !== 'idle' || voice.liveElsewhere !== null) return null;\n return (\n <div className=\"aui-chat-voice-continue flex items-center gap-1 px-2 pb-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"xs\"\n title={disabledReason ?? undefined}\n disabled={disabled || Boolean(disabledReason)}\n onClick={() => {\n offer.onDone();\n void voice.start({ opening: 'recap' });\n }}\n >\n <MicIcon aria-hidden=\"true\" />\n {VOICE_CONTINUE_BY_VOICE_LABEL}\n </Button>\n <TooltipIconButton\n type=\"button\"\n tooltip=\"Not now\"\n aria-label=\"Not now\"\n className=\"size-6 rounded-md p-0\"\n onClick={offer.onDone}\n >\n <XIcon className=\"size-3\" />\n </TooltipIconButton>\n </div>\n );\n}\n\nexport function ChatVoiceStatus({\n voice,\n offerMove = false,\n moveDisabled = false,\n moveDisabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n /** Whether this composer may take a call from another device (its voice is enabled). */\n offerMove?: boolean;\n /** Moving the call here would capture the microphone now (dictation, an upload, no channel). */\n moveDisabled?: boolean;\n /** Why the call can't move here now (Athena can't be reached), as the action's tooltip. */\n moveDisabledReason?: string | null;\n}) {\n const elapsed = useVoiceElapsed(voice.liveSince);\n const movedNotice = useVoiceMovedNotice(voice.movedTo);\n const active = voice.phase === 'connecting' || voice.phase === 'live';\n // \"Live call on your phone · Move here\", or briefly after this composer's call moved away,\n // \"Call moved to your phone\".\n const elsewhere = offerMove && !active && movedNotice === null ? voice.liveElsewhere : null;\n if (!active && !voice.error && !voice.handoffTask && movedNotice === null && elsewhere === null)\n return null;\n const moveUnavailable = moveDisabled || Boolean(moveDisabledReason);\n return (\n <div className=\"aui-chat-voice-status flex flex-col gap-1 px-2 pb-2 text-xs text-muted-foreground\">\n {/* No end control of its own: the composer's Stop ends the session\n (and any run), so there is one way to stop everything. */}\n {active && (\n <span role=\"status\">\n {voice.phase === 'connecting'\n ? 'Connecting voice…'\n : `Voice ${voice.micMuted ? 'muted' : 'live'}${elapsed ? ` · ${elapsed}` : ''}`}\n </span>\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 {movedNotice !== null && <span role=\"status\">{movedNotice}</span>}\n {elsewhere !== null && (\n <span className=\"aui-chat-voice-elsewhere flex items-center gap-1\">\n <span className=\"min-w-0 truncate\">{describeVoiceLiveElsewhere(elsewhere.surface)}</span>\n <span aria-hidden=\"true\">·</span>\n <button\n type=\"button\"\n title={moveDisabledReason ?? undefined}\n disabled={moveUnavailable}\n onClick={() => void voice.transferHere()}\n className=\"shrink-0 font-medium text-foreground underline-offset-2 hover:underline disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {VOICE_MOVE_HERE_ACTION}\n </button>\n </span>\n )}\n </div>\n );\n}\n"],"names":["useAui","useMentionAuth","useCatalogPrincipal","useChatVoiceEnabled","useRef","useMemo","createDeepAgentVoiceLiveKitAdapter","useAthenaChatVoice","athenaDiagnostics","credentialFingerprint","useEffect","VOICE_MOVE_CALL_HERE_LABEL","jsx","TooltipIconButton","cn","Loader2Icon","MicOffIcon","MicIcon","PhoneIcon","HeadphonesIcon","jsxs","Button","VOICE_CONTINUE_BY_VOICE_LABEL","XIcon","useVoiceElapsed","useVoiceMovedNotice","VOICE_LIVE_TYPED_HINT","describeVoiceLiveElsewhere","VOICE_MOVE_HERE_ACTION"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chat-voice-controls.cjs","sources":["../../src/chat/chat-voice-controls.tsx"],"sourcesContent":["import { useAui } from '@assistant-ui/react';\nimport {\n describeVoiceLiveElsewhere,\n playVoiceMoveEarcon,\n useVoiceElapsed,\n useVoiceMovedNotice,\n VOICE_CONTINUE_BY_VOICE_LABEL,\n VOICE_LIVE_TYPED_HINT,\n VOICE_MOVE_CALL_HERE_LABEL,\n VOICE_MOVE_HERE_ACTION,\n} from '@athenaintel/agent-runtime';\nimport { HeadphonesIcon, Loader2Icon, MicIcon, MicOffIcon, PhoneIcon, XIcon } 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 { cn } from '../lib/utils';\nimport { Button } from '../ui/button';\nimport { TooltipIconButton } from './tooltip-icon-button';\nimport { useMentionAuth } from '../chat-ui/mentions/suggestions/data/api-client';\nimport { credentialFingerprint, useCatalogPrincipal } from '../catalog/runtime-catalog';\n\n/**\n * `onCallMoved` replaces the move earcon a call plays when it moves to or\n * from this device (pass a no-op to keep it silent); by default the shared\n * one plays.\n */\nexport interface AthenaChatVoiceOptions\n extends Pick<UseAthenaChatVoiceOptions, 'model' | 'livekit' | 'onDiagnostic' | 'onCallMoved'> {}\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 onCallMoved: config?.onCallMoved ?? playVoiceMoveEarcon,\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 disabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n disabled: boolean;\n /**\n * Why a voice session can't start now (Athena can't be reached): shown as the tooltip of a\n * control that stays focusable and starts nothing. A session already connecting or live keeps its\n * Cancel and Mute.\n */\n disabledReason?: string | null;\n}) {\n const live = voice.phase === 'live';\n const connecting = voice.phase === 'connecting';\n const unavailable = Boolean(disabledReason) && !live && !connecting;\n // The conversation's call is live on another of the person's devices.\n const moveHere = !live && !connecting && voice.liveElsewhere !== null;\n const label = connecting\n ? 'Cancel voice connection'\n : live\n ? voice.micMuted\n ? 'Unmute microphone'\n : 'Mute microphone'\n : moveHere\n ? VOICE_MOVE_CALL_HERE_LABEL\n : 'Start voice session';\n return (\n <TooltipIconButton\n type=\"button\"\n tooltip={unavailable && disabledReason ? disabledReason : label}\n aria-label={label}\n aria-pressed={live && !voice.micMuted}\n aria-disabled={unavailable || undefined}\n data-unavailable={unavailable || undefined}\n className={cn(\n 'aui-composer-voice size-8 rounded-full p-0 disabled:opacity-30',\n unavailable && 'cursor-not-allowed opacity-30 hover:bg-transparent',\n )}\n disabled={!unavailable && !live && !connecting && disabled}\n onClick={() => {\n if (unavailable) return;\n if (connecting) voice.end();\n else if (live) voice.setMicMuted(!voice.micMuted);\n else if (moveHere) void voice.transferHere();\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 ) : moveHere ? (\n <PhoneIcon className=\"size-4\" />\n ) : (\n <HeadphonesIcon className=\"size-4\" />\n )}\n </TooltipIconButton>\n );\n}\n\n/**\n * A host's \"Continue by voice\" offer for the open conversation: a handover the\n * person sent in voice mode landed here. `onDone` runs once they answer it —\n * took it, dismissed it, or typed instead — so the host clears the offer.\n */\nexport interface AthenaChatContinueByVoice {\n onDone: () => void;\n}\n\n/**\n * The one-tap \"Continue by voice\" above the composer: it starts a call whose\n * model opens by saying where things stand. Shown while voice is idle here and\n * no call is live on another of the person's devices (\"Move here\" covers that).\n * Nothing starts the microphone without the tap.\n */\nexport function ChatVoiceContinueOffer({\n voice,\n offer,\n disabled = false,\n disabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n offer: AthenaChatContinueByVoice | null;\n /** Starting would capture the microphone now (dictation, an upload, no channel). */\n disabled?: boolean;\n /** Why voice can't start now (Athena can't be reached), as the action's tooltip. */\n disabledReason?: string | null;\n}) {\n if (!offer || voice.phase !== 'idle' || voice.liveElsewhere !== null) return null;\n return (\n <div className=\"aui-chat-voice-continue flex items-center gap-1 px-2 pb-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"xs\"\n title={disabledReason ?? undefined}\n disabled={disabled || Boolean(disabledReason)}\n onClick={() => {\n offer.onDone();\n void voice.start({ opening: 'recap' });\n }}\n >\n <MicIcon aria-hidden=\"true\" />\n {VOICE_CONTINUE_BY_VOICE_LABEL}\n </Button>\n <TooltipIconButton\n type=\"button\"\n tooltip=\"Not now\"\n aria-label=\"Not now\"\n className=\"size-6 rounded-md p-0\"\n onClick={offer.onDone}\n >\n <XIcon className=\"size-3\" />\n </TooltipIconButton>\n </div>\n );\n}\n\nexport function ChatVoiceStatus({\n voice,\n offerMove = false,\n moveDisabled = false,\n moveDisabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n /** Whether this composer may take a call from another device (its voice is enabled). */\n offerMove?: boolean;\n /** Moving the call here would capture the microphone now (dictation, an upload, no channel). */\n moveDisabled?: boolean;\n /** Why the call can't move here now (Athena can't be reached), as the action's tooltip. */\n moveDisabledReason?: string | null;\n}) {\n const elapsed = useVoiceElapsed(voice.liveSince);\n const movedNotice = useVoiceMovedNotice(voice.movedTo);\n const active = voice.phase === 'connecting' || voice.phase === 'live';\n // \"Live call on your phone · Move here\", or briefly after this composer's call moved away,\n // \"Call moved to your phone\".\n const elsewhere = offerMove && !active && movedNotice === null ? voice.liveElsewhere : null;\n if (!active && !voice.error && !voice.handoffTask && movedNotice === null && elsewhere === null)\n return null;\n const moveUnavailable = moveDisabled || Boolean(moveDisabledReason);\n return (\n <div className=\"aui-chat-voice-status flex flex-col gap-1 px-2 pb-2 text-xs text-muted-foreground\">\n {/* No end control of its own: the composer's Stop ends the session\n (and any run), so there is one way to stop everything. */}\n {active && (\n <span role=\"status\">\n {voice.phase === 'connecting'\n ? 'Connecting voice…'\n : `Voice ${voice.micMuted ? 'muted' : 'live'}${elapsed ? ` · ${elapsed}` : ''}`}\n </span>\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 {movedNotice !== null && <span role=\"status\">{movedNotice}</span>}\n {elsewhere !== null && (\n <span className=\"aui-chat-voice-elsewhere flex items-center gap-1\">\n <span className=\"min-w-0 truncate\">{describeVoiceLiveElsewhere(elsewhere.surface)}</span>\n <span aria-hidden=\"true\">·</span>\n <button\n type=\"button\"\n title={moveDisabledReason ?? undefined}\n disabled={moveUnavailable}\n onClick={() => void voice.transferHere()}\n className=\"shrink-0 font-medium text-foreground underline-offset-2 hover:underline disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {VOICE_MOVE_HERE_ACTION}\n </button>\n </span>\n )}\n </div>\n );\n}\n"],"names":["useAui","useMentionAuth","useCatalogPrincipal","useChatVoiceEnabled","useRef","useMemo","createDeepAgentVoiceLiveKitAdapter","useAthenaChatVoice","athenaDiagnostics","playVoiceMoveEarcon","credentialFingerprint","useEffect","VOICE_MOVE_CALL_HERE_LABEL","jsx","TooltipIconButton","cn","Loader2Icon","MicOffIcon","MicIcon","PhoneIcon","HeadphonesIcon","jsxs","Button","VOICE_CONTINUE_BY_VOICE_LABEL","XIcon","useVoiceElapsed","useVoiceMovedNotice","VOICE_LIVE_TYPED_HINT","describeVoiceLiveElsewhere","VOICE_MOVE_HERE_ACTION"],"mappings":"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"}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { type UseAthenaChatVoiceOptions, type UseAthenaChatVoiceReturn } from '../voice/chat';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* `onCallMoved` replaces the move earcon a call plays when it moves to or
|
|
4
|
+
* from this device (pass a no-op to keep it silent); by default the shared
|
|
5
|
+
* one plays.
|
|
6
|
+
*/
|
|
7
|
+
export interface AthenaChatVoiceOptions extends Pick<UseAthenaChatVoiceOptions, 'model' | 'livekit' | 'onDiagnostic' | 'onCallMoved'> {
|
|
3
8
|
}
|
|
4
9
|
/** One controller for this composer's controls, text sends and audio sink. */
|
|
5
10
|
export declare function useComposerChatVoice({ options }: {
|
|
@@ -10,6 +10,7 @@ import { Button } from "../ui/button.js";
|
|
|
10
10
|
import { TooltipIconButton } from "./tooltip-icon-button.js";
|
|
11
11
|
import { useMentionAuth } from "../chat-ui/mentions/suggestions/data/api-client.js";
|
|
12
12
|
import { useCatalogPrincipal, credentialFingerprint } from "../catalog/runtime-catalog.js";
|
|
13
|
+
import { playVoiceMoveEarcon } from "../agent-runtime/src/voice-earcon.js";
|
|
13
14
|
import { VOICE_LIVE_TYPED_HINT } from "../agent-runtime/src/voice-presentation.js";
|
|
14
15
|
import { cn } from "cnfast";
|
|
15
16
|
function useComposerChatVoice({ options }) {
|
|
@@ -27,7 +28,8 @@ function useComposerChatVoice({ options }) {
|
|
|
27
28
|
transport: "livekit",
|
|
28
29
|
livekit: (config == null ? void 0 : config.livekit) ?? media,
|
|
29
30
|
model: config == null ? void 0 : config.model,
|
|
30
|
-
onDiagnostic: (config == null ? void 0 : config.onDiagnostic) ?? ((entry) => athenaDiagnostics.emit("voice.chat", { data: { ...entry } }))
|
|
31
|
+
onDiagnostic: (config == null ? void 0 : config.onDiagnostic) ?? ((entry) => athenaDiagnostics.emit("voice.chat", { data: { ...entry } })),
|
|
32
|
+
onCallMoved: (config == null ? void 0 : config.onCallMoved) ?? playVoiceMoveEarcon
|
|
31
33
|
});
|
|
32
34
|
const active = voice.phase === "connecting" || voice.phase === "live";
|
|
33
35
|
const { end, syncProfile } = voice;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-voice-controls.js","sources":["../../src/chat/chat-voice-controls.tsx"],"sourcesContent":["import { useAui } from '@assistant-ui/react';\nimport {\n describeVoiceLiveElsewhere,\n useVoiceElapsed,\n useVoiceMovedNotice,\n VOICE_CONTINUE_BY_VOICE_LABEL,\n VOICE_LIVE_TYPED_HINT,\n VOICE_MOVE_CALL_HERE_LABEL,\n VOICE_MOVE_HERE_ACTION,\n} from '@athenaintel/agent-runtime';\nimport { HeadphonesIcon, Loader2Icon, MicIcon, MicOffIcon, PhoneIcon, XIcon } 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 { cn } from '../lib/utils';\nimport { Button } from '../ui/button';\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 disabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n disabled: boolean;\n /**\n * Why a voice session can't start now (Athena can't be reached): shown as the tooltip of a\n * control that stays focusable and starts nothing. A session already connecting or live keeps its\n * Cancel and Mute.\n */\n disabledReason?: string | null;\n}) {\n const live = voice.phase === 'live';\n const connecting = voice.phase === 'connecting';\n const unavailable = Boolean(disabledReason) && !live && !connecting;\n // The conversation's call is live on another of the person's devices.\n const moveHere = !live && !connecting && voice.liveElsewhere !== null;\n const label = connecting\n ? 'Cancel voice connection'\n : live\n ? voice.micMuted\n ? 'Unmute microphone'\n : 'Mute microphone'\n : moveHere\n ? VOICE_MOVE_CALL_HERE_LABEL\n : 'Start voice session';\n return (\n <TooltipIconButton\n type=\"button\"\n tooltip={unavailable && disabledReason ? disabledReason : label}\n aria-label={label}\n aria-pressed={live && !voice.micMuted}\n aria-disabled={unavailable || undefined}\n data-unavailable={unavailable || undefined}\n className={cn(\n 'aui-composer-voice size-8 rounded-full p-0 disabled:opacity-30',\n unavailable && 'cursor-not-allowed opacity-30 hover:bg-transparent',\n )}\n disabled={!unavailable && !live && !connecting && disabled}\n onClick={() => {\n if (unavailable) return;\n if (connecting) voice.end();\n else if (live) voice.setMicMuted(!voice.micMuted);\n else if (moveHere) void voice.transferHere();\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 ) : moveHere ? (\n <PhoneIcon className=\"size-4\" />\n ) : (\n <HeadphonesIcon className=\"size-4\" />\n )}\n </TooltipIconButton>\n );\n}\n\n/**\n * A host's \"Continue by voice\" offer for the open conversation: a handover the\n * person sent in voice mode landed here. `onDone` runs once they answer it —\n * took it, dismissed it, or typed instead — so the host clears the offer.\n */\nexport interface AthenaChatContinueByVoice {\n onDone: () => void;\n}\n\n/**\n * The one-tap \"Continue by voice\" above the composer: it starts a call whose\n * model opens by saying where things stand. Shown while voice is idle here and\n * no call is live on another of the person's devices (\"Move here\" covers that).\n * Nothing starts the microphone without the tap.\n */\nexport function ChatVoiceContinueOffer({\n voice,\n offer,\n disabled = false,\n disabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n offer: AthenaChatContinueByVoice | null;\n /** Starting would capture the microphone now (dictation, an upload, no channel). */\n disabled?: boolean;\n /** Why voice can't start now (Athena can't be reached), as the action's tooltip. */\n disabledReason?: string | null;\n}) {\n if (!offer || voice.phase !== 'idle' || voice.liveElsewhere !== null) return null;\n return (\n <div className=\"aui-chat-voice-continue flex items-center gap-1 px-2 pb-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"xs\"\n title={disabledReason ?? undefined}\n disabled={disabled || Boolean(disabledReason)}\n onClick={() => {\n offer.onDone();\n void voice.start({ opening: 'recap' });\n }}\n >\n <MicIcon aria-hidden=\"true\" />\n {VOICE_CONTINUE_BY_VOICE_LABEL}\n </Button>\n <TooltipIconButton\n type=\"button\"\n tooltip=\"Not now\"\n aria-label=\"Not now\"\n className=\"size-6 rounded-md p-0\"\n onClick={offer.onDone}\n >\n <XIcon className=\"size-3\" />\n </TooltipIconButton>\n </div>\n );\n}\n\nexport function ChatVoiceStatus({\n voice,\n offerMove = false,\n moveDisabled = false,\n moveDisabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n /** Whether this composer may take a call from another device (its voice is enabled). */\n offerMove?: boolean;\n /** Moving the call here would capture the microphone now (dictation, an upload, no channel). */\n moveDisabled?: boolean;\n /** Why the call can't move here now (Athena can't be reached), as the action's tooltip. */\n moveDisabledReason?: string | null;\n}) {\n const elapsed = useVoiceElapsed(voice.liveSince);\n const movedNotice = useVoiceMovedNotice(voice.movedTo);\n const active = voice.phase === 'connecting' || voice.phase === 'live';\n // \"Live call on your phone · Move here\", or briefly after this composer's call moved away,\n // \"Call moved to your phone\".\n const elsewhere = offerMove && !active && movedNotice === null ? voice.liveElsewhere : null;\n if (!active && !voice.error && !voice.handoffTask && movedNotice === null && elsewhere === null)\n return null;\n const moveUnavailable = moveDisabled || Boolean(moveDisabledReason);\n return (\n <div className=\"aui-chat-voice-status flex flex-col gap-1 px-2 pb-2 text-xs text-muted-foreground\">\n {/* No end control of its own: the composer's Stop ends the session\n (and any run), so there is one way to stop everything. */}\n {active && (\n <span role=\"status\">\n {voice.phase === 'connecting'\n ? 'Connecting voice…'\n : `Voice ${voice.micMuted ? 'muted' : 'live'}${elapsed ? ` · ${elapsed}` : ''}`}\n </span>\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 {movedNotice !== null && <span role=\"status\">{movedNotice}</span>}\n {elsewhere !== null && (\n <span className=\"aui-chat-voice-elsewhere flex items-center gap-1\">\n <span className=\"min-w-0 truncate\">{describeVoiceLiveElsewhere(elsewhere.surface)}</span>\n <span aria-hidden=\"true\">·</span>\n <button\n type=\"button\"\n title={moveDisabledReason ?? undefined}\n disabled={moveUnavailable}\n onClick={() => void voice.transferHere()}\n className=\"shrink-0 font-medium text-foreground underline-offset-2 hover:underline disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {VOICE_MOVE_HERE_ACTION}\n </button>\n </span>\n )}\n </div>\n );\n}\n"],"names":["useAthenaChatVoice"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chat-voice-controls.js","sources":["../../src/chat/chat-voice-controls.tsx"],"sourcesContent":["import { useAui } from '@assistant-ui/react';\nimport {\n describeVoiceLiveElsewhere,\n playVoiceMoveEarcon,\n useVoiceElapsed,\n useVoiceMovedNotice,\n VOICE_CONTINUE_BY_VOICE_LABEL,\n VOICE_LIVE_TYPED_HINT,\n VOICE_MOVE_CALL_HERE_LABEL,\n VOICE_MOVE_HERE_ACTION,\n} from '@athenaintel/agent-runtime';\nimport { HeadphonesIcon, Loader2Icon, MicIcon, MicOffIcon, PhoneIcon, XIcon } 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 { cn } from '../lib/utils';\nimport { Button } from '../ui/button';\nimport { TooltipIconButton } from './tooltip-icon-button';\nimport { useMentionAuth } from '../chat-ui/mentions/suggestions/data/api-client';\nimport { credentialFingerprint, useCatalogPrincipal } from '../catalog/runtime-catalog';\n\n/**\n * `onCallMoved` replaces the move earcon a call plays when it moves to or\n * from this device (pass a no-op to keep it silent); by default the shared\n * one plays.\n */\nexport interface AthenaChatVoiceOptions\n extends Pick<UseAthenaChatVoiceOptions, 'model' | 'livekit' | 'onDiagnostic' | 'onCallMoved'> {}\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 onCallMoved: config?.onCallMoved ?? playVoiceMoveEarcon,\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 disabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n disabled: boolean;\n /**\n * Why a voice session can't start now (Athena can't be reached): shown as the tooltip of a\n * control that stays focusable and starts nothing. A session already connecting or live keeps its\n * Cancel and Mute.\n */\n disabledReason?: string | null;\n}) {\n const live = voice.phase === 'live';\n const connecting = voice.phase === 'connecting';\n const unavailable = Boolean(disabledReason) && !live && !connecting;\n // The conversation's call is live on another of the person's devices.\n const moveHere = !live && !connecting && voice.liveElsewhere !== null;\n const label = connecting\n ? 'Cancel voice connection'\n : live\n ? voice.micMuted\n ? 'Unmute microphone'\n : 'Mute microphone'\n : moveHere\n ? VOICE_MOVE_CALL_HERE_LABEL\n : 'Start voice session';\n return (\n <TooltipIconButton\n type=\"button\"\n tooltip={unavailable && disabledReason ? disabledReason : label}\n aria-label={label}\n aria-pressed={live && !voice.micMuted}\n aria-disabled={unavailable || undefined}\n data-unavailable={unavailable || undefined}\n className={cn(\n 'aui-composer-voice size-8 rounded-full p-0 disabled:opacity-30',\n unavailable && 'cursor-not-allowed opacity-30 hover:bg-transparent',\n )}\n disabled={!unavailable && !live && !connecting && disabled}\n onClick={() => {\n if (unavailable) return;\n if (connecting) voice.end();\n else if (live) voice.setMicMuted(!voice.micMuted);\n else if (moveHere) void voice.transferHere();\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 ) : moveHere ? (\n <PhoneIcon className=\"size-4\" />\n ) : (\n <HeadphonesIcon className=\"size-4\" />\n )}\n </TooltipIconButton>\n );\n}\n\n/**\n * A host's \"Continue by voice\" offer for the open conversation: a handover the\n * person sent in voice mode landed here. `onDone` runs once they answer it —\n * took it, dismissed it, or typed instead — so the host clears the offer.\n */\nexport interface AthenaChatContinueByVoice {\n onDone: () => void;\n}\n\n/**\n * The one-tap \"Continue by voice\" above the composer: it starts a call whose\n * model opens by saying where things stand. Shown while voice is idle here and\n * no call is live on another of the person's devices (\"Move here\" covers that).\n * Nothing starts the microphone without the tap.\n */\nexport function ChatVoiceContinueOffer({\n voice,\n offer,\n disabled = false,\n disabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n offer: AthenaChatContinueByVoice | null;\n /** Starting would capture the microphone now (dictation, an upload, no channel). */\n disabled?: boolean;\n /** Why voice can't start now (Athena can't be reached), as the action's tooltip. */\n disabledReason?: string | null;\n}) {\n if (!offer || voice.phase !== 'idle' || voice.liveElsewhere !== null) return null;\n return (\n <div className=\"aui-chat-voice-continue flex items-center gap-1 px-2 pb-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"xs\"\n title={disabledReason ?? undefined}\n disabled={disabled || Boolean(disabledReason)}\n onClick={() => {\n offer.onDone();\n void voice.start({ opening: 'recap' });\n }}\n >\n <MicIcon aria-hidden=\"true\" />\n {VOICE_CONTINUE_BY_VOICE_LABEL}\n </Button>\n <TooltipIconButton\n type=\"button\"\n tooltip=\"Not now\"\n aria-label=\"Not now\"\n className=\"size-6 rounded-md p-0\"\n onClick={offer.onDone}\n >\n <XIcon className=\"size-3\" />\n </TooltipIconButton>\n </div>\n );\n}\n\nexport function ChatVoiceStatus({\n voice,\n offerMove = false,\n moveDisabled = false,\n moveDisabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n /** Whether this composer may take a call from another device (its voice is enabled). */\n offerMove?: boolean;\n /** Moving the call here would capture the microphone now (dictation, an upload, no channel). */\n moveDisabled?: boolean;\n /** Why the call can't move here now (Athena can't be reached), as the action's tooltip. */\n moveDisabledReason?: string | null;\n}) {\n const elapsed = useVoiceElapsed(voice.liveSince);\n const movedNotice = useVoiceMovedNotice(voice.movedTo);\n const active = voice.phase === 'connecting' || voice.phase === 'live';\n // \"Live call on your phone · Move here\", or briefly after this composer's call moved away,\n // \"Call moved to your phone\".\n const elsewhere = offerMove && !active && movedNotice === null ? voice.liveElsewhere : null;\n if (!active && !voice.error && !voice.handoffTask && movedNotice === null && elsewhere === null)\n return null;\n const moveUnavailable = moveDisabled || Boolean(moveDisabledReason);\n return (\n <div className=\"aui-chat-voice-status flex flex-col gap-1 px-2 pb-2 text-xs text-muted-foreground\">\n {/* No end control of its own: the composer's Stop ends the session\n (and any run), so there is one way to stop everything. */}\n {active && (\n <span role=\"status\">\n {voice.phase === 'connecting'\n ? 'Connecting voice…'\n : `Voice ${voice.micMuted ? 'muted' : 'live'}${elapsed ? ` · ${elapsed}` : ''}`}\n </span>\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 {movedNotice !== null && <span role=\"status\">{movedNotice}</span>}\n {elsewhere !== null && (\n <span className=\"aui-chat-voice-elsewhere flex items-center gap-1\">\n <span className=\"min-w-0 truncate\">{describeVoiceLiveElsewhere(elsewhere.surface)}</span>\n <span aria-hidden=\"true\">·</span>\n <button\n type=\"button\"\n title={moveDisabledReason ?? undefined}\n disabled={moveUnavailable}\n onClick={() => void voice.transferHere()}\n className=\"shrink-0 font-medium text-foreground underline-offset-2 hover:underline disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {VOICE_MOVE_HERE_ACTION}\n </button>\n </span>\n )}\n </div>\n );\n}\n"],"names":["useAthenaChatVoice"],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@ const ToolTimeline = require("./ToolTimeline.cjs");
|
|
|
15
15
|
const timelineStep = require("./timeline-step.cjs");
|
|
16
16
|
const toolkitMap = require("./toolkit-map.cjs");
|
|
17
17
|
const toolPartPlacement = require("./tool-part-placement.cjs");
|
|
18
|
+
const useEarlierStepsUnloaded = require("./use-earlier-steps-unloaded.cjs");
|
|
18
19
|
const cnfast = require("cnfast");
|
|
19
20
|
const DEFAULT_MAX_VISIBLE_STEPS = 3;
|
|
20
21
|
const MAX_REMEMBERED_EXPANSIONS = 200;
|
|
@@ -126,6 +127,7 @@ const SuperGroupingCard = require$$0.memo(function SuperGroupingCard2({
|
|
|
126
127
|
classNames
|
|
127
128
|
}) {
|
|
128
129
|
const messageId = react.useAuiState((s) => s.message.id);
|
|
130
|
+
const earlierStepsUnloaded = useEarlierStepsUnloaded.useEarlierStepsUnloaded();
|
|
129
131
|
const [selectActivityContent] = require$$0.useState(createActivityContentSelector);
|
|
130
132
|
const messageContent = react.useAuiState(selectActivityContent);
|
|
131
133
|
const messageStatus = react.useAuiState((s) => s.message.status);
|
|
@@ -197,7 +199,7 @@ const SuperGroupingCard = require$$0.memo(function SuperGroupingCard2({
|
|
|
197
199
|
}),
|
|
198
200
|
[toolUIs, ToolFallbackComponent, TextComponent, ReasoningComponent, GroupComponent]
|
|
199
201
|
);
|
|
200
|
-
const hasActivity = timeline.length > 0 || phase !== "complete";
|
|
202
|
+
const hasActivity = timeline.length > 0 || phase !== "complete" || earlierStepsUnloaded;
|
|
201
203
|
if (!hasActivity) return null;
|
|
202
204
|
const detailsMounted = isActive || mountDetails || expanded && detailsExpanded;
|
|
203
205
|
const activeSteps = steps.filter((step) => step.state === "running");
|
|
@@ -206,7 +208,7 @@ const SuperGroupingCard = require$$0.memo(function SuperGroupingCard2({
|
|
|
206
208
|
currentStep.verb,
|
|
207
209
|
currentStep.chip,
|
|
208
210
|
activeSteps.length > 1 ? `${activeSteps.length} in progress` : null
|
|
209
|
-
].filter(Boolean).join(" · ") : steps.length > 0 ? "Preparing response" : "Thinking" : activityTimeline.getActivitySummary(timeline);
|
|
211
|
+
].filter(Boolean).join(" · ") : steps.length > 0 ? "Preparing response" : "Thinking" : activityTimeline.getActivitySummary(timeline, { earlierStepsUnloaded });
|
|
210
212
|
const label = [latestStatus, ...isActive ? [] : summaryDetails ?? []].join(" · ");
|
|
211
213
|
const renderStepRow = stepRowParts.size > 0 ? (step) => {
|
|
212
214
|
const index = step.kind === "tool" ? stepRowParts.get(step.id) : void 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SuperGroupingCard.cjs","sources":["../../../src/chat/super-grouping/SuperGroupingCard.tsx"],"sourcesContent":["'use client';\n\nimport type {\n AssistantState,\n ReasoningMessagePartComponent,\n TextMessagePartComponent,\n ThreadAssistantMessagePart,\n ToolCallMessagePart,\n ToolCallMessagePartComponent,\n ToolCallMessagePartProps,\n} from '@assistant-ui/react';\nimport { MessagePrimitive, useAuiState } from '@assistant-ui/react';\nimport { ChevronRight } from 'lucide-react';\nimport { type ComponentType, memo, type ReactNode, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport { cn } from '../../lib/utils';\nimport { isSdkInlineTool } from '../../tools/inline-tools';\nimport { ToolFallback } from '../../tools/tool-fallback';\nimport { collectAssetTitles } from '../../tools/tool-summary';\nimport {\n type ActivityItem,\n type ActivityPhase,\n buildActivityTimeline,\n countActivityTools,\n excludeFinalTextFromGroups,\n excludePartsFromGroups,\n getActivitySummary,\n getFinalTextParts,\n resolveActivityPhase,\n} from './activity-timeline';\nimport { CollapsibleGroup, type CollapsibleGroupProps, SuperGroupingProvider } from './CollapsibleGroup';\nimport { GroupedMessageParts } from './GroupedMessageParts';\nimport { createToolBasedGroupingFunction, type GroupingFunction, isGroupedToolCallPart } from './grouping-function';\nimport { ToolTimeline } from './ToolTimeline';\nimport { type TimelineStep, toTimelineStep } from './timeline-step';\nimport { defaultGetToolGroupKey, type GetToolGroupKey } from './toolkit-map';\nimport { ToolPartPlacementProvider } from './tool-part-placement';\n\nconst DEFAULT_MAX_VISIBLE_STEPS = 3;\nconst MAX_REMEMBERED_EXPANSIONS = 200;\nconst EMPTY_TIMELINE: ActivityItem[] = [];\nconst EMPTY_CONTENT: readonly ThreadAssistantMessagePart[] = [];\nconst NO_STEP_ROW_TOOLS = (): boolean => false;\n\n/**\n * Whether `next` differs from `previous` only in the words of its last part, a text or reasoning\n * part that is still being written: same length, every earlier part the identical object, and the\n * last parts of the same kind.\n */\nfunction onlyLastPartGrew({\n previous,\n next,\n}: {\n previous: readonly ThreadAssistantMessagePart[];\n next: readonly ThreadAssistantMessagePart[];\n}): boolean {\n if (previous === next) return true;\n if (previous.length === 0 || previous.length !== next.length) return false;\n const lastIndex = next.length - 1;\n const kind = next[lastIndex]?.type;\n if ((kind !== 'text' && kind !== 'reasoning') || previous[lastIndex]?.type !== kind) return false;\n for (let index = 0; index < lastIndex; index += 1) {\n if (previous[index] !== next[index]) return false;\n }\n return true;\n}\n\n/**\n * The card's read of its message's content: the content itself, except that while the message is\n * generating it keeps the previous array when only the last text or reasoning part grew. The card\n * builds its steps from tool calls, their results and reasoning steps, never from those words (the\n * answer renders below it, `SuperGroupingFinalText`), and the turn being generated changes its last\n * part on every streamed token; earlier parts keep their objects across a token (the runtime's\n * conversion cache), so the card re-renders when a step changes, not per token. Any other change,\n * and the run ending, hand over the current content.\n */\nfunction createActivityContentSelector(): (\n state: AssistantState,\n) => readonly ThreadAssistantMessagePart[] {\n let last: readonly ThreadAssistantMessagePart[] = EMPTY_CONTENT;\n return (state) => {\n const message = state.message;\n const content = message.role === 'assistant' ? message.content : EMPTY_CONTENT;\n if (message.status?.type === 'running' && onlyLastPartGrew({ previous: last, next: content })) {\n return last;\n }\n last = content;\n return content;\n };\n}\nconst RUNNING_STATUS = { type: 'running' } as const;\nconst COMPLETE_STATUS = { type: 'complete' } as const;\n\n// ─── Disclosure memory ─────────────────────────────────────────────────\n\n/**\n * Session-local memory of which cards the user opened, keyed by the host's\n * expansion key (the message id by default; the Chrome extension keys by\n * thread + position so a streaming message keeps its choice when it receives\n * its stable id). Bounded so a long session cannot grow it without limit.\n */\nconst rememberedExpansions = new Map<string, boolean>();\n\nfunction rememberExpansion(key: string, expanded: boolean): void {\n rememberedExpansions.delete(key);\n rememberedExpansions.set(key, expanded);\n while (rememberedExpansions.size > MAX_REMEMBERED_EXPANSIONS) {\n const oldestKey = rememberedExpansions.keys().next().value;\n if (oldestKey === undefined) break;\n rememberedExpansions.delete(oldestKey);\n }\n}\n\nexport function resetRememberedExpansionsForTests(): void {\n rememberedExpansions.clear();\n}\n\n/**\n * The details disclosure: remembered per expansion key, and — when the host\n * opts in — opened once when the turn starts waiting on the person, so the\n * approval controls inside the details are visible without a click.\n */\nexport function useActivityExpansion({\n expansionKey,\n phase,\n expandWhileWaiting = false,\n}: {\n expansionKey: string;\n phase: ActivityPhase;\n expandWhileWaiting?: boolean;\n}) {\n const [userExpanded, setUserExpanded] = useState<boolean | undefined>(() =>\n rememberedExpansions.get(expansionKey),\n );\n const [waitingAutoExpanded, setWaitingAutoExpanded] = useState(false);\n const previousRef = useRef({ expansionKey, phase });\n\n useEffect(() => {\n setUserExpanded(rememberedExpansions.get(expansionKey));\n setWaitingAutoExpanded(false);\n }, [expansionKey]);\n\n useEffect(() => {\n const previous = previousRef.current;\n if (expandWhileWaiting && previous.expansionKey === expansionKey) {\n if (previous.phase !== 'waiting' && phase === 'waiting') {\n setWaitingAutoExpanded(true);\n } else if (previous.phase === 'waiting' && phase !== 'waiting') {\n setWaitingAutoExpanded(false);\n }\n }\n previousRef.current = { expansionKey, phase };\n }, [expansionKey, phase, expandWhileWaiting]);\n\n const expanded = waitingAutoExpanded || (userExpanded ?? (expandWhileWaiting && phase === 'waiting'));\n const toggleExpanded = () => {\n const next = !expanded;\n setWaitingAutoExpanded(false);\n rememberExpansion(expansionKey, next);\n setUserExpanded(next);\n };\n\n return { expanded, toggleExpanded };\n}\n\n// ─── Card ──────────────────────────────────────────────────────────────\n\nexport interface SuperGroupingCardProps {\n /** Tool UIs to render inside the details panel, by tool name (registered tool UIs still apply). */\n toolUIs?: Record<string, ToolCallMessagePartComponent>;\n /** Fallback for tools without a UI; the SDK's `ToolFallback` by default. */\n ToolFallback?: ComponentType<ToolCallMessagePartProps>;\n /** Text renderer for the details panel. */\n TextComponent: TextMessagePartComponent;\n /** Reasoning renderer for the details panel. */\n ReasoningComponent?: ReasoningMessagePartComponent;\n /** Per-toolkit block inside the details panel; the SDK's `CollapsibleGroup` by default. */\n GroupComponent?: ComponentType<CollapsibleGroupProps>;\n /** Toolkit lookup driving the per-toolkit sub-grouping; the SDK's static map by default. */\n getToolGroupKey?: GetToolGroupKey;\n /** A complete grouping function, when the host builds its own (overrides `getToolGroupKey`). */\n groupingFunction?: GroupingFunction;\n /** Tools whose unresolved call means the turn is waiting on the person (see `buildActivityTimeline`). */\n waitingTools?: ReadonlySet<string>;\n /**\n * Calls the run is parked on, by tool-call id: each one still without its\n * real result reads as waiting (see `buildActivityTimeline`). Keep the set's\n * identity stable while its ids are unchanged.\n */\n waitingToolCallIds?: ReadonlySet<string>;\n /** Tools whose result is a relayed application payload (see `buildActivityTimeline`). */\n passThroughTools?: ReadonlySet<string>;\n /**\n * Tools whose card renders below the header, outside both disclosures, and\n * stays mounted (an editable email draft, a UI test report). `isSdkInlineTool`\n * by default: the registry's `inline` flag plus the SDK's own report cards.\n * The call itself comes second, for a host whose choice depends on what the\n * call did (Olympus keeps an `eval` there only when it dispatched subagents).\n * The card renders it under `useToolPartPlacement()` `'inline'`.\n */\n isInlineTool?: (toolName: string, part: ToolCallMessagePart) => boolean;\n /**\n * Tools whose card is their own step's row in the step list, drawn in place\n * of the status line (Olympus's one-line code runs) and kept out of the\n * details. The card renders it under `useToolPartPlacement()` `'step'`, kept\n * mounted while the step list is (the turn is live, or `mountDetails`); a\n * call that is inline too renders in both places. None by default.\n */\n isStepRowTool?: (toolName: string) => boolean;\n /**\n * Host assertion that the turn is waiting on the person. The SDK derives waiting\n * from `requires-action` and waiting tools itself; no in-repo host passes this.\n */\n waitingForInput?: boolean;\n /** Names tools the shared registry has no entry for (Olympus's tool catalog). */\n resolveToolDisplayName?: (toolName: string) => string | undefined;\n /** Key the disclosure choice is remembered under; the message id by default. */\n expansionKey?: string;\n /** Open the details once when the turn starts waiting on the person. */\n expandWhileWaiting?: boolean;\n /**\n * Keep the details panel mounted (hidden) while collapsed, so a host that\n * needs to find content inside it (a deep link into the run's steps) can.\n */\n mountDetails?: boolean;\n /** Latest steps shown when expanded; unresolved parallel calls always remain visible. */\n maxVisibleSteps?: number;\n /** Extra segments after the collapsed summary (\"1m 23s\", \"12.4k tokens\"), joined with \" · \". */\n summaryDetails?: readonly string[];\n /** Header controls beside the title (a stop button while running, a changes button once done). */\n renderHeaderActions?: (context: {\n phase: ActivityPhase;\n messageContent: readonly ThreadAssistantMessagePart[];\n }) => ReactNode;\n /**\n * Inline on the header row, right of the status, while the turn is running\n * (an elapsed counter). Not rendered while the turn waits on the person.\n */\n renderRunningMeta?: (context: { phase: ActivityPhase }) => ReactNode;\n /** The host's icon for a step's tool, shown on its status row (see `ToolTimeline`). */\n renderStepIcon?: (step: TimelineStep) => ReactNode;\n /** Show the favicons of the sites a finished step's result lists. */\n showSourceFavicons?: boolean;\n className?: string;\n classNames?: {\n root?: string;\n details?: string;\n /** The flex column the run's parts render in; override its gap here. */\n detailsParts?: string;\n /** The flex column the inline tool calls render in, below the header; override its gap here. */\n inlineTools?: string;\n };\n}\n\n/**\n * The collapsed view (previously called the conversational view) of an\n * assistant turn: a compact current action, an expandable tool timeline, and\n * the original interactive tool UIs behind a separate details disclosure. The\n * answer itself renders below the card (`SuperGroupingFinalText`). Shared by\n * the SDK's `AthenaChat`, Olympus, and the Chrome extension, which supply\n * their own text/tool renderers and the host-specific controls through the\n * slots.\n *\n * The details panel stays mounted (visually hidden) while the turn is live so\n * the tool UIs inside run their effects — an asset-streaming card's follow\n * mode must not wait for \"Show details\". A host can take a call out of the\n * details to render its tool UI in the open (`isInlineTool`) or as its step's\n * row (`isStepRowTool`); the tool UI reads which (`useToolPartPlacement`).\n *\n * Selector discipline: raw reads only (`message.status`, `message.id`, and the\n * content through `createActivityContentSelector`, which hands back the\n * message's own array or the previous one, never a new array); every\n * derivation runs in `useMemo` — a selector that returned a fresh array would\n * tear through `useSyncExternalStore`.\n */\nexport const SuperGroupingCard = memo(function SuperGroupingCard({\n toolUIs,\n ToolFallback: ToolFallbackComponent = ToolFallback,\n TextComponent,\n ReasoningComponent,\n GroupComponent = CollapsibleGroup,\n getToolGroupKey = defaultGetToolGroupKey,\n groupingFunction,\n waitingTools,\n waitingToolCallIds,\n passThroughTools,\n isInlineTool = isSdkInlineTool,\n isStepRowTool = NO_STEP_ROW_TOOLS,\n waitingForInput = false,\n resolveToolDisplayName,\n expansionKey: expansionKeyProp,\n expandWhileWaiting = false,\n mountDetails = false,\n maxVisibleSteps = DEFAULT_MAX_VISIBLE_STEPS,\n summaryDetails,\n renderStepIcon,\n showSourceFavicons,\n renderHeaderActions,\n renderRunningMeta,\n className,\n classNames,\n}: SuperGroupingCardProps) {\n const messageId = useAuiState((s) => s.message.id);\n const [selectActivityContent] = useState(createActivityContentSelector);\n const messageContent = useAuiState(selectActivityContent);\n const messageStatus = useAuiState((s) => s.message.status);\n\n const timeline = useMemo(() => {\n const items = buildActivityTimeline(messageContent, messageStatus, {\n waitingTools,\n waitingToolCallIds,\n passThroughTools,\n });\n return items.length > 0 ? items : EMPTY_TIMELINE;\n }, [messageContent, messageStatus, waitingTools, waitingToolCallIds, passThroughTools]);\n const phase = useMemo(\n () => resolveActivityPhase(messageStatus, timeline, { waitingForInput }),\n [messageStatus, timeline, waitingForInput],\n );\n const toolCount = countActivityTools(timeline);\n\n const { expanded, toggleExpanded } = useActivityExpansion({\n expansionKey: expansionKeyProp ?? messageId,\n phase,\n expandWhileWaiting,\n });\n const generatedId = useId().replace(/[^A-Za-z0-9_-]/g, '');\n const detailsPanelId = `aui-sg-details-${generatedId}`;\n const statusId = `aui-sg-status-${generatedId}`;\n const { expanded: detailsExpanded, toggleExpanded: toggleDetails } = useActivityExpansion({\n expansionKey: `${expansionKeyProp ?? messageId}:details`,\n phase,\n expandWhileWaiting,\n });\n const assetTitles = useMemo(() => collectAssetTitles(messageContent), [messageContent]);\n const steps = useMemo(\n () =>\n timeline.map((item, index) =>\n toTimelineStep({ item, index, resolveToolDisplayName, assetTitles, withSourceUrls: showSourceFavicons }),\n ),\n [timeline, resolveToolDisplayName, assetTitles, showSourceFavicons],\n );\n\n // The calls rendered outside the details: inline below the header, and as\n // their own step's row (by tool-call id, a tool step's id).\n const { inlineParts, stepRowParts } = useMemo(() => {\n const inline: { index: number; key: string }[] = [];\n const stepRows = new Map<string, number>();\n messageContent.forEach((part, index) => {\n if (part.type !== 'tool-call') return;\n if (isInlineTool(part.toolName, part)) inline.push({ index, key: part.toolCallId });\n if (isStepRowTool(part.toolName)) stepRows.set(part.toolCallId, index);\n });\n return { inlineParts: inline, stepRowParts: stepRows };\n }, [messageContent, isInlineTool, isStepRowTool]);\n const grouping = useMemo(\n () =>\n excludePartsFromGroups(\n excludeFinalTextFromGroups(groupingFunction ?? createToolBasedGroupingFunction(getToolGroupKey)),\n (part) =>\n isGroupedToolCallPart(part) && (isStepRowTool(part.toolName) || isInlineTool(part.toolName, part)),\n ),\n [groupingFunction, getToolGroupKey, isInlineTool, isStepRowTool],\n );\n const hasDetails = useMemo(() => grouping(messageContent).length > 0, [grouping, messageContent]);\n const isActive = phase === 'running' || phase === 'waiting';\n // The toolkit groups inside keep their tool UIs mounted when the details do.\n const groupingContext = useMemo(\n () => ({ groupingFunction: grouping, mountHidden: isActive || mountDetails }),\n [grouping, isActive, mountDetails],\n );\n const components = useMemo(\n () => ({\n tools: {\n ...(toolUIs ? { by_name: toolUIs } : {}),\n Fallback: ToolFallbackComponent,\n },\n Text: TextComponent,\n ...(ReasoningComponent ? { Reasoning: ReasoningComponent } : {}),\n Group: GroupComponent,\n }),\n [toolUIs, ToolFallbackComponent, TextComponent, ReasoningComponent, GroupComponent],\n );\n\n const hasActivity = timeline.length > 0 || phase !== 'complete';\n if (!hasActivity) return null;\n\n // Tool UIs perform live subscriptions and asset-follow effects. Collapsing\n // either disclosure must only hide them while active or targeted by a deep link.\n const detailsMounted = isActive || mountDetails || (expanded && detailsExpanded);\n const activeSteps = steps.filter((step) => step.state === 'running');\n const currentStep = activeSteps.at(-1);\n const latestStatus =\n phase === 'waiting'\n ? 'Waiting for you'\n : phase === 'error'\n ? messageStatus?.type === 'incomplete' && messageStatus.reason === 'cancelled'\n ? 'Run stopped'\n : 'Something went wrong'\n : phase === 'running'\n ? currentStep\n ? [\n currentStep.verb,\n currentStep.chip,\n activeSteps.length > 1 ? `${activeSteps.length} in progress` : null,\n ]\n .filter(Boolean)\n .join(' · ')\n : steps.length > 0\n ? 'Preparing response'\n : 'Thinking'\n : getActivitySummary(timeline);\n const label = [latestStatus, ...(isActive ? [] : (summaryDetails ?? []))].join(' · ');\n // A step-row call's own card is its step's row.\n const renderStepRow =\n stepRowParts.size > 0\n ? (step: TimelineStep) => {\n const index = step.kind === 'tool' ? stepRowParts.get(step.id) : undefined;\n return index === undefined ? null : (\n <ToolPartPlacementProvider value=\"step\">\n <MessagePrimitive.PartByIndex index={index} components={components} />\n </ToolPartPlacementProvider>\n );\n }\n : undefined;\n\n return (\n <section\n data-slot=\"super-grouping-card\"\n data-phase={phase}\n data-tool-count={toolCount}\n className={cn('my-3 min-w-0', className, classNames?.root)}\n aria-busy={phase === 'running' ? 'true' : 'false'}\n aria-describedby={statusId}\n >\n <ToolTimeline\n steps={steps}\n open={expanded}\n onOpenChange={toggleExpanded}\n label={label}\n phase={phase}\n maxVisibleSteps={maxVisibleSteps}\n keepMounted={isActive || mountDetails}\n actions={renderHeaderActions?.({ phase, messageContent })}\n meta={phase === 'running' ? renderRunningMeta?.({ phase }) : null}\n renderStepIcon={renderStepIcon}\n renderStepRow={renderStepRow}\n showSourceFavicons={showSourceFavicons}\n >\n {hasDetails ? (\n <div className=\"min-w-0 pl-5\">\n <button\n type=\"button\"\n onClick={toggleDetails}\n aria-expanded={detailsExpanded}\n aria-controls={detailsPanelId}\n className=\"flex items-center gap-1.5 rounded py-1 text-xs text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <ChevronRight\n aria-hidden=\"true\"\n className={cn(\n 'size-3 transition-transform motion-reduce:transition-none',\n detailsExpanded && 'rotate-90',\n )}\n />\n {detailsExpanded ? 'Hide tool details' : 'Show tool details'}\n </button>\n {detailsMounted ? (\n <section\n id={detailsPanelId}\n aria-label=\"Run details\"\n hidden={!expanded || !detailsExpanded}\n className={cn(\n 'min-w-0 overflow-hidden border-l border-border/60 py-2 pl-3',\n classNames?.details,\n )}\n >\n <div className={cn('flex flex-col gap-2', classNames?.detailsParts)}>\n <SuperGroupingProvider value={groupingContext}>\n <ToolPartPlacementProvider value={null}>\n <GroupedMessageParts groupingFunction={grouping} components={components} />\n </ToolPartPlacementProvider>\n </SuperGroupingProvider>\n </div>\n </section>\n ) : null}\n </div>\n ) : null}\n </ToolTimeline>\n {inlineParts.length > 0 ? (\n <div\n data-slot=\"super-grouping-inline-tools\"\n className={cn('mt-2 flex min-w-0 flex-col gap-2', classNames?.inlineTools)}\n >\n <ToolPartPlacementProvider value=\"inline\">\n {inlineParts.map(({ index, key }) => (\n <MessagePrimitive.PartByIndex key={key} index={index} components={components} />\n ))}\n </ToolPartPlacementProvider>\n </div>\n ) : null}\n <output id={statusId} aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {phase === 'waiting' ? 'Waiting for your input' : latestStatus}\n </output>\n </section>\n );\n});\n\n/**\n * The turn's visible answer, rendered below the card: the text after the last\n * tool call (all of it when the turn called no tools) and the words of any\n * spoken voice turn the message holds (`getFinalTextParts`), streaming while\n * the turn is live so the person reads the answer as it arrives.\n *\n * Each part renders through its own `TextComponent`, keyed by its content\n * index, so a part only ever grows at its end: a spoken turn streaming while\n * the run's reply streams after it would otherwise change one joined string in\n * the middle, and a streaming reveal restarts from empty on every word. Only\n * the last part carries the running status (and its caret).\n */\nexport const SuperGroupingFinalText = memo(function SuperGroupingFinalText({\n TextComponent,\n}: {\n TextComponent: TextMessagePartComponent;\n}) {\n const messageContent = useAuiState((s) =>\n s.message.role === 'assistant' ? s.message.content : EMPTY_CONTENT,\n );\n const statusType = useAuiState((s) => s.message.status?.type);\n const parts = useMemo(() => getFinalTextParts(messageContent), [messageContent]);\n\n if (parts.length === 0) return null;\n const running = statusType === 'running' || statusType === 'requires-action';\n return (\n <div data-slot=\"super-grouping-final-text\" className=\"flex min-w-0 flex-col gap-1.5\">\n {parts.map((part, position) => (\n <TextComponent\n key={part.index}\n type=\"text\"\n text={part.text}\n status={running && position === parts.length - 1 ? RUNNING_STATUS : COMPLETE_STATUS}\n />\n ))}\n </div>\n );\n});\n"],"names":["useState","useRef","useEffect","memo","SuperGroupingCard","ToolFallback","CollapsibleGroup","defaultGetToolGroupKey","groupingFunction","isSdkInlineTool","useAuiState","useMemo","buildActivityTimeline","resolveActivityPhase","countActivityTools","useId","collectAssetTitles","toTimelineStep","excludePartsFromGroups","excludeFinalTextFromGroups","createToolBasedGroupingFunction","isGroupedToolCallPart","getActivitySummary","jsx","ToolPartPlacementProvider","MessagePrimitive","jsxs","cn","ToolTimeline","ChevronRight","SuperGroupingProvider","GroupedMessageParts","SuperGroupingFinalText","getFinalTextParts"],"mappings":";;;;;;;;;;;;;;;;;;AAqCA,MAAM,4BAA4B;AAClC,MAAM,4BAA4B;AAClC,MAAM,iBAAiC,CAAA;AACvC,MAAM,gBAAuD,CAAA;AAC7D,MAAM,oBAAoB,MAAe;AAOzC,SAAS,iBAAiB;AAAA,EACxB;AAAA,EACA;AACF,GAGY;;AACV,MAAI,aAAa,KAAM,QAAO;AAC9B,MAAI,SAAS,WAAW,KAAK,SAAS,WAAW,KAAK,OAAQ,QAAO;AACrE,QAAM,YAAY,KAAK,SAAS;AAChC,QAAM,QAAO,UAAK,SAAS,MAAd,mBAAiB;AAC9B,MAAK,SAAS,UAAU,SAAS,iBAAgB,cAAS,SAAS,MAAlB,mBAAqB,UAAS,KAAM,QAAO;AAC5F,WAAS,QAAQ,GAAG,QAAQ,WAAW,SAAS,GAAG;AACjD,QAAI,SAAS,KAAK,MAAM,KAAK,KAAK,EAAG,QAAO;AAAA,EAC9C;AACA,SAAO;AACT;AAWA,SAAS,gCAEkC;AACzC,MAAI,OAA8C;AAClD,SAAO,CAAC,UAAU;;AAChB,UAAM,UAAU,MAAM;AACtB,UAAM,UAAU,QAAQ,SAAS,cAAc,QAAQ,UAAU;AACjE,UAAI,aAAQ,WAAR,mBAAgB,UAAS,aAAa,iBAAiB,EAAE,UAAU,MAAM,MAAM,QAAA,CAAS,GAAG;AAC7F,aAAO;AAAA,IACT;AACA,WAAO;AACP,WAAO;AAAA,EACT;AACF;AACA,MAAM,iBAAiB,EAAE,MAAM,UAAA;AAC/B,MAAM,kBAAkB,EAAE,MAAM,WAAA;AAUhC,MAAM,2CAA2B,IAAA;AAEjC,SAAS,kBAAkB,KAAa,UAAyB;AAC/D,uBAAqB,OAAO,GAAG;AAC/B,uBAAqB,IAAI,KAAK,QAAQ;AACtC,SAAO,qBAAqB,OAAO,2BAA2B;AAC5D,UAAM,YAAY,qBAAqB,KAAA,EAAO,OAAO;AACrD,QAAI,cAAc,OAAW;AAC7B,yBAAqB,OAAO,SAAS;AAAA,EACvC;AACF;AAEO,SAAS,oCAA0C;AACxD,uBAAqB,MAAA;AACvB;AAOO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA,qBAAqB;AACvB,GAIG;AACD,QAAM,CAAC,cAAc,eAAe,IAAIA,WAAAA;AAAAA,IAA8B,MACpE,qBAAqB,IAAI,YAAY;AAAA,EAAA;AAEvC,QAAM,CAAC,qBAAqB,sBAAsB,IAAIA,WAAAA,SAAS,KAAK;AACpE,QAAM,cAAcC,WAAAA,OAAO,EAAE,cAAc,OAAO;AAElDC,aAAAA,UAAU,MAAM;AACd,oBAAgB,qBAAqB,IAAI,YAAY,CAAC;AACtD,2BAAuB,KAAK;AAAA,EAC9B,GAAG,CAAC,YAAY,CAAC;AAEjBA,aAAAA,UAAU,MAAM;AACd,UAAM,WAAW,YAAY;AAC7B,QAAI,sBAAsB,SAAS,iBAAiB,cAAc;AAChE,UAAI,SAAS,UAAU,aAAa,UAAU,WAAW;AACvD,+BAAuB,IAAI;AAAA,MAC7B,WAAW,SAAS,UAAU,aAAa,UAAU,WAAW;AAC9D,+BAAuB,KAAK;AAAA,MAC9B;AAAA,IACF;AACA,gBAAY,UAAU,EAAE,cAAc,MAAA;AAAA,EACxC,GAAG,CAAC,cAAc,OAAO,kBAAkB,CAAC;AAE5C,QAAM,WAAW,wBAAwB,iBAAiB,sBAAsB,UAAU;AAC1F,QAAM,iBAAiB,MAAM;AAC3B,UAAM,OAAO,CAAC;AACd,2BAAuB,KAAK;AAC5B,sBAAkB,cAAc,IAAI;AACpC,oBAAgB,IAAI;AAAA,EACtB;AAEA,SAAO,EAAE,UAAU,eAAA;AACrB;AAgHO,MAAM,oBAAoBC,WAAAA,KAAK,SAASC,mBAAkB;AAAA,EAC/D;AAAA,EACA,cAAc,wBAAwBC,aAAAA;AAAAA,EACtC;AAAA,EACA;AAAA,EACA,iBAAiBC,iBAAAA;AAAAA,EACjB,kBAAkBC,WAAAA;AAAAA,EAAA,kBAClBC;AAAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAeC,YAAAA;AAAAA,EACf,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB;AAAA,EACA,cAAc;AAAA,EACd,qBAAqB;AAAA,EACrB,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,QAAM,YAAYC,MAAAA,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE;AACjD,QAAM,CAAC,qBAAqB,IAAIV,WAAAA,SAAS,6BAA6B;AACtE,QAAM,iBAAiBU,MAAAA,YAAY,qBAAqB;AACxD,QAAM,gBAAgBA,MAAAA,YAAY,CAAC,MAAM,EAAE,QAAQ,MAAM;AAEzD,QAAM,WAAWC,WAAAA,QAAQ,MAAM;AAC7B,UAAM,QAAQC,iBAAAA,sBAAsB,gBAAgB,eAAe;AAAA,MACjE;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AACD,WAAO,MAAM,SAAS,IAAI,QAAQ;AAAA,EACpC,GAAG,CAAC,gBAAgB,eAAe,cAAc,oBAAoB,gBAAgB,CAAC;AACtF,QAAM,QAAQD,WAAAA;AAAAA,IACZ,MAAME,iBAAAA,qBAAqB,eAAe,UAAU,EAAE,iBAAiB;AAAA,IACvE,CAAC,eAAe,UAAU,eAAe;AAAA,EAAA;AAE3C,QAAM,YAAYC,iBAAAA,mBAAmB,QAAQ;AAE7C,QAAM,EAAE,UAAU,eAAA,IAAmB,qBAAqB;AAAA,IACxD,cAAc,oBAAoB;AAAA,IAClC;AAAA,IACA;AAAA,EAAA,CACD;AACD,QAAM,cAAcC,WAAAA,MAAA,EAAQ,QAAQ,mBAAmB,EAAE;AACzD,QAAM,iBAAiB,kBAAkB,WAAW;AACpD,QAAM,WAAW,iBAAiB,WAAW;AAC7C,QAAM,EAAE,UAAU,iBAAiB,gBAAgB,cAAA,IAAkB,qBAAqB;AAAA,IACxF,cAAc,GAAG,oBAAoB,SAAS;AAAA,IAC9C;AAAA,IACA;AAAA,EAAA,CACD;AACD,QAAM,cAAcJ,WAAAA,QAAQ,MAAMK,YAAAA,mBAAmB,cAAc,GAAG,CAAC,cAAc,CAAC;AACtF,QAAM,QAAQL,WAAAA;AAAAA,IACZ,MACE,SAAS;AAAA,MAAI,CAAC,MAAM,UAClBM,aAAAA,eAAe,EAAE,MAAM,OAAO,wBAAwB,aAAa,gBAAgB,mBAAA,CAAoB;AAAA,IAAA;AAAA,IAE3G,CAAC,UAAU,wBAAwB,aAAa,kBAAkB;AAAA,EAAA;AAKpE,QAAM,EAAE,aAAa,aAAA,IAAiBN,WAAAA,QAAQ,MAAM;AAClD,UAAM,SAA2C,CAAA;AACjD,UAAM,+BAAe,IAAA;AACrB,mBAAe,QAAQ,CAAC,MAAM,UAAU;AACtC,UAAI,KAAK,SAAS,YAAa;AAC/B,UAAI,aAAa,KAAK,UAAU,IAAI,EAAG,QAAO,KAAK,EAAE,OAAO,KAAK,KAAK,WAAA,CAAY;AAClF,UAAI,cAAc,KAAK,QAAQ,YAAY,IAAI,KAAK,YAAY,KAAK;AAAA,IACvE,CAAC;AACD,WAAO,EAAE,aAAa,QAAQ,cAAc,SAAA;AAAA,EAC9C,GAAG,CAAC,gBAAgB,cAAc,aAAa,CAAC;AAChD,QAAM,WAAWA,WAAAA;AAAAA,IACf,MACEO,iBAAAA;AAAAA,MACEC,iBAAAA,2BAA2BX,sBAAoBY,iDAAgC,eAAe,CAAC;AAAA,MAC/F,CAAC,SACCC,iBAAAA,sBAAsB,IAAI,MAAM,cAAc,KAAK,QAAQ,KAAK,aAAa,KAAK,UAAU,IAAI;AAAA,IAAA;AAAA,IAEtG,CAACb,oBAAkB,iBAAiB,cAAc,aAAa;AAAA,EAAA;AAEjE,QAAM,aAAaG,WAAAA,QAAQ,MAAM,SAAS,cAAc,EAAE,SAAS,GAAG,CAAC,UAAU,cAAc,CAAC;AAChG,QAAM,WAAW,UAAU,aAAa,UAAU;AAElD,QAAM,kBAAkBA,WAAAA;AAAAA,IACtB,OAAO,EAAE,kBAAkB,UAAU,aAAa,YAAY,aAAA;AAAA,IAC9D,CAAC,UAAU,UAAU,YAAY;AAAA,EAAA;AAEnC,QAAM,aAAaA,WAAAA;AAAAA,IACjB,OAAO;AAAA,MACL,OAAO;AAAA,QACL,GAAI,UAAU,EAAE,SAAS,QAAA,IAAY,CAAA;AAAA,QACrC,UAAU;AAAA,MAAA;AAAA,MAEZ,MAAM;AAAA,MACN,GAAI,qBAAqB,EAAE,WAAW,mBAAA,IAAuB,CAAA;AAAA,MAC7D,OAAO;AAAA,IAAA;AAAA,IAET,CAAC,SAAS,uBAAuB,eAAe,oBAAoB,cAAc;AAAA,EAAA;AAGpF,QAAM,cAAc,SAAS,SAAS,KAAK,UAAU;AACrD,MAAI,CAAC,YAAa,QAAO;AAIzB,QAAM,iBAAiB,YAAY,gBAAiB,YAAY;AAChE,QAAM,cAAc,MAAM,OAAO,CAAC,SAAS,KAAK,UAAU,SAAS;AACnE,QAAM,cAAc,YAAY,GAAG,EAAE;AACrC,QAAM,eACJ,UAAU,YACN,oBACA,UAAU,WACR,+CAAe,UAAS,gBAAgB,cAAc,WAAW,cAC/D,gBACA,yBACF,UAAU,YACR,cACE;AAAA,IACE,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY,SAAS,IAAI,GAAG,YAAY,MAAM,iBAAiB;AAAA,EAAA,EAE9D,OAAO,OAAO,EACd,KAAK,KAAK,IACb,MAAM,SAAS,IACb,uBACA,aACJW,iBAAAA,mBAAmB,QAAQ;AACrC,QAAM,QAAQ,CAAC,cAAc,GAAI,WAAW,KAAM,kBAAkB,CAAA,CAAI,EAAE,KAAK,KAAK;AAEpF,QAAM,gBACJ,aAAa,OAAO,IAChB,CAAC,SAAuB;AACtB,UAAM,QAAQ,KAAK,SAAS,SAAS,aAAa,IAAI,KAAK,EAAE,IAAI;AACjE,WAAO,UAAU,SAAY,OAC3BC,2BAAAA,IAACC,kBAAAA,2BAAA,EAA0B,OAAM,QAC/B,UAAAD,2BAAAA,IAACE,MAAAA,iBAAiB,aAAjB,EAA6B,OAAc,YAAwB,GACtE;AAAA,EAEJ,IACA;AAEN,SACEC,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,cAAY;AAAA,MACZ,mBAAiB;AAAA,MACjB,WAAWC,OAAAA,GAAG,gBAAgB,WAAW,yCAAY,IAAI;AAAA,MACzD,aAAW,UAAU,YAAY,SAAS;AAAA,MAC1C,oBAAkB;AAAA,MAElB,UAAA;AAAA,QAAAJ,2BAAAA;AAAAA,UAACK,aAAAA;AAAAA,UAAA;AAAA,YACC;AAAA,YACA,MAAM;AAAA,YACN,cAAc;AAAA,YACd;AAAA,YACA;AAAA,YACA;AAAA,YACA,aAAa,YAAY;AAAA,YACzB,SAAS,2DAAsB,EAAE,OAAO;YACxC,MAAM,UAAU,YAAY,uDAAoB,EAAE,MAAA,KAAW;AAAA,YAC7D;AAAA,YACA;AAAA,YACA;AAAA,YAEC,UAAA,aACCF,gCAAC,OAAA,EAAI,WAAU,gBACb,UAAA;AAAA,cAAAA,2BAAAA;AAAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,MAAK;AAAA,kBACL,SAAS;AAAA,kBACT,iBAAe;AAAA,kBACf,iBAAe;AAAA,kBACf,WAAU;AAAA,kBAEV,UAAA;AAAA,oBAAAH,2BAAAA;AAAAA,sBAACM,YAAAA;AAAAA,sBAAA;AAAA,wBACC,eAAY;AAAA,wBACZ,WAAWF,OAAAA;AAAAA,0BACT;AAAA,0BACA,mBAAmB;AAAA,wBAAA;AAAA,sBACrB;AAAA,oBAAA;AAAA,oBAED,kBAAkB,sBAAsB;AAAA,kBAAA;AAAA,gBAAA;AAAA,cAAA;AAAA,cAE1C,iBACCJ,2BAAAA;AAAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,IAAI;AAAA,kBACJ,cAAW;AAAA,kBACX,QAAQ,CAAC,YAAY,CAAC;AAAA,kBACtB,WAAWI,OAAAA;AAAAA,oBACT;AAAA,oBACA,yCAAY;AAAA,kBAAA;AAAA,kBAGd,UAAAJ,2BAAAA,IAAC,SAAI,WAAWI,OAAAA,GAAG,uBAAuB,yCAAY,YAAY,GAChE,UAAAJ,2BAAAA,IAACO,iBAAAA,uBAAA,EAAsB,OAAO,iBAC5B,UAAAP,2BAAAA,IAACC,kBAAAA,2BAAA,EAA0B,OAAO,MAChC,UAAAD,2BAAAA,IAACQ,oBAAAA,qBAAA,EAAoB,kBAAkB,UAAU,WAAA,CAAwB,EAAA,CAC3E,EAAA,CACF,EAAA,CACF;AAAA,gBAAA;AAAA,cAAA,IAEA;AAAA,YAAA,EAAA,CACN,IACE;AAAA,UAAA;AAAA,QAAA;AAAA,QAEL,YAAY,SAAS,IACpBR,2BAAAA;AAAAA,UAAC;AAAA,UAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAWI,OAAAA,GAAG,oCAAoC,yCAAY,WAAW;AAAA,YAEzE,yCAACH,kBAAAA,2BAAA,EAA0B,OAAM,UAC9B,UAAA,YAAY,IAAI,CAAC,EAAE,OAAO,UACzBD,+BAACE,MAAAA,iBAAiB,aAAjB,EAAuC,OAAc,WAAA,GAAnB,GAA2C,CAC/E,EAAA,CACH;AAAA,UAAA;AAAA,QAAA,IAEA;AAAA,QACJF,2BAAAA,IAAC,UAAA,EAAO,IAAI,UAAU,aAAU,UAAS,eAAY,QAAO,WAAU,WACnE,UAAA,UAAU,YAAY,2BAA2B,aAAA,CACpD;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN,CAAC;AAcM,MAAM,yBAAyBpB,WAAAA,KAAK,SAAS6B,wBAAuB;AAAA,EACzE;AACF,GAEG;AACD,QAAM,iBAAiBtB,MAAAA;AAAAA,IAAY,CAAC,MAClC,EAAE,QAAQ,SAAS,cAAc,EAAE,QAAQ,UAAU;AAAA,EAAA;AAEvD,QAAM,aAAaA,MAAAA,YAAY,CAAC;;AAAM,mBAAE,QAAQ,WAAV,mBAAkB;AAAA,GAAI;AAC5D,QAAM,QAAQC,WAAAA,QAAQ,MAAMsB,iBAAAA,kBAAkB,cAAc,GAAG,CAAC,cAAc,CAAC;AAE/E,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,UAAU,eAAe,aAAa,eAAe;AAC3D,SACEV,2BAAAA,IAAC,OAAA,EAAI,aAAU,6BAA4B,WAAU,iCAClD,UAAA,MAAM,IAAI,CAAC,MAAM,aAChBA,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MAEC,MAAK;AAAA,MACL,MAAM,KAAK;AAAA,MACX,QAAQ,WAAW,aAAa,MAAM,SAAS,IAAI,iBAAiB;AAAA,IAAA;AAAA,IAH/D,KAAK;AAAA,EAAA,CAKb,GACH;AAEJ,CAAC;;;;;"}
|
|
1
|
+
{"version":3,"file":"SuperGroupingCard.cjs","sources":["../../../src/chat/super-grouping/SuperGroupingCard.tsx"],"sourcesContent":["'use client';\n\nimport type {\n AssistantState,\n ReasoningMessagePartComponent,\n TextMessagePartComponent,\n ThreadAssistantMessagePart,\n ToolCallMessagePart,\n ToolCallMessagePartComponent,\n ToolCallMessagePartProps,\n} from '@assistant-ui/react';\nimport { MessagePrimitive, useAuiState } from '@assistant-ui/react';\nimport { ChevronRight } from 'lucide-react';\nimport { type ComponentType, memo, type ReactNode, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport { cn } from '../../lib/utils';\nimport { isSdkInlineTool } from '../../tools/inline-tools';\nimport { ToolFallback } from '../../tools/tool-fallback';\nimport { collectAssetTitles } from '../../tools/tool-summary';\nimport {\n type ActivityItem,\n type ActivityPhase,\n buildActivityTimeline,\n countActivityTools,\n excludeFinalTextFromGroups,\n excludePartsFromGroups,\n getActivitySummary,\n getFinalTextParts,\n resolveActivityPhase,\n} from './activity-timeline';\nimport { CollapsibleGroup, type CollapsibleGroupProps, SuperGroupingProvider } from './CollapsibleGroup';\nimport { GroupedMessageParts } from './GroupedMessageParts';\nimport { createToolBasedGroupingFunction, type GroupingFunction, isGroupedToolCallPart } from './grouping-function';\nimport { ToolTimeline } from './ToolTimeline';\nimport { type TimelineStep, toTimelineStep } from './timeline-step';\nimport { defaultGetToolGroupKey, type GetToolGroupKey } from './toolkit-map';\nimport { ToolPartPlacementProvider } from './tool-part-placement';\nimport { useEarlierStepsUnloaded } from './use-earlier-steps-unloaded';\n\nconst DEFAULT_MAX_VISIBLE_STEPS = 3;\nconst MAX_REMEMBERED_EXPANSIONS = 200;\nconst EMPTY_TIMELINE: ActivityItem[] = [];\nconst EMPTY_CONTENT: readonly ThreadAssistantMessagePart[] = [];\nconst NO_STEP_ROW_TOOLS = (): boolean => false;\n\n/**\n * Whether `next` differs from `previous` only in the words of its last part, a text or reasoning\n * part that is still being written: same length, every earlier part the identical object, and the\n * last parts of the same kind.\n */\nfunction onlyLastPartGrew({\n previous,\n next,\n}: {\n previous: readonly ThreadAssistantMessagePart[];\n next: readonly ThreadAssistantMessagePart[];\n}): boolean {\n if (previous === next) return true;\n if (previous.length === 0 || previous.length !== next.length) return false;\n const lastIndex = next.length - 1;\n const kind = next[lastIndex]?.type;\n if ((kind !== 'text' && kind !== 'reasoning') || previous[lastIndex]?.type !== kind) return false;\n for (let index = 0; index < lastIndex; index += 1) {\n if (previous[index] !== next[index]) return false;\n }\n return true;\n}\n\n/**\n * The card's read of its message's content: the content itself, except that while the message is\n * generating it keeps the previous array when only the last text or reasoning part grew. The card\n * builds its steps from tool calls, their results and reasoning steps, never from those words (the\n * answer renders below it, `SuperGroupingFinalText`), and the turn being generated changes its last\n * part on every streamed token; earlier parts keep their objects across a token (the runtime's\n * conversion cache), so the card re-renders when a step changes, not per token. Any other change,\n * and the run ending, hand over the current content.\n */\nfunction createActivityContentSelector(): (\n state: AssistantState,\n) => readonly ThreadAssistantMessagePart[] {\n let last: readonly ThreadAssistantMessagePart[] = EMPTY_CONTENT;\n return (state) => {\n const message = state.message;\n const content = message.role === 'assistant' ? message.content : EMPTY_CONTENT;\n if (message.status?.type === 'running' && onlyLastPartGrew({ previous: last, next: content })) {\n return last;\n }\n last = content;\n return content;\n };\n}\nconst RUNNING_STATUS = { type: 'running' } as const;\nconst COMPLETE_STATUS = { type: 'complete' } as const;\n\n// ─── Disclosure memory ─────────────────────────────────────────────────\n\n/**\n * Session-local memory of which cards the user opened, keyed by the host's\n * expansion key (the message id by default; the Chrome extension keys by\n * thread + position so a streaming message keeps its choice when it receives\n * its stable id). Bounded so a long session cannot grow it without limit.\n */\nconst rememberedExpansions = new Map<string, boolean>();\n\nfunction rememberExpansion(key: string, expanded: boolean): void {\n rememberedExpansions.delete(key);\n rememberedExpansions.set(key, expanded);\n while (rememberedExpansions.size > MAX_REMEMBERED_EXPANSIONS) {\n const oldestKey = rememberedExpansions.keys().next().value;\n if (oldestKey === undefined) break;\n rememberedExpansions.delete(oldestKey);\n }\n}\n\nexport function resetRememberedExpansionsForTests(): void {\n rememberedExpansions.clear();\n}\n\n/**\n * The details disclosure: remembered per expansion key, and — when the host\n * opts in — opened once when the turn starts waiting on the person, so the\n * approval controls inside the details are visible without a click.\n */\nexport function useActivityExpansion({\n expansionKey,\n phase,\n expandWhileWaiting = false,\n}: {\n expansionKey: string;\n phase: ActivityPhase;\n expandWhileWaiting?: boolean;\n}) {\n const [userExpanded, setUserExpanded] = useState<boolean | undefined>(() =>\n rememberedExpansions.get(expansionKey),\n );\n const [waitingAutoExpanded, setWaitingAutoExpanded] = useState(false);\n const previousRef = useRef({ expansionKey, phase });\n\n useEffect(() => {\n setUserExpanded(rememberedExpansions.get(expansionKey));\n setWaitingAutoExpanded(false);\n }, [expansionKey]);\n\n useEffect(() => {\n const previous = previousRef.current;\n if (expandWhileWaiting && previous.expansionKey === expansionKey) {\n if (previous.phase !== 'waiting' && phase === 'waiting') {\n setWaitingAutoExpanded(true);\n } else if (previous.phase === 'waiting' && phase !== 'waiting') {\n setWaitingAutoExpanded(false);\n }\n }\n previousRef.current = { expansionKey, phase };\n }, [expansionKey, phase, expandWhileWaiting]);\n\n const expanded = waitingAutoExpanded || (userExpanded ?? (expandWhileWaiting && phase === 'waiting'));\n const toggleExpanded = () => {\n const next = !expanded;\n setWaitingAutoExpanded(false);\n rememberExpansion(expansionKey, next);\n setUserExpanded(next);\n };\n\n return { expanded, toggleExpanded };\n}\n\n// ─── Card ──────────────────────────────────────────────────────────────\n\nexport interface SuperGroupingCardProps {\n /** Tool UIs to render inside the details panel, by tool name (registered tool UIs still apply). */\n toolUIs?: Record<string, ToolCallMessagePartComponent>;\n /** Fallback for tools without a UI; the SDK's `ToolFallback` by default. */\n ToolFallback?: ComponentType<ToolCallMessagePartProps>;\n /** Text renderer for the details panel. */\n TextComponent: TextMessagePartComponent;\n /** Reasoning renderer for the details panel. */\n ReasoningComponent?: ReasoningMessagePartComponent;\n /** Per-toolkit block inside the details panel; the SDK's `CollapsibleGroup` by default. */\n GroupComponent?: ComponentType<CollapsibleGroupProps>;\n /** Toolkit lookup driving the per-toolkit sub-grouping; the SDK's static map by default. */\n getToolGroupKey?: GetToolGroupKey;\n /** A complete grouping function, when the host builds its own (overrides `getToolGroupKey`). */\n groupingFunction?: GroupingFunction;\n /** Tools whose unresolved call means the turn is waiting on the person (see `buildActivityTimeline`). */\n waitingTools?: ReadonlySet<string>;\n /**\n * Calls the run is parked on, by tool-call id: each one still without its\n * real result reads as waiting (see `buildActivityTimeline`). Keep the set's\n * identity stable while its ids are unchanged.\n */\n waitingToolCallIds?: ReadonlySet<string>;\n /** Tools whose result is a relayed application payload (see `buildActivityTimeline`). */\n passThroughTools?: ReadonlySet<string>;\n /**\n * Tools whose card renders below the header, outside both disclosures, and\n * stays mounted (an editable email draft, a UI test report). `isSdkInlineTool`\n * by default: the registry's `inline` flag plus the SDK's own report cards.\n * The call itself comes second, for a host whose choice depends on what the\n * call did (Olympus keeps an `eval` there only when it dispatched subagents).\n * The card renders it under `useToolPartPlacement()` `'inline'`.\n */\n isInlineTool?: (toolName: string, part: ToolCallMessagePart) => boolean;\n /**\n * Tools whose card is their own step's row in the step list, drawn in place\n * of the status line (Olympus's one-line code runs) and kept out of the\n * details. The card renders it under `useToolPartPlacement()` `'step'`, kept\n * mounted while the step list is (the turn is live, or `mountDetails`); a\n * call that is inline too renders in both places. None by default.\n */\n isStepRowTool?: (toolName: string) => boolean;\n /**\n * Host assertion that the turn is waiting on the person. The SDK derives waiting\n * from `requires-action` and waiting tools itself; no in-repo host passes this.\n */\n waitingForInput?: boolean;\n /** Names tools the shared registry has no entry for (Olympus's tool catalog). */\n resolveToolDisplayName?: (toolName: string) => string | undefined;\n /** Key the disclosure choice is remembered under; the message id by default. */\n expansionKey?: string;\n /** Open the details once when the turn starts waiting on the person. */\n expandWhileWaiting?: boolean;\n /**\n * Keep the details panel mounted (hidden) while collapsed, so a host that\n * needs to find content inside it (a deep link into the run's steps) can.\n */\n mountDetails?: boolean;\n /** Latest steps shown when expanded; unresolved parallel calls always remain visible. */\n maxVisibleSteps?: number;\n /** Extra segments after the collapsed summary (\"1m 23s\", \"12.4k tokens\"), joined with \" · \". */\n summaryDetails?: readonly string[];\n /** Header controls beside the title (a stop button while running, a changes button once done). */\n renderHeaderActions?: (context: {\n phase: ActivityPhase;\n messageContent: readonly ThreadAssistantMessagePart[];\n }) => ReactNode;\n /**\n * Inline on the header row, right of the status, while the turn is running\n * (an elapsed counter). Not rendered while the turn waits on the person.\n */\n renderRunningMeta?: (context: { phase: ActivityPhase }) => ReactNode;\n /** The host's icon for a step's tool, shown on its status row (see `ToolTimeline`). */\n renderStepIcon?: (step: TimelineStep) => ReactNode;\n /** Show the favicons of the sites a finished step's result lists. */\n showSourceFavicons?: boolean;\n className?: string;\n classNames?: {\n root?: string;\n details?: string;\n /** The flex column the run's parts render in; override its gap here. */\n detailsParts?: string;\n /** The flex column the inline tool calls render in, below the header; override its gap here. */\n inlineTools?: string;\n };\n}\n\n/**\n * The collapsed view (previously called the conversational view) of an\n * assistant turn: a compact current action, an expandable tool timeline, and\n * the original interactive tool UIs behind a separate details disclosure. The\n * answer itself renders below the card (`SuperGroupingFinalText`). Shared by\n * the SDK's `AthenaChat`, Olympus, and the Chrome extension, which supply\n * their own text/tool renderers and the host-specific controls through the\n * slots.\n *\n * The details panel stays mounted (visually hidden) while the turn is live so\n * the tool UIs inside run their effects — an asset-streaming card's follow\n * mode must not wait for \"Show details\". A host can take a call out of the\n * details to render its tool UI in the open (`isInlineTool`) or as its step's\n * row (`isStepRowTool`); the tool UI reads which (`useToolPartPlacement`).\n *\n * Selector discipline: raw reads only (`message.status`, `message.id`, and the\n * content through `createActivityContentSelector`, which hands back the\n * message's own array or the previous one, never a new array); every\n * derivation runs in `useMemo` — a selector that returned a fresh array would\n * tear through `useSyncExternalStore`.\n */\nexport const SuperGroupingCard = memo(function SuperGroupingCard({\n toolUIs,\n ToolFallback: ToolFallbackComponent = ToolFallback,\n TextComponent,\n ReasoningComponent,\n GroupComponent = CollapsibleGroup,\n getToolGroupKey = defaultGetToolGroupKey,\n groupingFunction,\n waitingTools,\n waitingToolCallIds,\n passThroughTools,\n isInlineTool = isSdkInlineTool,\n isStepRowTool = NO_STEP_ROW_TOOLS,\n waitingForInput = false,\n resolveToolDisplayName,\n expansionKey: expansionKeyProp,\n expandWhileWaiting = false,\n mountDetails = false,\n maxVisibleSteps = DEFAULT_MAX_VISIBLE_STEPS,\n summaryDetails,\n renderStepIcon,\n showSourceFavicons,\n renderHeaderActions,\n renderRunningMeta,\n className,\n classNames,\n}: SuperGroupingCardProps) {\n const messageId = useAuiState((s) => s.message.id);\n const earlierStepsUnloaded = useEarlierStepsUnloaded();\n const [selectActivityContent] = useState(createActivityContentSelector);\n const messageContent = useAuiState(selectActivityContent);\n const messageStatus = useAuiState((s) => s.message.status);\n\n const timeline = useMemo(() => {\n const items = buildActivityTimeline(messageContent, messageStatus, {\n waitingTools,\n waitingToolCallIds,\n passThroughTools,\n });\n return items.length > 0 ? items : EMPTY_TIMELINE;\n }, [messageContent, messageStatus, waitingTools, waitingToolCallIds, passThroughTools]);\n const phase = useMemo(\n () => resolveActivityPhase(messageStatus, timeline, { waitingForInput }),\n [messageStatus, timeline, waitingForInput],\n );\n const toolCount = countActivityTools(timeline);\n\n const { expanded, toggleExpanded } = useActivityExpansion({\n expansionKey: expansionKeyProp ?? messageId,\n phase,\n expandWhileWaiting,\n });\n const generatedId = useId().replace(/[^A-Za-z0-9_-]/g, '');\n const detailsPanelId = `aui-sg-details-${generatedId}`;\n const statusId = `aui-sg-status-${generatedId}`;\n const { expanded: detailsExpanded, toggleExpanded: toggleDetails } = useActivityExpansion({\n expansionKey: `${expansionKeyProp ?? messageId}:details`,\n phase,\n expandWhileWaiting,\n });\n const assetTitles = useMemo(() => collectAssetTitles(messageContent), [messageContent]);\n const steps = useMemo(\n () =>\n timeline.map((item, index) =>\n toTimelineStep({ item, index, resolveToolDisplayName, assetTitles, withSourceUrls: showSourceFavicons }),\n ),\n [timeline, resolveToolDisplayName, assetTitles, showSourceFavicons],\n );\n\n // The calls rendered outside the details: inline below the header, and as\n // their own step's row (by tool-call id, a tool step's id).\n const { inlineParts, stepRowParts } = useMemo(() => {\n const inline: { index: number; key: string }[] = [];\n const stepRows = new Map<string, number>();\n messageContent.forEach((part, index) => {\n if (part.type !== 'tool-call') return;\n if (isInlineTool(part.toolName, part)) inline.push({ index, key: part.toolCallId });\n if (isStepRowTool(part.toolName)) stepRows.set(part.toolCallId, index);\n });\n return { inlineParts: inline, stepRowParts: stepRows };\n }, [messageContent, isInlineTool, isStepRowTool]);\n const grouping = useMemo(\n () =>\n excludePartsFromGroups(\n excludeFinalTextFromGroups(groupingFunction ?? createToolBasedGroupingFunction(getToolGroupKey)),\n (part) =>\n isGroupedToolCallPart(part) && (isStepRowTool(part.toolName) || isInlineTool(part.toolName, part)),\n ),\n [groupingFunction, getToolGroupKey, isInlineTool, isStepRowTool],\n );\n const hasDetails = useMemo(() => grouping(messageContent).length > 0, [grouping, messageContent]);\n const isActive = phase === 'running' || phase === 'waiting';\n // The toolkit groups inside keep their tool UIs mounted when the details do.\n const groupingContext = useMemo(\n () => ({ groupingFunction: grouping, mountHidden: isActive || mountDetails }),\n [grouping, isActive, mountDetails],\n );\n const components = useMemo(\n () => ({\n tools: {\n ...(toolUIs ? { by_name: toolUIs } : {}),\n Fallback: ToolFallbackComponent,\n },\n Text: TextComponent,\n ...(ReasoningComponent ? { Reasoning: ReasoningComponent } : {}),\n Group: GroupComponent,\n }),\n [toolUIs, ToolFallbackComponent, TextComponent, ReasoningComponent, GroupComponent],\n );\n\n // A turn whose earlier steps have not loaded says so even when only its\n // answer has: its steps (and prompt) are older than the window.\n const hasActivity = timeline.length > 0 || phase !== 'complete' || earlierStepsUnloaded;\n if (!hasActivity) return null;\n\n // Tool UIs perform live subscriptions and asset-follow effects. Collapsing\n // either disclosure must only hide them while active or targeted by a deep link.\n const detailsMounted = isActive || mountDetails || (expanded && detailsExpanded);\n const activeSteps = steps.filter((step) => step.state === 'running');\n const currentStep = activeSteps.at(-1);\n const latestStatus =\n phase === 'waiting'\n ? 'Waiting for you'\n : phase === 'error'\n ? messageStatus?.type === 'incomplete' && messageStatus.reason === 'cancelled'\n ? 'Run stopped'\n : 'Something went wrong'\n : phase === 'running'\n ? currentStep\n ? [\n currentStep.verb,\n currentStep.chip,\n activeSteps.length > 1 ? `${activeSteps.length} in progress` : null,\n ]\n .filter(Boolean)\n .join(' · ')\n : steps.length > 0\n ? 'Preparing response'\n : 'Thinking'\n : getActivitySummary(timeline, { earlierStepsUnloaded });\n const label = [latestStatus, ...(isActive ? [] : (summaryDetails ?? []))].join(' · ');\n // A step-row call's own card is its step's row.\n const renderStepRow =\n stepRowParts.size > 0\n ? (step: TimelineStep) => {\n const index = step.kind === 'tool' ? stepRowParts.get(step.id) : undefined;\n return index === undefined ? null : (\n <ToolPartPlacementProvider value=\"step\">\n <MessagePrimitive.PartByIndex index={index} components={components} />\n </ToolPartPlacementProvider>\n );\n }\n : undefined;\n\n return (\n <section\n data-slot=\"super-grouping-card\"\n data-phase={phase}\n data-tool-count={toolCount}\n className={cn('my-3 min-w-0', className, classNames?.root)}\n aria-busy={phase === 'running' ? 'true' : 'false'}\n aria-describedby={statusId}\n >\n <ToolTimeline\n steps={steps}\n open={expanded}\n onOpenChange={toggleExpanded}\n label={label}\n phase={phase}\n maxVisibleSteps={maxVisibleSteps}\n keepMounted={isActive || mountDetails}\n actions={renderHeaderActions?.({ phase, messageContent })}\n meta={phase === 'running' ? renderRunningMeta?.({ phase }) : null}\n renderStepIcon={renderStepIcon}\n renderStepRow={renderStepRow}\n showSourceFavicons={showSourceFavicons}\n >\n {hasDetails ? (\n <div className=\"min-w-0 pl-5\">\n <button\n type=\"button\"\n onClick={toggleDetails}\n aria-expanded={detailsExpanded}\n aria-controls={detailsPanelId}\n className=\"flex items-center gap-1.5 rounded py-1 text-xs text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <ChevronRight\n aria-hidden=\"true\"\n className={cn(\n 'size-3 transition-transform motion-reduce:transition-none',\n detailsExpanded && 'rotate-90',\n )}\n />\n {detailsExpanded ? 'Hide tool details' : 'Show tool details'}\n </button>\n {detailsMounted ? (\n <section\n id={detailsPanelId}\n aria-label=\"Run details\"\n hidden={!expanded || !detailsExpanded}\n className={cn(\n 'min-w-0 overflow-hidden border-l border-border/60 py-2 pl-3',\n classNames?.details,\n )}\n >\n <div className={cn('flex flex-col gap-2', classNames?.detailsParts)}>\n <SuperGroupingProvider value={groupingContext}>\n <ToolPartPlacementProvider value={null}>\n <GroupedMessageParts groupingFunction={grouping} components={components} />\n </ToolPartPlacementProvider>\n </SuperGroupingProvider>\n </div>\n </section>\n ) : null}\n </div>\n ) : null}\n </ToolTimeline>\n {inlineParts.length > 0 ? (\n <div\n data-slot=\"super-grouping-inline-tools\"\n className={cn('mt-2 flex min-w-0 flex-col gap-2', classNames?.inlineTools)}\n >\n <ToolPartPlacementProvider value=\"inline\">\n {inlineParts.map(({ index, key }) => (\n <MessagePrimitive.PartByIndex key={key} index={index} components={components} />\n ))}\n </ToolPartPlacementProvider>\n </div>\n ) : null}\n <output id={statusId} aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {phase === 'waiting' ? 'Waiting for your input' : latestStatus}\n </output>\n </section>\n );\n});\n\n/**\n * The turn's visible answer, rendered below the card: the text after the last\n * tool call (all of it when the turn called no tools) and the words of any\n * spoken voice turn the message holds (`getFinalTextParts`), streaming while\n * the turn is live so the person reads the answer as it arrives.\n *\n * Each part renders through its own `TextComponent`, keyed by its content\n * index, so a part only ever grows at its end: a spoken turn streaming while\n * the run's reply streams after it would otherwise change one joined string in\n * the middle, and a streaming reveal restarts from empty on every word. Only\n * the last part carries the running status (and its caret).\n */\nexport const SuperGroupingFinalText = memo(function SuperGroupingFinalText({\n TextComponent,\n}: {\n TextComponent: TextMessagePartComponent;\n}) {\n const messageContent = useAuiState((s) =>\n s.message.role === 'assistant' ? s.message.content : EMPTY_CONTENT,\n );\n const statusType = useAuiState((s) => s.message.status?.type);\n const parts = useMemo(() => getFinalTextParts(messageContent), [messageContent]);\n\n if (parts.length === 0) return null;\n const running = statusType === 'running' || statusType === 'requires-action';\n return (\n <div data-slot=\"super-grouping-final-text\" className=\"flex min-w-0 flex-col gap-1.5\">\n {parts.map((part, position) => (\n <TextComponent\n key={part.index}\n type=\"text\"\n text={part.text}\n status={running && position === parts.length - 1 ? RUNNING_STATUS : COMPLETE_STATUS}\n />\n ))}\n </div>\n );\n});\n"],"names":["useState","useRef","useEffect","memo","SuperGroupingCard","ToolFallback","CollapsibleGroup","defaultGetToolGroupKey","groupingFunction","isSdkInlineTool","useAuiState","useEarlierStepsUnloaded","useMemo","buildActivityTimeline","resolveActivityPhase","countActivityTools","useId","collectAssetTitles","toTimelineStep","excludePartsFromGroups","excludeFinalTextFromGroups","createToolBasedGroupingFunction","isGroupedToolCallPart","getActivitySummary","jsx","ToolPartPlacementProvider","MessagePrimitive","jsxs","cn","ToolTimeline","ChevronRight","SuperGroupingProvider","GroupedMessageParts","SuperGroupingFinalText","getFinalTextParts"],"mappings":"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"}
|
|
@@ -13,6 +13,7 @@ import { ToolTimeline } from "./ToolTimeline.js";
|
|
|
13
13
|
import { toTimelineStep } from "./timeline-step.js";
|
|
14
14
|
import { defaultGetToolGroupKey } from "./toolkit-map.js";
|
|
15
15
|
import { ToolPartPlacementProvider } from "./tool-part-placement.js";
|
|
16
|
+
import { useEarlierStepsUnloaded } from "./use-earlier-steps-unloaded.js";
|
|
16
17
|
import { cn } from "cnfast";
|
|
17
18
|
const DEFAULT_MAX_VISIBLE_STEPS = 3;
|
|
18
19
|
const MAX_REMEMBERED_EXPANSIONS = 200;
|
|
@@ -124,6 +125,7 @@ const SuperGroupingCard = memo(function SuperGroupingCard2({
|
|
|
124
125
|
classNames
|
|
125
126
|
}) {
|
|
126
127
|
const messageId = useAuiState((s) => s.message.id);
|
|
128
|
+
const earlierStepsUnloaded = useEarlierStepsUnloaded();
|
|
127
129
|
const [selectActivityContent] = useState(createActivityContentSelector);
|
|
128
130
|
const messageContent = useAuiState(selectActivityContent);
|
|
129
131
|
const messageStatus = useAuiState((s) => s.message.status);
|
|
@@ -195,7 +197,7 @@ const SuperGroupingCard = memo(function SuperGroupingCard2({
|
|
|
195
197
|
}),
|
|
196
198
|
[toolUIs, ToolFallbackComponent, TextComponent, ReasoningComponent, GroupComponent]
|
|
197
199
|
);
|
|
198
|
-
const hasActivity = timeline.length > 0 || phase !== "complete";
|
|
200
|
+
const hasActivity = timeline.length > 0 || phase !== "complete" || earlierStepsUnloaded;
|
|
199
201
|
if (!hasActivity) return null;
|
|
200
202
|
const detailsMounted = isActive || mountDetails || expanded && detailsExpanded;
|
|
201
203
|
const activeSteps = steps.filter((step) => step.state === "running");
|
|
@@ -204,7 +206,7 @@ const SuperGroupingCard = memo(function SuperGroupingCard2({
|
|
|
204
206
|
currentStep.verb,
|
|
205
207
|
currentStep.chip,
|
|
206
208
|
activeSteps.length > 1 ? `${activeSteps.length} in progress` : null
|
|
207
|
-
].filter(Boolean).join(" · ") : steps.length > 0 ? "Preparing response" : "Thinking" : getActivitySummary(timeline);
|
|
209
|
+
].filter(Boolean).join(" · ") : steps.length > 0 ? "Preparing response" : "Thinking" : getActivitySummary(timeline, { earlierStepsUnloaded });
|
|
208
210
|
const label = [latestStatus, ...isActive ? [] : summaryDetails ?? []].join(" · ");
|
|
209
211
|
const renderStepRow = stepRowParts.size > 0 ? (step) => {
|
|
210
212
|
const index = step.kind === "tool" ? stepRowParts.get(step.id) : void 0;
|