@athenaintel/react 0.48.0 → 0.50.0
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 +48 -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/index.d.ts +6 -5
- 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 +8 -8
- package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +113 -1
- 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-thread-handoff-flow.d.ts +8 -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/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-ui/index.cjs +3 -0
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.d.ts +1 -0
- package/dist/chat-ui/index.js +3 -0
- 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 +2 -0
- package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.js +2 -0
- package/dist/chat-ui/thread/chat-thread-shell.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 +3 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +190 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +132 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js +190 -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/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/package.json +1 -1
|
@@ -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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"}
|
package/dist/chat-ui/index.cjs
CHANGED
|
@@ -38,6 +38,7 @@ const wakeMarker = require("./messages/wake-marker.cjs");
|
|
|
38
38
|
const handoffArrivalDivider = require("./messages/handoff-arrival-divider.cjs");
|
|
39
39
|
const answerLabel = require("./messages/answer-label.cjs");
|
|
40
40
|
const voiceDelegationCard = require("./messages/voice-delegation-card.cjs");
|
|
41
|
+
const fromTheCallCard = require("./messages/from-the-call-card.cjs");
|
|
41
42
|
const continuePill = require("./messages/continue-pill.cjs");
|
|
42
43
|
const statewireContinueRun = require("./messages/statewire-continue-run.cjs");
|
|
43
44
|
const StatewireApprovalCard = require("../chat/StatewireApprovalCard.cjs");
|
|
@@ -145,6 +146,8 @@ exports.AnswerLabel = answerLabel.AnswerLabel;
|
|
|
145
146
|
exports.AnswerLabelScope = answerLabel.AnswerLabelScope;
|
|
146
147
|
exports.VoiceDelegationCard = voiceDelegationCard.VoiceDelegationCard;
|
|
147
148
|
exports.VoiceDelegationMessage = voiceDelegationCard.VoiceDelegationMessage;
|
|
149
|
+
exports.FromTheCallAfterMessage = fromTheCallCard.FromTheCallAfterMessage;
|
|
150
|
+
exports.FromTheCallCard = fromTheCallCard.FromTheCallCard;
|
|
148
151
|
exports.ContinuePill = continuePill.ContinuePill;
|
|
149
152
|
exports.StatewireContinueRunAffordance = statewireContinueRun.StatewireContinueRunAffordance;
|
|
150
153
|
exports.StatewireContinueRunButton = statewireContinueRun.StatewireContinueRunButton;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/chat-ui/index.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export { WakeMarkerCard, type WakeMarkerCardProps, WakeMarkerMessage, } from "./
|
|
|
48
48
|
export { HandoffArrivalAfterMessage, HandoffArrivalDivider, type HandoffArrivalDividerProps, HandoffArrivalScope, } from "./messages/handoff-arrival-divider";
|
|
49
49
|
export { AnswerLabel, type AnswerLabelProps, AnswerLabelScope, type AnswerLabelScopeProps, } from "./messages/answer-label";
|
|
50
50
|
export { VoiceDelegationCard, type VoiceDelegationCardProps, VoiceDelegationMessage, } from "./messages/voice-delegation-card";
|
|
51
|
+
export { FromTheCallAfterMessage, FromTheCallCard, type FromTheCallCardProps, } from "./messages/from-the-call-card";
|
|
51
52
|
export { ContinuePill } from "./messages/continue-pill";
|
|
52
53
|
export { type ReadStatewireTailEnding, StatewireContinueRunAffordance, StatewireContinueRunButton, type StatewireContinueRunButtonProps, StatewireRunEndedFooter, type StatewireRunEndedFooterActionsContext, type StatewireRunEndedFooterProps, type StatewireTailEnding, } from "./messages/statewire-continue-run";
|
|
53
54
|
export { StatewireApprovalCard, type StatewireApprovalCardProps, type StatewireApprovalEditorContext, type StatewireApprovalRequestContext, type StatewireDecisionAnswer, type StatewireDecisionConfirmer, } from "../chat/StatewireApprovalCard";
|
package/dist/chat-ui/index.js
CHANGED
|
@@ -36,6 +36,7 @@ import { WakeMarkerCard, WakeMarkerMessage } from "./messages/wake-marker.js";
|
|
|
36
36
|
import { HandoffArrivalAfterMessage, HandoffArrivalDivider, HandoffArrivalScope } from "./messages/handoff-arrival-divider.js";
|
|
37
37
|
import { AnswerLabel, AnswerLabelScope } from "./messages/answer-label.js";
|
|
38
38
|
import { VoiceDelegationCard, VoiceDelegationMessage } from "./messages/voice-delegation-card.js";
|
|
39
|
+
import { FromTheCallAfterMessage, FromTheCallCard } from "./messages/from-the-call-card.js";
|
|
39
40
|
import { ContinuePill } from "./messages/continue-pill.js";
|
|
40
41
|
import { StatewireContinueRunAffordance, StatewireContinueRunButton, StatewireRunEndedFooter } from "./messages/statewire-continue-run.js";
|
|
41
42
|
import { StatewireApprovalCard } from "../chat/StatewireApprovalCard.js";
|
|
@@ -70,6 +71,8 @@ export {
|
|
|
70
71
|
ContinuePill,
|
|
71
72
|
ConversationHistoryPanel,
|
|
72
73
|
DrivingOnChip,
|
|
74
|
+
FromTheCallAfterMessage,
|
|
75
|
+
FromTheCallCard,
|
|
73
76
|
HISTORY_EDGE_NEAR_MARGIN_PX,
|
|
74
77
|
HITL_PENDING_RESUME_GRACE_MS,
|
|
75
78
|
HandoffArrivalAfterMessage,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const react = require("@assistant-ui/react");
|
|
5
|
+
const reactStatewire = require("@assistant-ui/react-statewire");
|
|
6
|
+
const lucideReact = require("lucide-react");
|
|
7
|
+
const require$$0 = require("react");
|
|
8
|
+
const useStatewireApprovalRequest = require("../../chat/use-statewire-approval-request.cjs");
|
|
9
|
+
const mentionChip = require("../mentions/mention-chip.cjs");
|
|
10
|
+
const chatLink = require("../rich-text/chat-link.cjs");
|
|
11
|
+
const chatMessageBody = require("../rich-text/chat-message-body.cjs");
|
|
12
|
+
const callRecap = require("../../agent-runtime/src/call-recap.cjs");
|
|
13
|
+
const threadExtras = require("../../agent-runtime/src/thread-extras.cjs");
|
|
14
|
+
const toolMetadata = require("../../agent-runtime/src/tool-metadata.cjs");
|
|
15
|
+
const cnfast = require("cnfast");
|
|
16
|
+
const APPROVAL_CARD_SELECTOR = ".aui-approval-card";
|
|
17
|
+
const NO_IDS = /* @__PURE__ */ new Set();
|
|
18
|
+
function scrollToApprovalCard() {
|
|
19
|
+
var _a;
|
|
20
|
+
if (typeof document === "undefined") return;
|
|
21
|
+
(_a = document.querySelector(APPROVAL_CARD_SELECTOR)) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
22
|
+
}
|
|
23
|
+
function assetHref(item) {
|
|
24
|
+
if (item.href) return item.href;
|
|
25
|
+
const params = new URLSearchParams({ id: item.assetId });
|
|
26
|
+
if (item.assetType) params.set("type", item.assetType);
|
|
27
|
+
return `mention://asset?${params.toString()}`;
|
|
28
|
+
}
|
|
29
|
+
const FromTheCallCard = ({
|
|
30
|
+
items,
|
|
31
|
+
onApprovalClick,
|
|
32
|
+
className
|
|
33
|
+
}) => {
|
|
34
|
+
const handleLinkClick = chatMessageBody.useChatLinkClick();
|
|
35
|
+
const openApproval = require$$0.useCallback(
|
|
36
|
+
(toolCallId) => onApprovalClick ? onApprovalClick(toolCallId) : scrollToApprovalCard(),
|
|
37
|
+
[onApprovalClick]
|
|
38
|
+
);
|
|
39
|
+
if (items.length === 0) return null;
|
|
40
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
41
|
+
"section",
|
|
42
|
+
{
|
|
43
|
+
"aria-label": callRecap.CALL_RECAP_TITLE,
|
|
44
|
+
"data-testid": "from-the-call-card",
|
|
45
|
+
className: cnfast.cn(
|
|
46
|
+
"my-2 w-full rounded-xl border border-primary/20 bg-primary/5 px-3 py-2",
|
|
47
|
+
className
|
|
48
|
+
),
|
|
49
|
+
children: [
|
|
50
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-1.5 flex items-center gap-1.5", children: [
|
|
51
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex size-4 items-center justify-center rounded bg-primary/15", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AudioLinesIcon, { className: "size-2.5 text-primary", "aria-hidden": true }) }),
|
|
52
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium uppercase tracking-wide text-primary", children: callRecap.CALL_RECAP_TITLE })
|
|
53
|
+
] }),
|
|
54
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-1.5 text-sm", onClick: handleLinkClick, children: items.map((item) => {
|
|
55
|
+
if (item.kind === "asset") {
|
|
56
|
+
return /* @__PURE__ */ jsxRuntime.jsx(mentionChip.MentionLink, { href: assetHref(item), children: item.title || "Untitled" }, item.key);
|
|
57
|
+
}
|
|
58
|
+
if (item.kind === "link") {
|
|
59
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
60
|
+
"a",
|
|
61
|
+
{
|
|
62
|
+
href: item.url,
|
|
63
|
+
target: "_blank",
|
|
64
|
+
rel: "noopener noreferrer",
|
|
65
|
+
title: item.url,
|
|
66
|
+
"data-testid": "from-the-call-link",
|
|
67
|
+
className: cnfast.cn(mentionChip.CHIP_PILL_CLASS, "max-w-[275px]", mentionChip.CHIP_LINK_CLASS),
|
|
68
|
+
children: [
|
|
69
|
+
/* @__PURE__ */ jsxRuntime.jsx(chatLink.SourceFavicon, { href: item.url }),
|
|
70
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 max-w-[220px] overflow-hidden text-ellipsis", children: item.label })
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
item.key
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
const tool = toolMetadata.getToolDisplayMeta(item.toolName).displayName;
|
|
77
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
78
|
+
"button",
|
|
79
|
+
{
|
|
80
|
+
type: "button",
|
|
81
|
+
onClick: () => openApproval(item.toolCallId),
|
|
82
|
+
"data-testid": "from-the-call-approval",
|
|
83
|
+
className: cnfast.cn(mentionChip.CHIP_PILL_CLASS, "max-w-full", mentionChip.CHIP_LINK_CLASS),
|
|
84
|
+
children: [
|
|
85
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.HandIcon, { className: cnfast.cn(mentionChip.CHIP_ICON_CLASS, "text-amber-600"), "aria-hidden": true }),
|
|
86
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 overflow-hidden text-ellipsis", children: [
|
|
87
|
+
callRecap.CALL_RECAP_APPROVAL_LABEL,
|
|
88
|
+
" · ",
|
|
89
|
+
tool
|
|
90
|
+
] })
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
item.key
|
|
94
|
+
);
|
|
95
|
+
}) })
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
const StatewireFromTheCall = ({ recap }) => {
|
|
101
|
+
const callMayBeLive = reactStatewire.useStatewireState(callRecap.mayHaveLiveCall);
|
|
102
|
+
const { toolCallIds } = useStatewireApprovalRequest.useStatewireApprovalWait();
|
|
103
|
+
const items = require$$0.useMemo(() => callRecap.callRecapItems(recap, toolCallIds), [recap, toolCallIds]);
|
|
104
|
+
if (recap.trailing && callMayBeLive) return null;
|
|
105
|
+
return /* @__PURE__ */ jsxRuntime.jsx(FromTheCallCard, { items });
|
|
106
|
+
};
|
|
107
|
+
const StaticFromTheCall = ({ recap }) => {
|
|
108
|
+
const items = require$$0.useMemo(() => callRecap.callRecapItems(recap, NO_IDS), [recap]);
|
|
109
|
+
return /* @__PURE__ */ jsxRuntime.jsx(FromTheCallCard, { items });
|
|
110
|
+
};
|
|
111
|
+
const FromTheCallAfterMessage = () => {
|
|
112
|
+
const recap = react.useAuiState(
|
|
113
|
+
(s) => {
|
|
114
|
+
var _a;
|
|
115
|
+
return ((_a = s.thread.messages[s.message.index]) == null ? void 0 : _a.id) === s.message.id ? callRecap.callRecapAfter(s.thread.messages, s.message.index) : null;
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
const statewire = react.useAuiState((s) => threadExtras.hasStatewireThreadExtras(s.thread.extras));
|
|
119
|
+
if (!recap) return null;
|
|
120
|
+
return statewire ? /* @__PURE__ */ jsxRuntime.jsx(StatewireFromTheCall, { recap }) : /* @__PURE__ */ jsxRuntime.jsx(StaticFromTheCall, { recap });
|
|
121
|
+
};
|
|
122
|
+
exports.FromTheCallAfterMessage = FromTheCallAfterMessage;
|
|
123
|
+
exports.FromTheCallCard = FromTheCallCard;
|
|
124
|
+
//# sourceMappingURL=from-the-call-card.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"from-the-call-card.cjs","sources":["../../../src/chat-ui/messages/from-the-call-card.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * \"From the call\": the card a transcript shows where a voice call's stretch\n * ends, collecting what the call produced or mentioned — assets, web links and\n * the approvals it is still waiting on. The derivation is\n * `@athenaintel/agent-runtime`'s `call-recap` (shared with Olympus and the\n * phone app); this is the SDK's rendering, which `ChatThreadShell` mounts after\n * every message, so the Chrome side panel and the desktop app show it too.\n *\n * Every item opens through the transcript's own path: an asset is the asset\n * chip the message body draws (`MentionLink`), and a click goes through the\n * same link handler as a click in a message; a web link opens in a new tab;\n * an approval scrolls to the approval card, where it is answered.\n */\nimport { useAuiState } from \"@assistant-ui/react\";\nimport { useStatewireState } from \"@assistant-ui/react-statewire\";\nimport {\n CALL_RECAP_APPROVAL_LABEL,\n CALL_RECAP_TITLE,\n type CallRecap,\n type CallRecapItem,\n callRecapAfter,\n callRecapItems,\n getToolDisplayMeta,\n hasStatewireThreadExtras,\n mayHaveLiveCall,\n} from \"@athenaintel/agent-runtime\";\nimport { AudioLinesIcon, HandIcon } from \"lucide-react\";\nimport { type FC, useCallback, useMemo } from \"react\";\nimport { useStatewireApprovalWait } from \"../../chat/use-statewire-approval-request\";\nimport { cn } from \"../../lib/utils\";\nimport {\n CHIP_ICON_CLASS,\n CHIP_LINK_CLASS,\n CHIP_PILL_CLASS,\n MentionLink,\n} from \"../mentions/mention-chip\";\nimport { SourceFavicon } from \"../rich-text/chat-link\";\nimport { useChatLinkClick } from \"../rich-text/chat-message-body\";\n\n/** The CSS class `StatewireApprovalCard` puts on its section. */\nconst APPROVAL_CARD_SELECTOR = \".aui-approval-card\";\n\nconst NO_IDS: ReadonlySet<string> = new Set();\n\nexport interface FromTheCallCardProps {\n /** What the call produced or mentioned, in the order to show it (`callRecapItems`). */\n items: readonly CallRecapItem[];\n /** An approval item was clicked; defaults to scrolling the approval card into view. */\n onApprovalClick?: (toolCallId: string) => void;\n className?: string;\n}\n\nfunction scrollToApprovalCard(): void {\n if (typeof document === \"undefined\") return;\n document\n .querySelector(APPROVAL_CARD_SELECTOR)\n ?.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n}\n\n/** An asset's mention link: the one the transcript carried, else the asset's own mention URI. */\nfunction assetHref(item: Extract<CallRecapItem, { kind: \"asset\" }>): string {\n if (item.href) return item.href;\n const params = new URLSearchParams({ id: item.assetId });\n if (item.assetType) params.set(\"type\", item.assetType);\n return `mention://asset?${params.toString()}`;\n}\n\n/** Pure presenter: a compact card in the voice-task card's style. Renders nothing without items. */\nexport const FromTheCallCard: FC<FromTheCallCardProps> = ({\n items,\n onApprovalClick,\n className,\n}) => {\n const handleLinkClick = useChatLinkClick();\n const openApproval = useCallback(\n (toolCallId: string) =>\n onApprovalClick ? onApprovalClick(toolCallId) : scrollToApprovalCard(),\n [onApprovalClick],\n );\n if (items.length === 0) return null;\n return (\n <section\n aria-label={CALL_RECAP_TITLE}\n data-testid=\"from-the-call-card\"\n className={cn(\n \"my-2 w-full rounded-xl border border-primary/20 bg-primary/5 px-3 py-2\",\n className,\n )}\n >\n <div className=\"mb-1.5 flex items-center gap-1.5\">\n <span className=\"inline-flex size-4 items-center justify-center rounded bg-primary/15\">\n <AudioLinesIcon className=\"size-2.5 text-primary\" aria-hidden />\n </span>\n <span className=\"text-[10px] font-medium uppercase tracking-wide text-primary\">\n {CALL_RECAP_TITLE}\n </span>\n </div>\n {/* One delegated handler, as in a message body: asset chips open where a transcript chip would. */}\n {/* biome-ignore lint/a11y/noNoninteractiveElementInteractions lint/a11y/noStaticElementInteractions lint/a11y/useKeyWithClickEvents: delegated from the links inside, which stay the focusable controls and fire this click on Enter */}\n <div className=\"flex flex-wrap items-center gap-1.5 text-sm\" onClick={handleLinkClick}>\n {items.map((item) => {\n if (item.kind === \"asset\") {\n return (\n <MentionLink key={item.key} href={assetHref(item)}>\n {item.title || \"Untitled\"}\n </MentionLink>\n );\n }\n if (item.kind === \"link\") {\n return (\n <a\n key={item.key}\n href={item.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n title={item.url}\n data-testid=\"from-the-call-link\"\n className={cn(CHIP_PILL_CLASS, \"max-w-[275px]\", CHIP_LINK_CLASS)}\n >\n <SourceFavicon href={item.url} />\n <span className=\"min-w-0 max-w-[220px] overflow-hidden text-ellipsis\">\n {item.label}\n </span>\n </a>\n );\n }\n const tool = getToolDisplayMeta(item.toolName).displayName;\n return (\n <button\n key={item.key}\n type=\"button\"\n onClick={() => openApproval(item.toolCallId)}\n data-testid=\"from-the-call-approval\"\n className={cn(CHIP_PILL_CLASS, \"max-w-full\", CHIP_LINK_CLASS)}\n >\n <HandIcon className={cn(CHIP_ICON_CLASS, \"text-amber-600\")} aria-hidden />\n <span className=\"min-w-0 overflow-hidden text-ellipsis\">\n {CALL_RECAP_APPROVAL_LABEL} · {tool}\n </span>\n </button>\n );\n })}\n </div>\n </section>\n );\n};\n\n/** The card for a recap on a statewire thread: hidden while its call may be live, approvals from the run's wait. */\nconst StatewireFromTheCall: FC<{ recap: CallRecap }> = ({ recap }) => {\n const callMayBeLive = useStatewireState(mayHaveLiveCall);\n const { toolCallIds } = useStatewireApprovalWait();\n const items = useMemo(() => callRecapItems(recap, toolCallIds), [recap, toolCallIds]);\n if (recap.trailing && callMayBeLive) return null;\n return <FromTheCallCard items={items} />;\n};\n\n/** A thread with no statewire scope (a read-only transcript) knows of no live call or approval. */\nconst StaticFromTheCall: FC<{ recap: CallRecap }> = ({ recap }) => {\n const items = useMemo(() => callRecapItems(recap, NO_IDS), [recap]);\n return <FromTheCallCard items={items} />;\n};\n\n/**\n * Inside a message row: the card when a call's stretch ends at this message.\n * Selects a recap that keeps its identity while unchanged, so the row\n * re-renders only when the card's contents do.\n */\nexport const FromTheCallAfterMessage: FC = () => {\n const recap = useAuiState((s) =>\n s.thread.messages[s.message.index]?.id === s.message.id\n ? callRecapAfter(s.thread.messages, s.message.index)\n : null,\n );\n const statewire = useAuiState((s) => hasStatewireThreadExtras(s.thread.extras));\n if (!recap) return null;\n return statewire ? <StatewireFromTheCall recap={recap} /> : <StaticFromTheCall recap={recap} />;\n};\n"],"names":["useChatLinkClick","useCallback","jsxs","CALL_RECAP_TITLE","cn","jsx","AudioLinesIcon","MentionLink","CHIP_PILL_CLASS","CHIP_LINK_CLASS","SourceFavicon","getToolDisplayMeta","HandIcon","CHIP_ICON_CLASS","CALL_RECAP_APPROVAL_LABEL","useStatewireState","mayHaveLiveCall","useStatewireApprovalWait","useMemo","callRecapItems","useAuiState","callRecapAfter","hasStatewireThreadExtras"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type CallRecapItem } from "../../agent-runtime/src/index";
|
|
2
|
+
import { type FC } from "react";
|
|
3
|
+
export interface FromTheCallCardProps {
|
|
4
|
+
/** What the call produced or mentioned, in the order to show it (`callRecapItems`). */
|
|
5
|
+
items: readonly CallRecapItem[];
|
|
6
|
+
/** An approval item was clicked; defaults to scrolling the approval card into view. */
|
|
7
|
+
onApprovalClick?: (toolCallId: string) => void;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Pure presenter: a compact card in the voice-task card's style. Renders nothing without items. */
|
|
11
|
+
export declare const FromTheCallCard: FC<FromTheCallCardProps>;
|
|
12
|
+
/**
|
|
13
|
+
* Inside a message row: the card when a call's stretch ends at this message.
|
|
14
|
+
* Selects a recap that keeps its identity while unchanged, so the row
|
|
15
|
+
* re-renders only when the card's contents do.
|
|
16
|
+
*/
|
|
17
|
+
export declare const FromTheCallAfterMessage: FC;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuiState } from "@assistant-ui/react";
|
|
3
|
+
import { useStatewireState } from "@assistant-ui/react-statewire";
|
|
4
|
+
import { AudioLinesIcon, HandIcon } from "lucide-react";
|
|
5
|
+
import { useCallback, useMemo } from "react";
|
|
6
|
+
import { useStatewireApprovalWait } from "../../chat/use-statewire-approval-request.js";
|
|
7
|
+
import { MentionLink, CHIP_PILL_CLASS, CHIP_LINK_CLASS, CHIP_ICON_CLASS } from "../mentions/mention-chip.js";
|
|
8
|
+
import { SourceFavicon } from "../rich-text/chat-link.js";
|
|
9
|
+
import { useChatLinkClick } from "../rich-text/chat-message-body.js";
|
|
10
|
+
import { callRecapAfter, CALL_RECAP_TITLE, CALL_RECAP_APPROVAL_LABEL, mayHaveLiveCall, callRecapItems } from "../../agent-runtime/src/call-recap.js";
|
|
11
|
+
import { hasStatewireThreadExtras } from "../../agent-runtime/src/thread-extras.js";
|
|
12
|
+
import { getToolDisplayMeta } from "../../agent-runtime/src/tool-metadata.js";
|
|
13
|
+
import { cn } from "cnfast";
|
|
14
|
+
const APPROVAL_CARD_SELECTOR = ".aui-approval-card";
|
|
15
|
+
const NO_IDS = /* @__PURE__ */ new Set();
|
|
16
|
+
function scrollToApprovalCard() {
|
|
17
|
+
var _a;
|
|
18
|
+
if (typeof document === "undefined") return;
|
|
19
|
+
(_a = document.querySelector(APPROVAL_CARD_SELECTOR)) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
20
|
+
}
|
|
21
|
+
function assetHref(item) {
|
|
22
|
+
if (item.href) return item.href;
|
|
23
|
+
const params = new URLSearchParams({ id: item.assetId });
|
|
24
|
+
if (item.assetType) params.set("type", item.assetType);
|
|
25
|
+
return `mention://asset?${params.toString()}`;
|
|
26
|
+
}
|
|
27
|
+
const FromTheCallCard = ({
|
|
28
|
+
items,
|
|
29
|
+
onApprovalClick,
|
|
30
|
+
className
|
|
31
|
+
}) => {
|
|
32
|
+
const handleLinkClick = useChatLinkClick();
|
|
33
|
+
const openApproval = useCallback(
|
|
34
|
+
(toolCallId) => onApprovalClick ? onApprovalClick(toolCallId) : scrollToApprovalCard(),
|
|
35
|
+
[onApprovalClick]
|
|
36
|
+
);
|
|
37
|
+
if (items.length === 0) return null;
|
|
38
|
+
return /* @__PURE__ */ jsxs(
|
|
39
|
+
"section",
|
|
40
|
+
{
|
|
41
|
+
"aria-label": CALL_RECAP_TITLE,
|
|
42
|
+
"data-testid": "from-the-call-card",
|
|
43
|
+
className: cn(
|
|
44
|
+
"my-2 w-full rounded-xl border border-primary/20 bg-primary/5 px-3 py-2",
|
|
45
|
+
className
|
|
46
|
+
),
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-1.5 flex items-center gap-1.5", children: [
|
|
49
|
+
/* @__PURE__ */ jsx("span", { className: "inline-flex size-4 items-center justify-center rounded bg-primary/15", children: /* @__PURE__ */ jsx(AudioLinesIcon, { className: "size-2.5 text-primary", "aria-hidden": true }) }),
|
|
50
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium uppercase tracking-wide text-primary", children: CALL_RECAP_TITLE })
|
|
51
|
+
] }),
|
|
52
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-1.5 text-sm", onClick: handleLinkClick, children: items.map((item) => {
|
|
53
|
+
if (item.kind === "asset") {
|
|
54
|
+
return /* @__PURE__ */ jsx(MentionLink, { href: assetHref(item), children: item.title || "Untitled" }, item.key);
|
|
55
|
+
}
|
|
56
|
+
if (item.kind === "link") {
|
|
57
|
+
return /* @__PURE__ */ jsxs(
|
|
58
|
+
"a",
|
|
59
|
+
{
|
|
60
|
+
href: item.url,
|
|
61
|
+
target: "_blank",
|
|
62
|
+
rel: "noopener noreferrer",
|
|
63
|
+
title: item.url,
|
|
64
|
+
"data-testid": "from-the-call-link",
|
|
65
|
+
className: cn(CHIP_PILL_CLASS, "max-w-[275px]", CHIP_LINK_CLASS),
|
|
66
|
+
children: [
|
|
67
|
+
/* @__PURE__ */ jsx(SourceFavicon, { href: item.url }),
|
|
68
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 max-w-[220px] overflow-hidden text-ellipsis", children: item.label })
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
item.key
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const tool = getToolDisplayMeta(item.toolName).displayName;
|
|
75
|
+
return /* @__PURE__ */ jsxs(
|
|
76
|
+
"button",
|
|
77
|
+
{
|
|
78
|
+
type: "button",
|
|
79
|
+
onClick: () => openApproval(item.toolCallId),
|
|
80
|
+
"data-testid": "from-the-call-approval",
|
|
81
|
+
className: cn(CHIP_PILL_CLASS, "max-w-full", CHIP_LINK_CLASS),
|
|
82
|
+
children: [
|
|
83
|
+
/* @__PURE__ */ jsx(HandIcon, { className: cn(CHIP_ICON_CLASS, "text-amber-600"), "aria-hidden": true }),
|
|
84
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 overflow-hidden text-ellipsis", children: [
|
|
85
|
+
CALL_RECAP_APPROVAL_LABEL,
|
|
86
|
+
" · ",
|
|
87
|
+
tool
|
|
88
|
+
] })
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
item.key
|
|
92
|
+
);
|
|
93
|
+
}) })
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
const StatewireFromTheCall = ({ recap }) => {
|
|
99
|
+
const callMayBeLive = useStatewireState(mayHaveLiveCall);
|
|
100
|
+
const { toolCallIds } = useStatewireApprovalWait();
|
|
101
|
+
const items = useMemo(() => callRecapItems(recap, toolCallIds), [recap, toolCallIds]);
|
|
102
|
+
if (recap.trailing && callMayBeLive) return null;
|
|
103
|
+
return /* @__PURE__ */ jsx(FromTheCallCard, { items });
|
|
104
|
+
};
|
|
105
|
+
const StaticFromTheCall = ({ recap }) => {
|
|
106
|
+
const items = useMemo(() => callRecapItems(recap, NO_IDS), [recap]);
|
|
107
|
+
return /* @__PURE__ */ jsx(FromTheCallCard, { items });
|
|
108
|
+
};
|
|
109
|
+
const FromTheCallAfterMessage = () => {
|
|
110
|
+
const recap = useAuiState(
|
|
111
|
+
(s) => {
|
|
112
|
+
var _a;
|
|
113
|
+
return ((_a = s.thread.messages[s.message.index]) == null ? void 0 : _a.id) === s.message.id ? callRecapAfter(s.thread.messages, s.message.index) : null;
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
const statewire = useAuiState((s) => hasStatewireThreadExtras(s.thread.extras));
|
|
117
|
+
if (!recap) return null;
|
|
118
|
+
return statewire ? /* @__PURE__ */ jsx(StatewireFromTheCall, { recap }) : /* @__PURE__ */ jsx(StaticFromTheCall, { recap });
|
|
119
|
+
};
|
|
120
|
+
export {
|
|
121
|
+
FromTheCallAfterMessage,
|
|
122
|
+
FromTheCallCard
|
|
123
|
+
};
|
|
124
|
+
//# sourceMappingURL=from-the-call-card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"from-the-call-card.js","sources":["../../../src/chat-ui/messages/from-the-call-card.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * \"From the call\": the card a transcript shows where a voice call's stretch\n * ends, collecting what the call produced or mentioned — assets, web links and\n * the approvals it is still waiting on. The derivation is\n * `@athenaintel/agent-runtime`'s `call-recap` (shared with Olympus and the\n * phone app); this is the SDK's rendering, which `ChatThreadShell` mounts after\n * every message, so the Chrome side panel and the desktop app show it too.\n *\n * Every item opens through the transcript's own path: an asset is the asset\n * chip the message body draws (`MentionLink`), and a click goes through the\n * same link handler as a click in a message; a web link opens in a new tab;\n * an approval scrolls to the approval card, where it is answered.\n */\nimport { useAuiState } from \"@assistant-ui/react\";\nimport { useStatewireState } from \"@assistant-ui/react-statewire\";\nimport {\n CALL_RECAP_APPROVAL_LABEL,\n CALL_RECAP_TITLE,\n type CallRecap,\n type CallRecapItem,\n callRecapAfter,\n callRecapItems,\n getToolDisplayMeta,\n hasStatewireThreadExtras,\n mayHaveLiveCall,\n} from \"@athenaintel/agent-runtime\";\nimport { AudioLinesIcon, HandIcon } from \"lucide-react\";\nimport { type FC, useCallback, useMemo } from \"react\";\nimport { useStatewireApprovalWait } from \"../../chat/use-statewire-approval-request\";\nimport { cn } from \"../../lib/utils\";\nimport {\n CHIP_ICON_CLASS,\n CHIP_LINK_CLASS,\n CHIP_PILL_CLASS,\n MentionLink,\n} from \"../mentions/mention-chip\";\nimport { SourceFavicon } from \"../rich-text/chat-link\";\nimport { useChatLinkClick } from \"../rich-text/chat-message-body\";\n\n/** The CSS class `StatewireApprovalCard` puts on its section. */\nconst APPROVAL_CARD_SELECTOR = \".aui-approval-card\";\n\nconst NO_IDS: ReadonlySet<string> = new Set();\n\nexport interface FromTheCallCardProps {\n /** What the call produced or mentioned, in the order to show it (`callRecapItems`). */\n items: readonly CallRecapItem[];\n /** An approval item was clicked; defaults to scrolling the approval card into view. */\n onApprovalClick?: (toolCallId: string) => void;\n className?: string;\n}\n\nfunction scrollToApprovalCard(): void {\n if (typeof document === \"undefined\") return;\n document\n .querySelector(APPROVAL_CARD_SELECTOR)\n ?.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n}\n\n/** An asset's mention link: the one the transcript carried, else the asset's own mention URI. */\nfunction assetHref(item: Extract<CallRecapItem, { kind: \"asset\" }>): string {\n if (item.href) return item.href;\n const params = new URLSearchParams({ id: item.assetId });\n if (item.assetType) params.set(\"type\", item.assetType);\n return `mention://asset?${params.toString()}`;\n}\n\n/** Pure presenter: a compact card in the voice-task card's style. Renders nothing without items. */\nexport const FromTheCallCard: FC<FromTheCallCardProps> = ({\n items,\n onApprovalClick,\n className,\n}) => {\n const handleLinkClick = useChatLinkClick();\n const openApproval = useCallback(\n (toolCallId: string) =>\n onApprovalClick ? onApprovalClick(toolCallId) : scrollToApprovalCard(),\n [onApprovalClick],\n );\n if (items.length === 0) return null;\n return (\n <section\n aria-label={CALL_RECAP_TITLE}\n data-testid=\"from-the-call-card\"\n className={cn(\n \"my-2 w-full rounded-xl border border-primary/20 bg-primary/5 px-3 py-2\",\n className,\n )}\n >\n <div className=\"mb-1.5 flex items-center gap-1.5\">\n <span className=\"inline-flex size-4 items-center justify-center rounded bg-primary/15\">\n <AudioLinesIcon className=\"size-2.5 text-primary\" aria-hidden />\n </span>\n <span className=\"text-[10px] font-medium uppercase tracking-wide text-primary\">\n {CALL_RECAP_TITLE}\n </span>\n </div>\n {/* One delegated handler, as in a message body: asset chips open where a transcript chip would. */}\n {/* biome-ignore lint/a11y/noNoninteractiveElementInteractions lint/a11y/noStaticElementInteractions lint/a11y/useKeyWithClickEvents: delegated from the links inside, which stay the focusable controls and fire this click on Enter */}\n <div className=\"flex flex-wrap items-center gap-1.5 text-sm\" onClick={handleLinkClick}>\n {items.map((item) => {\n if (item.kind === \"asset\") {\n return (\n <MentionLink key={item.key} href={assetHref(item)}>\n {item.title || \"Untitled\"}\n </MentionLink>\n );\n }\n if (item.kind === \"link\") {\n return (\n <a\n key={item.key}\n href={item.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n title={item.url}\n data-testid=\"from-the-call-link\"\n className={cn(CHIP_PILL_CLASS, \"max-w-[275px]\", CHIP_LINK_CLASS)}\n >\n <SourceFavicon href={item.url} />\n <span className=\"min-w-0 max-w-[220px] overflow-hidden text-ellipsis\">\n {item.label}\n </span>\n </a>\n );\n }\n const tool = getToolDisplayMeta(item.toolName).displayName;\n return (\n <button\n key={item.key}\n type=\"button\"\n onClick={() => openApproval(item.toolCallId)}\n data-testid=\"from-the-call-approval\"\n className={cn(CHIP_PILL_CLASS, \"max-w-full\", CHIP_LINK_CLASS)}\n >\n <HandIcon className={cn(CHIP_ICON_CLASS, \"text-amber-600\")} aria-hidden />\n <span className=\"min-w-0 overflow-hidden text-ellipsis\">\n {CALL_RECAP_APPROVAL_LABEL} · {tool}\n </span>\n </button>\n );\n })}\n </div>\n </section>\n );\n};\n\n/** The card for a recap on a statewire thread: hidden while its call may be live, approvals from the run's wait. */\nconst StatewireFromTheCall: FC<{ recap: CallRecap }> = ({ recap }) => {\n const callMayBeLive = useStatewireState(mayHaveLiveCall);\n const { toolCallIds } = useStatewireApprovalWait();\n const items = useMemo(() => callRecapItems(recap, toolCallIds), [recap, toolCallIds]);\n if (recap.trailing && callMayBeLive) return null;\n return <FromTheCallCard items={items} />;\n};\n\n/** A thread with no statewire scope (a read-only transcript) knows of no live call or approval. */\nconst StaticFromTheCall: FC<{ recap: CallRecap }> = ({ recap }) => {\n const items = useMemo(() => callRecapItems(recap, NO_IDS), [recap]);\n return <FromTheCallCard items={items} />;\n};\n\n/**\n * Inside a message row: the card when a call's stretch ends at this message.\n * Selects a recap that keeps its identity while unchanged, so the row\n * re-renders only when the card's contents do.\n */\nexport const FromTheCallAfterMessage: FC = () => {\n const recap = useAuiState((s) =>\n s.thread.messages[s.message.index]?.id === s.message.id\n ? callRecapAfter(s.thread.messages, s.message.index)\n : null,\n );\n const statewire = useAuiState((s) => hasStatewireThreadExtras(s.thread.extras));\n if (!recap) return null;\n return statewire ? <StatewireFromTheCall recap={recap} /> : <StaticFromTheCall recap={recap} />;\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-link.cjs","sources":["../../../src/chat-ui/rich-text/chat-link.tsx"],"sourcesContent":["import { Globe2Icon } from \"lucide-react\";\nimport { type ReactNode, useState } from \"react\";\nimport { CHIP_ICON_CLASS, CHIP_LINK_CLASS, CHIP_PILL_CLASS, MentionLink } from \"../mentions/mention-chip\";\nimport { cn } from \"../../lib/utils\";\nimport {\n buildScrollToTextUrl,\n isWebHref,\n sourceFaviconUrl,\n} from \"./citation-anchors\";\n\n/**\n * A link mark of the static message body. A web source the model cited with\n * an excerpt renders as a source chip; everything else goes through\n * `MentionLink`, which draws mention and Spaces hrefs as asset chips,\n * screen points as coordinate chips, and the rest as a plain link.\n */\nexport function ChatLink({\n href,\n excerpt,\n children,\n}: {\n href: string | null | undefined;\n excerpt?: string | null;\n children: ReactNode;\n}) {\n if (href && excerpt && isWebHref(href)) {\n return (\n <SourceChip href={href} excerpt={excerpt}>\n {children}\n </SourceChip>\n );\n }\n return <MentionLink href={href ?? undefined}>{children}</MentionLink>;\n}\n\n/**\n * A cited web page: the site's favicon, the model's label, the excerpt as\n * the tooltip, opening the page at the excerpt (scroll-to-text) in a new tab.\n * The asset mention chip's pill (`CHIP_PILL_CLASS`), so a sentence that cites\n * a PDF and a web page reads as one row of sources.\n */\nexport function SourceChip({\n href,\n excerpt,\n children,\n}: {\n href: string;\n excerpt: string;\n children: ReactNode;\n}) {\n return (\n <a\n href={buildScrollToTextUrl(href, excerpt)}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n title={excerpt}\n data-chat-source=\"\"\n data-excerpt={excerpt}\n className={cn(CHIP_PILL_CLASS, \"max-w-[240px]\", CHIP_LINK_CLASS)}\n >\n <SourceFavicon href={href} />\n <span className=\"min-w-0 max-w-[200px] overflow-hidden text-ellipsis\">\n {children}\n </span>\n </a>\n );\n}\n\n/** The site's favicon; a globe once the favicon service has no icon for the host. */\
|
|
1
|
+
{"version":3,"file":"chat-link.cjs","sources":["../../../src/chat-ui/rich-text/chat-link.tsx"],"sourcesContent":["import { Globe2Icon } from \"lucide-react\";\nimport { type ReactNode, useState } from \"react\";\nimport { CHIP_ICON_CLASS, CHIP_LINK_CLASS, CHIP_PILL_CLASS, MentionLink } from \"../mentions/mention-chip\";\nimport { cn } from \"../../lib/utils\";\nimport {\n buildScrollToTextUrl,\n isWebHref,\n sourceFaviconUrl,\n} from \"./citation-anchors\";\n\n/**\n * A link mark of the static message body. A web source the model cited with\n * an excerpt renders as a source chip; everything else goes through\n * `MentionLink`, which draws mention and Spaces hrefs as asset chips,\n * screen points as coordinate chips, and the rest as a plain link.\n */\nexport function ChatLink({\n href,\n excerpt,\n children,\n}: {\n href: string | null | undefined;\n excerpt?: string | null;\n children: ReactNode;\n}) {\n if (href && excerpt && isWebHref(href)) {\n return (\n <SourceChip href={href} excerpt={excerpt}>\n {children}\n </SourceChip>\n );\n }\n return <MentionLink href={href ?? undefined}>{children}</MentionLink>;\n}\n\n/**\n * A cited web page: the site's favicon, the model's label, the excerpt as\n * the tooltip, opening the page at the excerpt (scroll-to-text) in a new tab.\n * The asset mention chip's pill (`CHIP_PILL_CLASS`), so a sentence that cites\n * a PDF and a web page reads as one row of sources.\n */\nexport function SourceChip({\n href,\n excerpt,\n children,\n}: {\n href: string;\n excerpt: string;\n children: ReactNode;\n}) {\n return (\n <a\n href={buildScrollToTextUrl(href, excerpt)}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n title={excerpt}\n data-chat-source=\"\"\n data-excerpt={excerpt}\n className={cn(CHIP_PILL_CLASS, \"max-w-[240px]\", CHIP_LINK_CLASS)}\n >\n <SourceFavicon href={href} />\n <span className=\"min-w-0 max-w-[200px] overflow-hidden text-ellipsis\">\n {children}\n </span>\n </a>\n );\n}\n\n/** The site's favicon; a globe once the favicon service has no icon for the host. */\nexport function SourceFavicon({ href }: { href: string }) {\n const [failed, setFailed] = useState(false);\n const src = failed ? null : sourceFaviconUrl(href);\n if (!src) {\n return (\n <Globe2Icon\n className={cn(CHIP_ICON_CLASS, \"text-muted-foreground\")}\n aria-hidden=\"true\"\n />\n );\n }\n return (\n <img\n src={src}\n alt=\"\"\n width={14}\n height={14}\n className={cn(CHIP_ICON_CLASS, \"rounded-[3px]\")}\n loading=\"lazy\"\n referrerPolicy=\"no-referrer\"\n draggable={false}\n onError={() => setFailed(true)}\n />\n );\n}\n"],"names":["isWebHref","jsx","MentionLink","jsxs","buildScrollToTextUrl","cn","CHIP_PILL_CLASS","CHIP_LINK_CLASS","useState","sourceFaviconUrl","Globe2Icon","CHIP_ICON_CLASS"],"mappings":";;;;;;;;AAgBO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,QAAQ,WAAWA,gBAAAA,UAAU,IAAI,GAAG;AACtC,WACEC,2BAAAA,IAAC,YAAA,EAAW,MAAY,SACrB,SAAA,CACH;AAAA,EAEJ;AACA,SAAOA,2BAAAA,IAACC,YAAAA,aAAA,EAAY,MAAM,QAAQ,QAAY,UAAS;AACzD;AAQO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,SACEC,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAMC,gBAAAA,qBAAqB,MAAM,OAAO;AAAA,MACxC,QAAO;AAAA,MACP,KAAI;AAAA,MACJ,OAAO;AAAA,MACP,oBAAiB;AAAA,MACjB,gBAAc;AAAA,MACd,WAAWC,OAAAA,GAAGC,6BAAiB,iBAAiBC,YAAAA,eAAe;AAAA,MAE/D,UAAA;AAAA,QAAAN,+BAAC,iBAAc,MAAY;AAAA,QAC3BA,2BAAAA,IAAC,QAAA,EAAK,WAAU,uDACb,SAAA,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;AAGO,SAAS,cAAc,EAAE,QAA0B;AACxD,QAAM,CAAC,QAAQ,SAAS,IAAIO,WAAAA,SAAS,KAAK;AAC1C,QAAM,MAAM,SAAS,OAAOC,gBAAAA,iBAAiB,IAAI;AACjD,MAAI,CAAC,KAAK;AACR,WACER,2BAAAA;AAAAA,MAACS,YAAAA;AAAAA,MAAA;AAAA,QACC,WAAWL,OAAAA,GAAGM,YAAAA,iBAAiB,uBAAuB;AAAA,QACtD,eAAY;AAAA,MAAA;AAAA,IAAA;AAAA,EAGlB;AACA,SACEV,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,KAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,WAAWI,OAAAA,GAAGM,YAAAA,iBAAiB,eAAe;AAAA,MAC9C,SAAQ;AAAA,MACR,gBAAe;AAAA,MACf,WAAW;AAAA,MACX,SAAS,MAAM,UAAU,IAAI;AAAA,IAAA;AAAA,EAAA;AAGnC;;;;"}
|
|
@@ -21,3 +21,7 @@ export declare function SourceChip({ href, excerpt, children, }: {
|
|
|
21
21
|
excerpt: string;
|
|
22
22
|
children: ReactNode;
|
|
23
23
|
}): import("react").JSX.Element;
|
|
24
|
+
/** The site's favicon; a globe once the favicon service has no icon for the host. */
|
|
25
|
+
export declare function SourceFavicon({ href }: {
|
|
26
|
+
href: string;
|
|
27
|
+
}): import("react").JSX.Element;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Globe2Icon } from "lucide-react";
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import { MentionLink, CHIP_PILL_CLASS, CHIP_LINK_CLASS
|
|
5
|
-
import { isWebHref, buildScrollToTextUrl
|
|
4
|
+
import { CHIP_ICON_CLASS, MentionLink, CHIP_PILL_CLASS, CHIP_LINK_CLASS } from "../mentions/mention-chip.js";
|
|
5
|
+
import { sourceFaviconUrl, isWebHref, buildScrollToTextUrl } from "./citation-anchors.js";
|
|
6
6
|
import { cn } from "cnfast";
|
|
7
7
|
function ChatLink({
|
|
8
8
|
href,
|
|
@@ -65,6 +65,7 @@ function SourceFavicon({ href }) {
|
|
|
65
65
|
}
|
|
66
66
|
export {
|
|
67
67
|
ChatLink,
|
|
68
|
-
SourceChip
|
|
68
|
+
SourceChip,
|
|
69
|
+
SourceFavicon
|
|
69
70
|
};
|
|
70
71
|
//# sourceMappingURL=chat-link.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-link.js","sources":["../../../src/chat-ui/rich-text/chat-link.tsx"],"sourcesContent":["import { Globe2Icon } from \"lucide-react\";\nimport { type ReactNode, useState } from \"react\";\nimport { CHIP_ICON_CLASS, CHIP_LINK_CLASS, CHIP_PILL_CLASS, MentionLink } from \"../mentions/mention-chip\";\nimport { cn } from \"../../lib/utils\";\nimport {\n buildScrollToTextUrl,\n isWebHref,\n sourceFaviconUrl,\n} from \"./citation-anchors\";\n\n/**\n * A link mark of the static message body. A web source the model cited with\n * an excerpt renders as a source chip; everything else goes through\n * `MentionLink`, which draws mention and Spaces hrefs as asset chips,\n * screen points as coordinate chips, and the rest as a plain link.\n */\nexport function ChatLink({\n href,\n excerpt,\n children,\n}: {\n href: string | null | undefined;\n excerpt?: string | null;\n children: ReactNode;\n}) {\n if (href && excerpt && isWebHref(href)) {\n return (\n <SourceChip href={href} excerpt={excerpt}>\n {children}\n </SourceChip>\n );\n }\n return <MentionLink href={href ?? undefined}>{children}</MentionLink>;\n}\n\n/**\n * A cited web page: the site's favicon, the model's label, the excerpt as\n * the tooltip, opening the page at the excerpt (scroll-to-text) in a new tab.\n * The asset mention chip's pill (`CHIP_PILL_CLASS`), so a sentence that cites\n * a PDF and a web page reads as one row of sources.\n */\nexport function SourceChip({\n href,\n excerpt,\n children,\n}: {\n href: string;\n excerpt: string;\n children: ReactNode;\n}) {\n return (\n <a\n href={buildScrollToTextUrl(href, excerpt)}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n title={excerpt}\n data-chat-source=\"\"\n data-excerpt={excerpt}\n className={cn(CHIP_PILL_CLASS, \"max-w-[240px]\", CHIP_LINK_CLASS)}\n >\n <SourceFavicon href={href} />\n <span className=\"min-w-0 max-w-[200px] overflow-hidden text-ellipsis\">\n {children}\n </span>\n </a>\n );\n}\n\n/** The site's favicon; a globe once the favicon service has no icon for the host. */\
|
|
1
|
+
{"version":3,"file":"chat-link.js","sources":["../../../src/chat-ui/rich-text/chat-link.tsx"],"sourcesContent":["import { Globe2Icon } from \"lucide-react\";\nimport { type ReactNode, useState } from \"react\";\nimport { CHIP_ICON_CLASS, CHIP_LINK_CLASS, CHIP_PILL_CLASS, MentionLink } from \"../mentions/mention-chip\";\nimport { cn } from \"../../lib/utils\";\nimport {\n buildScrollToTextUrl,\n isWebHref,\n sourceFaviconUrl,\n} from \"./citation-anchors\";\n\n/**\n * A link mark of the static message body. A web source the model cited with\n * an excerpt renders as a source chip; everything else goes through\n * `MentionLink`, which draws mention and Spaces hrefs as asset chips,\n * screen points as coordinate chips, and the rest as a plain link.\n */\nexport function ChatLink({\n href,\n excerpt,\n children,\n}: {\n href: string | null | undefined;\n excerpt?: string | null;\n children: ReactNode;\n}) {\n if (href && excerpt && isWebHref(href)) {\n return (\n <SourceChip href={href} excerpt={excerpt}>\n {children}\n </SourceChip>\n );\n }\n return <MentionLink href={href ?? undefined}>{children}</MentionLink>;\n}\n\n/**\n * A cited web page: the site's favicon, the model's label, the excerpt as\n * the tooltip, opening the page at the excerpt (scroll-to-text) in a new tab.\n * The asset mention chip's pill (`CHIP_PILL_CLASS`), so a sentence that cites\n * a PDF and a web page reads as one row of sources.\n */\nexport function SourceChip({\n href,\n excerpt,\n children,\n}: {\n href: string;\n excerpt: string;\n children: ReactNode;\n}) {\n return (\n <a\n href={buildScrollToTextUrl(href, excerpt)}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n title={excerpt}\n data-chat-source=\"\"\n data-excerpt={excerpt}\n className={cn(CHIP_PILL_CLASS, \"max-w-[240px]\", CHIP_LINK_CLASS)}\n >\n <SourceFavicon href={href} />\n <span className=\"min-w-0 max-w-[200px] overflow-hidden text-ellipsis\">\n {children}\n </span>\n </a>\n );\n}\n\n/** The site's favicon; a globe once the favicon service has no icon for the host. */\nexport function SourceFavicon({ href }: { href: string }) {\n const [failed, setFailed] = useState(false);\n const src = failed ? null : sourceFaviconUrl(href);\n if (!src) {\n return (\n <Globe2Icon\n className={cn(CHIP_ICON_CLASS, \"text-muted-foreground\")}\n aria-hidden=\"true\"\n />\n );\n }\n return (\n <img\n src={src}\n alt=\"\"\n width={14}\n height={14}\n className={cn(CHIP_ICON_CLASS, \"rounded-[3px]\")}\n loading=\"lazy\"\n referrerPolicy=\"no-referrer\"\n draggable={false}\n onError={() => setFailed(true)}\n />\n );\n}\n"],"names":[],"mappings":";;;;;;AAgBO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,QAAQ,WAAW,UAAU,IAAI,GAAG;AACtC,WACE,oBAAC,YAAA,EAAW,MAAY,SACrB,SAAA,CACH;AAAA,EAEJ;AACA,SAAO,oBAAC,aAAA,EAAY,MAAM,QAAQ,QAAY,UAAS;AACzD;AAQO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAM,qBAAqB,MAAM,OAAO;AAAA,MACxC,QAAO;AAAA,MACP,KAAI;AAAA,MACJ,OAAO;AAAA,MACP,oBAAiB;AAAA,MACjB,gBAAc;AAAA,MACd,WAAW,GAAG,iBAAiB,iBAAiB,eAAe;AAAA,MAE/D,UAAA;AAAA,QAAA,oBAAC,iBAAc,MAAY;AAAA,QAC3B,oBAAC,QAAA,EAAK,WAAU,uDACb,SAAA,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;AAGO,SAAS,cAAc,EAAE,QAA0B;AACxD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,MAAM,SAAS,OAAO,iBAAiB,IAAI;AACjD,MAAI,CAAC,KAAK;AACR,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,GAAG,iBAAiB,uBAAuB;AAAA,QACtD,eAAY;AAAA,MAAA;AAAA,IAAA;AAAA,EAGlB;AACA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,KAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,WAAW,GAAG,iBAAiB,eAAe;AAAA,MAC9C,SAAQ;AAAA,MACR,gBAAe;AAAA,MACf,WAAW;AAAA,MACX,SAAS,MAAM,UAAU,IAAI;AAAA,IAAA;AAAA,EAAA;AAGnC;"}
|