@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.
Files changed (255) hide show
  1. package/README.md +68 -10
  2. package/dist/agent-runtime/src/call-recap.cjs +319 -0
  3. package/dist/agent-runtime/src/call-recap.cjs.map +1 -0
  4. package/dist/agent-runtime/src/call-recap.d.ts +142 -0
  5. package/dist/agent-runtime/src/call-recap.js +319 -0
  6. package/dist/agent-runtime/src/call-recap.js.map +1 -0
  7. package/dist/agent-runtime/src/chat-outbox-store.cjs +1 -0
  8. package/dist/agent-runtime/src/chat-outbox-store.cjs.map +1 -1
  9. package/dist/agent-runtime/src/chat-outbox-store.d.ts +7 -0
  10. package/dist/agent-runtime/src/chat-outbox-store.js +1 -0
  11. package/dist/agent-runtime/src/chat-outbox-store.js.map +1 -1
  12. package/dist/agent-runtime/src/chat-outbox.cjs +46 -5
  13. package/dist/agent-runtime/src/chat-outbox.cjs.map +1 -1
  14. package/dist/agent-runtime/src/chat-outbox.d.ts +29 -0
  15. package/dist/agent-runtime/src/chat-outbox.js +46 -5
  16. package/dist/agent-runtime/src/chat-outbox.js.map +1 -1
  17. package/dist/agent-runtime/src/conversation-index.cjs.map +1 -1
  18. package/dist/agent-runtime/src/conversation-index.d.ts +7 -0
  19. package/dist/agent-runtime/src/conversation-index.js.map +1 -1
  20. package/dist/agent-runtime/src/conversation-list.cjs +2 -1
  21. package/dist/agent-runtime/src/conversation-list.cjs.map +1 -1
  22. package/dist/agent-runtime/src/conversation-list.d.ts +19 -3
  23. package/dist/agent-runtime/src/conversation-list.js +2 -1
  24. package/dist/agent-runtime/src/conversation-list.js.map +1 -1
  25. package/dist/agent-runtime/src/desktop-handoff.d.ts +10 -0
  26. package/dist/agent-runtime/src/edit-command.cjs +21 -0
  27. package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
  28. package/dist/agent-runtime/src/edit-command.d.ts +8 -0
  29. package/dist/agent-runtime/src/edit-command.js +21 -0
  30. package/dist/agent-runtime/src/edit-command.js.map +1 -1
  31. package/dist/agent-runtime/src/handoff-arrival.cjs +19 -0
  32. package/dist/agent-runtime/src/handoff-arrival.cjs.map +1 -1
  33. package/dist/agent-runtime/src/handoff-arrival.d.ts +18 -3
  34. package/dist/agent-runtime/src/handoff-arrival.js +20 -1
  35. package/dist/agent-runtime/src/handoff-arrival.js.map +1 -1
  36. package/dist/agent-runtime/src/handoff-cursor-id.cjs +6 -0
  37. package/dist/agent-runtime/src/handoff-cursor-id.cjs.map +1 -0
  38. package/dist/agent-runtime/src/handoff-cursor-id.d.ts +12 -0
  39. package/dist/agent-runtime/src/handoff-cursor-id.js +6 -0
  40. package/dist/agent-runtime/src/handoff-cursor-id.js.map +1 -0
  41. package/dist/agent-runtime/src/handoff-cursor.cjs +88 -0
  42. package/dist/agent-runtime/src/handoff-cursor.cjs.map +1 -0
  43. package/dist/agent-runtime/src/handoff-cursor.d.ts +120 -0
  44. package/dist/agent-runtime/src/handoff-cursor.js +88 -0
  45. package/dist/agent-runtime/src/handoff-cursor.js.map +1 -0
  46. package/dist/agent-runtime/src/index.d.ts +9 -7
  47. package/dist/agent-runtime/src/interrupts.cjs +28 -1
  48. package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
  49. package/dist/agent-runtime/src/interrupts.d.ts +21 -1
  50. package/dist/agent-runtime/src/interrupts.js +28 -1
  51. package/dist/agent-runtime/src/interrupts.js.map +1 -1
  52. package/dist/agent-runtime/src/model-context.cjs +3 -2
  53. package/dist/agent-runtime/src/model-context.cjs.map +1 -1
  54. package/dist/agent-runtime/src/model-context.d.ts +6 -1
  55. package/dist/agent-runtime/src/model-context.js +3 -2
  56. package/dist/agent-runtime/src/model-context.js.map +1 -1
  57. package/dist/agent-runtime/src/runtime.d.ts +3 -2
  58. package/dist/agent-runtime/src/send-recovery.cjs +37 -3
  59. package/dist/agent-runtime/src/send-recovery.cjs.map +1 -1
  60. package/dist/agent-runtime/src/send-recovery.d.ts +19 -0
  61. package/dist/agent-runtime/src/send-recovery.js +37 -3
  62. package/dist/agent-runtime/src/send-recovery.js.map +1 -1
  63. package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +14 -8
  64. package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +227 -5
  65. package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
  66. package/dist/agent-runtime/src/use-conversation-list.cjs +3 -2
  67. package/dist/agent-runtime/src/use-conversation-list.cjs.map +1 -1
  68. package/dist/agent-runtime/src/use-conversation-list.d.ts +6 -0
  69. package/dist/agent-runtime/src/use-conversation-list.js +3 -2
  70. package/dist/agent-runtime/src/use-conversation-list.js.map +1 -1
  71. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs +21 -1
  72. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs.map +1 -1
  73. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.d.ts +8 -2
  74. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js +21 -1
  75. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js.map +1 -1
  76. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +24 -6
  77. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
  78. package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +17 -0
  79. package/dist/agent-runtime/src/use-deep-agent-runtime.js +24 -6
  80. package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
  81. package/dist/agent-runtime/src/use-deep-agent-voice.cjs +44 -9
  82. package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
  83. package/dist/agent-runtime/src/use-deep-agent-voice.d.ts +34 -1
  84. package/dist/agent-runtime/src/use-deep-agent-voice.js +44 -9
  85. package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
  86. package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs +8 -3
  87. package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -1
  88. package/dist/agent-runtime/src/use-deep-agent-window-sync.d.ts +7 -0
  89. package/dist/agent-runtime/src/use-deep-agent-window-sync.js +9 -4
  90. package/dist/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -1
  91. package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +13 -2
  92. package/dist/agent-runtime/src/voice-earcon.cjs +139 -0
  93. package/dist/agent-runtime/src/voice-earcon.cjs.map +1 -0
  94. package/dist/agent-runtime/src/voice-earcon.d.ts +38 -0
  95. package/dist/agent-runtime/src/voice-earcon.js +139 -0
  96. package/dist/agent-runtime/src/voice-earcon.js.map +1 -0
  97. package/dist/agent-runtime/src/voice-media.cjs.map +1 -1
  98. package/dist/agent-runtime/src/voice-media.d.ts +14 -0
  99. package/dist/agent-runtime/src/voice-media.js.map +1 -1
  100. package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
  101. package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
  102. package/dist/agent-runtime/src/voice-presentation.js +2 -0
  103. package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
  104. package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
  105. package/dist/assets/AthenaAssetEmbed.d.ts +11 -5
  106. package/dist/assets/AthenaAssetEmbed.js.map +1 -1
  107. package/dist/assets/use-asset-embed.cjs.map +1 -1
  108. package/dist/assets/use-asset-embed.d.ts +12 -5
  109. package/dist/assets/use-asset-embed.js.map +1 -1
  110. package/dist/chat/AthenaChat.cjs +27 -12
  111. package/dist/chat/AthenaChat.cjs.map +1 -1
  112. package/dist/chat/AthenaChat.d.ts +13 -0
  113. package/dist/chat/AthenaChat.js +27 -12
  114. package/dist/chat/AthenaChat.js.map +1 -1
  115. package/dist/chat/AthenaPlanStrip.cjs +97 -0
  116. package/dist/chat/AthenaPlanStrip.cjs.map +1 -0
  117. package/dist/chat/AthenaPlanStrip.d.ts +15 -0
  118. package/dist/chat/AthenaPlanStrip.js +97 -0
  119. package/dist/chat/AthenaPlanStrip.js.map +1 -0
  120. package/dist/chat/StatewireApprovalCard.cjs +41 -6
  121. package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
  122. package/dist/chat/StatewireApprovalCard.d.ts +18 -1
  123. package/dist/chat/StatewireApprovalCard.js +42 -7
  124. package/dist/chat/StatewireApprovalCard.js.map +1 -1
  125. package/dist/chat/StatewireQueuedMessages.cjs +56 -6
  126. package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
  127. package/dist/chat/StatewireQueuedMessages.d.ts +14 -0
  128. package/dist/chat/StatewireQueuedMessages.js +58 -8
  129. package/dist/chat/StatewireQueuedMessages.js.map +1 -1
  130. package/dist/chat/WaitingOnYouBanner.cjs +6 -2
  131. package/dist/chat/WaitingOnYouBanner.cjs.map +1 -1
  132. package/dist/chat/WaitingOnYouBanner.js +7 -3
  133. package/dist/chat/WaitingOnYouBanner.js.map +1 -1
  134. package/dist/chat/chat-voice-controls.cjs +3 -1
  135. package/dist/chat/chat-voice-controls.cjs.map +1 -1
  136. package/dist/chat/chat-voice-controls.d.ts +6 -1
  137. package/dist/chat/chat-voice-controls.js +3 -1
  138. package/dist/chat/chat-voice-controls.js.map +1 -1
  139. package/dist/chat/super-grouping/SuperGroupingCard.cjs +4 -2
  140. package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
  141. package/dist/chat/super-grouping/SuperGroupingCard.js +4 -2
  142. package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
  143. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs +4 -2
  144. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
  145. package/dist/chat/super-grouping/SuperGroupingTranscript.js +4 -2
  146. package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
  147. package/dist/chat/super-grouping/activity-timeline.cjs +6 -4
  148. package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
  149. package/dist/chat/super-grouping/activity-timeline.d.ts +9 -1
  150. package/dist/chat/super-grouping/activity-timeline.js +6 -4
  151. package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
  152. package/dist/chat/super-grouping/index.d.ts +1 -1
  153. package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs +17 -0
  154. package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs.map +1 -0
  155. package/dist/chat/super-grouping/use-earlier-steps-unloaded.d.ts +8 -0
  156. package/dist/chat/super-grouping/use-earlier-steps-unloaded.js +17 -0
  157. package/dist/chat/super-grouping/use-earlier-steps-unloaded.js.map +1 -0
  158. package/dist/chat-ui/index.cjs +11 -0
  159. package/dist/chat-ui/index.cjs.map +1 -1
  160. package/dist/chat-ui/index.d.ts +3 -1
  161. package/dist/chat-ui/index.js +12 -1
  162. package/dist/chat-ui/index.js.map +1 -1
  163. package/dist/chat-ui/messages/from-the-call-card.cjs +124 -0
  164. package/dist/chat-ui/messages/from-the-call-card.cjs.map +1 -0
  165. package/dist/chat-ui/messages/from-the-call-card.d.ts +17 -0
  166. package/dist/chat-ui/messages/from-the-call-card.js +124 -0
  167. package/dist/chat-ui/messages/from-the-call-card.js.map +1 -0
  168. package/dist/chat-ui/rich-text/chat-link.cjs +1 -0
  169. package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -1
  170. package/dist/chat-ui/rich-text/chat-link.d.ts +4 -0
  171. package/dist/chat-ui/rich-text/chat-link.js +4 -3
  172. package/dist/chat-ui/rich-text/chat-link.js.map +1 -1
  173. package/dist/chat-ui/rich-text/chat-message-body.cjs +1 -0
  174. package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
  175. package/dist/chat-ui/rich-text/chat-message-body.d.ts +12 -0
  176. package/dist/chat-ui/rich-text/chat-message-body.js +2 -1
  177. package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
  178. package/dist/chat-ui/thread/chat-thread-shell.cjs +4 -0
  179. package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
  180. package/dist/chat-ui/thread/chat-thread-shell.d.ts +4 -0
  181. package/dist/chat-ui/thread/chat-thread-shell.js +4 -0
  182. package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
  183. package/dist/chat-ui/thread/handoff-cursor.cjs +216 -0
  184. package/dist/chat-ui/thread/handoff-cursor.cjs.map +1 -0
  185. package/dist/chat-ui/thread/handoff-cursor.d.ts +57 -0
  186. package/dist/chat-ui/thread/handoff-cursor.js +216 -0
  187. package/dist/chat-ui/thread/handoff-cursor.js.map +1 -0
  188. package/dist/chat-ui/thread/statewire-history-edge.cjs +2 -0
  189. package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
  190. package/dist/chat-ui/thread/statewire-history-edge.d.ts +2 -0
  191. package/dist/chat-ui/thread/statewire-history-edge.js +3 -1
  192. package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
  193. package/dist/collab/client.cjs +15 -3
  194. package/dist/collab/client.cjs.map +1 -1
  195. package/dist/collab/client.d.ts +4 -2
  196. package/dist/collab/client.js +16 -4
  197. package/dist/collab/client.js.map +1 -1
  198. package/dist/composer/index.d.ts +1 -1
  199. package/dist/composer/useReadComposerDraft.cjs +26 -7
  200. package/dist/composer/useReadComposerDraft.cjs.map +1 -1
  201. package/dist/composer/useReadComposerDraft.d.ts +23 -1
  202. package/dist/composer/useReadComposerDraft.js +27 -8
  203. package/dist/composer/useReadComposerDraft.js.map +1 -1
  204. package/dist/index.cjs +5 -0
  205. package/dist/index.cjs.map +1 -1
  206. package/dist/index.d.ts +5 -2
  207. package/dist/index.js +6 -1
  208. package/dist/index.js.map +1 -1
  209. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +207 -0
  210. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -0
  211. package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +141 -0
  212. package/dist/keryx-http/src/unconfirmed-update-attributions.js +207 -0
  213. package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -0
  214. package/dist/keryx-http/src/update-attributions.cjs +34 -4
  215. package/dist/keryx-http/src/update-attributions.cjs.map +1 -1
  216. package/dist/keryx-http/src/update-attributions.d.ts +36 -4
  217. package/dist/keryx-http/src/update-attributions.js +35 -5
  218. package/dist/keryx-http/src/update-attributions.js.map +1 -1
  219. package/dist/provider/AthenaProvider.cjs +4 -0
  220. package/dist/provider/AthenaProvider.cjs.map +1 -1
  221. package/dist/provider/AthenaProvider.d.ts +11 -1
  222. package/dist/provider/AthenaProvider.js +4 -0
  223. package/dist/provider/AthenaProvider.js.map +1 -1
  224. package/dist/react/package.json.cjs +1 -1
  225. package/dist/react/package.json.js +1 -1
  226. package/dist/styles.css +1 -1
  227. package/dist/threads/api.cjs.map +1 -1
  228. package/dist/threads/api.d.ts +15 -0
  229. package/dist/threads/api.js.map +1 -1
  230. package/dist/threads/conversation-list-fetcher.cjs +13 -5
  231. package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
  232. package/dist/threads/conversation-list-fetcher.d.ts +4 -1
  233. package/dist/threads/conversation-list-fetcher.js +13 -5
  234. package/dist/threads/conversation-list-fetcher.js.map +1 -1
  235. package/dist/threads/statewire-thread-list.cjs +28 -6
  236. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  237. package/dist/threads/statewire-thread-list.d.ts +20 -8
  238. package/dist/threads/statewire-thread-list.js +28 -6
  239. package/dist/threads/statewire-thread-list.js.map +1 -1
  240. package/dist/threads/use-athena-sessions.cjs +27 -5
  241. package/dist/threads/use-athena-sessions.cjs.map +1 -1
  242. package/dist/threads/use-athena-sessions.d.ts +10 -5
  243. package/dist/threads/use-athena-sessions.js +28 -6
  244. package/dist/threads/use-athena-sessions.js.map +1 -1
  245. package/dist/threads/use-rest-conversation-history.d.ts +1 -0
  246. package/dist/tools/write-todos-tool-ui.cjs +8 -0
  247. package/dist/tools/write-todos-tool-ui.cjs.map +1 -1
  248. package/dist/tools/write-todos-tool-ui.d.ts +2 -0
  249. package/dist/tools/write-todos-tool-ui.js +8 -0
  250. package/dist/tools/write-todos-tool-ui.js.map +1 -1
  251. package/dist/workbench/session-workbench-panes.cjs +1 -8
  252. package/dist/workbench/session-workbench-panes.cjs.map +1 -1
  253. package/dist/workbench/session-workbench-panes.js +2 -9
  254. package/dist/workbench/session-workbench-panes.js.map +1 -1
  255. package/package.json +2 -2
@@ -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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"}
@@ -67,4 +67,5 @@ function SourceFavicon({ href }) {
67
67
  }
68
68
  exports.ChatLink = ChatLink;
69
69
  exports.SourceChip = SourceChip;
70
+ exports.SourceFavicon = SourceFavicon;
70
71
  //# sourceMappingURL=chat-link.cjs.map
@@ -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. */\nfunction 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;AAGA,SAAS,cAAc,EAAE,QAA0B;AACjD,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;;;"}
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, CHIP_ICON_CLASS } from "../mentions/mention-chip.js";
5
- import { isWebHref, buildScrollToTextUrl, sourceFaviconUrl } from "./citation-anchors.js";
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. */\nfunction 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;AAGA,SAAS,cAAc,EAAE,QAA0B;AACjD,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;"}
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;"}
@@ -204,4 +204,5 @@ function renderMarkRuns(nodes, depth, tail) {
204
204
  return children;
205
205
  }
206
206
  exports.ChatMessageBody = ChatMessageBody;
207
+ exports.useChatLinkClick = useChatLinkClick;
207
208
  //# sourceMappingURL=chat-message-body.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"chat-message-body.cjs","sources":["../../../src/chat-ui/rich-text/chat-message-body.tsx"],"sourcesContent":["import type { Node as ProseMirrorNode } from \"@tiptap/pm/model\";\nimport {\n Fragment,\n memo,\n type MouseEvent,\n type ReactNode,\n useCallback,\n useMemo,\n useSyncExternalStore,\n} from \"react\";\nimport { useAthenaLinkClickHandler } from \"../../chat/athena-citation-links\";\nimport { type OpenFence, splitChatBlocks } from \"./chat-blocks\";\nimport { ChatLink } from \"./chat-link\";\nimport {\n ChatCodeBlock,\n ChatMention,\n StreamingCodeBlock,\n} from \"./chat-node-views\";\nimport {\n CHAT_TEXT_CLASS,\n parseChatText,\n parseTypedChatText,\n} from \"./chat-text-schema\";\nimport { renderDOMOutputSpec } from \"./dom-output-spec\";\n\nconst subscribe = () => () => {};\nconst browserSnapshot = () => true;\nconst serverSnapshot = () => false;\n\n/**\n * Render the composer's document schema through its own node/mark serializers.\n *\n * The text is split into its top-level blocks (`splitChatBlocks`), each parsed\n * and drawn by its own memoized component under one `CHAT_TEXT_CLASS`\n * wrapper. While a reply streams, only the trailing block re-parses with each\n * token; every finished block keeps its parse and its DOM. The trailing block\n * reveals its newest words with a settle tint and a caret, and a fenced code\n * block still being written renders as plain code until its fence closes.\n *\n * `typed` reads the text as a person typed it (`parseTypedChatText`: raw HTML\n * and underscore emphasis stay the characters they were typed with), for the\n * user bubble; assistant text keeps the full reading.\n */\nexport function ChatMessageBody({\n text,\n isRunning = false,\n typed = false,\n}: {\n text: string;\n isRunning?: boolean;\n typed?: boolean;\n}) {\n const hasDom = useSyncExternalStore(\n subscribe,\n browserSnapshot,\n serverSnapshot,\n );\n const onClick = useChatLinkClick();\n const { blocks, definitions } = useMemo(\n () => splitChatBlocks(text, { streaming: isRunning }),\n [text, isRunning],\n );\n return (\n // The click handler only delegates for the anchors inside, which are\n // themselves focusable and keyboard-operable; the wrapper is not a control.\n <div\n className={CHAT_TEXT_CLASS}\n data-chat-text=\"\"\n data-status={isRunning ? \"running\" : \"complete\"}\n onClick={onClick}\n >\n {hasDom\n ? blocks.map((block, index) => (\n <ChatBlock\n key={index}\n text={block.text}\n definitions={definitions}\n openFence={block.openFence}\n streaming={isRunning && index === blocks.length - 1}\n typed={typed}\n />\n ))\n : text}\n </div>\n );\n}\n\n/**\n * One top-level block. Memoized on its inputs: a finished block's `text` never\n * changes again, so React skips it on every token that lands in the block\n * after it. Only the trailing block carries `streaming` (and, while its code\n * fence is open, `openFence`).\n */\nconst ChatBlock = memo(function ChatBlock({\n text,\n definitions,\n openFence,\n streaming,\n typed,\n}: {\n text: string;\n definitions: string;\n openFence: OpenFence | null;\n streaming: boolean;\n typed: boolean;\n}) {\n // A fence is only \"open\" while the part streams. A message that ended\n // mid-fence parses as the code block it is (markdown-it closes it at the\n // end), copy action and all, with no caret.\n const open = streaming ? openFence : null;\n const doc = useMemo(() => {\n if (open) return null;\n const markdown = definitions ? `${text}\\n\\n${definitions}` : text;\n return typed ? parseTypedChatText(markdown) : parseChatText(markdown);\n }, [text, definitions, open, typed]);\n if (open) {\n return (\n <StreamingCodeBlock\n language={open.language}\n code={open.code}\n caret={<StreamingCaret />}\n />\n );\n }\n // A block of nothing but reference definitions parses to one empty\n // paragraph (the schema's default content); draw nothing for it.\n if (!doc || isEmptyDoc(doc)) return null;\n return <>{renderContent(doc, streaming ? findStreamingTail(doc) : null)}</>;\n});\n\nfunction isEmptyDoc(doc: ProseMirrorNode): boolean {\n if (doc.childCount === 0) return true;\n const only = doc.firstChild;\n return doc.childCount === 1 && only !== null && only.isTextblock && only.content.size === 0;\n}\n\n/**\n * One delegated click handler per body: a click on any rendered anchor —\n * source chip, asset chip, plain link — goes through the SDK's link-click\n * path (`linkClicks` on the provider, then the asset panel for an Athena\n * citation) and is cancelled when that path handled it; otherwise the\n * anchor's own `target=\"_blank\"` navigation runs. Only the native `click`\n * can be cancelled — a `mouseup` handler cannot stop the navigation. A\n * modified click (Cmd/Ctrl for a new tab, Shift for a new window, Alt for a\n * download) is the browser's and is never intercepted.\n */\nfunction useChatLinkClick() {\n const handleLinkClick = useAthenaLinkClickHandler();\n return useCallback(\n (event: MouseEvent<HTMLDivElement>) => {\n if (\n event.button !== 0 ||\n event.defaultPrevented ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey ||\n event.altKey\n ) {\n return;\n }\n const target = event.target;\n const element =\n target instanceof Element\n ? target\n : target instanceof Node\n ? target.parentElement\n : null;\n const anchor = element?.closest(\"a[href]\");\n if (\n !(anchor instanceof HTMLAnchorElement) ||\n !event.currentTarget.contains(anchor)\n ) {\n return;\n }\n const handled = handleLinkClick({\n href: anchor.getAttribute(\"href\") ?? anchor.href,\n // A citation chip's text ends with its place (`p.10`); the asset is named without it.\n linkText: anchor.getAttribute(\"data-mention-name\") ?? anchor.textContent,\n target: anchor.target,\n nativeEvent: event.nativeEvent,\n });\n if (handled) event.preventDefault();\n },\n [handleLinkClick],\n );\n}\n\n/** Where the block still being written ends: its last text block, and that block's last text node (none inside code). */\ninterface StreamingTail {\n textblock: ProseMirrorNode | null;\n text: ProseMirrorNode | null;\n}\n\nfunction findStreamingTail(doc: ProseMirrorNode): StreamingTail {\n const tail: StreamingTail = { textblock: null, text: null };\n doc.descendants((node) => {\n if (node.isTextblock) {\n tail.textblock = node;\n tail.text = null;\n } else if (\n node.isText &&\n tail.textblock &&\n tail.textblock.type.name !== \"codeBlock\"\n ) {\n tail.text = node;\n }\n return true;\n });\n return tail;\n}\n\n/** How many trailing words keep their own span, so a word's settle animation outlives the tokens after it. */\nconst FRESH_WORD_WINDOW = 6;\nconst WORD = /\\s*\\S+/g;\n\n/**\n * The newest words of the streaming block, each in a span keyed by its\n * position so the same element persists while the word grows and the words\n * after it arrive; a span mounts tinted and settles to ink through\n * `animate-chat-word-settle`. Words that leave the window render as text.\n */\nfunction renderFreshWords(text: string): ReactNode {\n const words = [...text.matchAll(WORD)];\n if (words.length === 0) return text;\n const from = Math.max(0, words.length - FRESH_WORD_WINDOW);\n const first = words[from]!;\n const last = words[words.length - 1]!;\n return (\n <>\n {text.slice(0, first.index)}\n {words.slice(from).map((word, offset) => (\n <span\n key={from + offset}\n className=\"animate-chat-word-settle motion-reduce:animate-none\"\n data-chat-fresh=\"\"\n >\n {word[0]}\n </span>\n ))}\n {text.slice(last.index + last[0].length)}\n </>\n );\n}\n\n/** A thin blinking caret after the newest text while the part streams; no layout of its own beyond a hairline. */\nfunction StreamingCaret() {\n return (\n <span\n aria-hidden=\"true\"\n data-chat-caret=\"\"\n className=\"ml-0.5 inline-block h-[1em] w-0.5 rounded-full bg-primary align-[-0.125em] animate-chat-caret-blink motion-reduce:animate-none\"\n />\n );\n}\n\nfunction renderNode(\n node: ProseMirrorNode,\n tail: StreamingTail | null,\n): ReactNode {\n if (node.isText) {\n return tail?.text === node ? renderFreshWords(node.text ?? \"\") : node.text;\n }\n if (node.type.name === \"mention\") return <ChatMention node={node} />;\n if (node.type.name === \"codeBlock\") {\n return (\n <ChatCodeBlock node={node}>\n <code>\n {node.textContent}\n {tail?.textblock === node ? <StreamingCaret /> : null}\n </code>\n </ChatCodeBlock>\n );\n }\n const toDOM = node.type.spec.toDOM;\n if (!toDOM) return null;\n if (node.isLeaf) return renderDOMOutputSpec(toDOM(node), null);\n const content = renderContent(node, tail);\n if (tail?.textblock === node) content.push(<StreamingCaret key=\"caret\" />);\n return renderDOMOutputSpec(toDOM(node), content);\n}\n\nfunction renderContent(\n parent: ProseMirrorNode,\n tail: StreamingTail | null,\n): ReactNode[] {\n const nodes: ProseMirrorNode[] = [];\n parent.forEach((node) => {\n nodes.push(node);\n });\n return renderMarkRuns(nodes, 0, tail);\n}\n\n/** Match ProseMirror's spanning marks: a link with bold words stays one link. */\nfunction renderMarkRuns(\n nodes: readonly ProseMirrorNode[],\n depth: number,\n tail: StreamingTail | null,\n): ReactNode[] {\n const children: ReactNode[] = [];\n let index = 0;\n while (index < nodes.length) {\n const node = nodes[index]!;\n const mark = node.marks[depth];\n if (!mark) {\n children.push(<Fragment key={index}>{renderNode(node, tail)}</Fragment>);\n index++;\n continue;\n }\n let end = index + 1;\n if (mark.type.spec.spanning !== false) {\n while (end < nodes.length && nodes[end]?.marks[depth]?.eq(mark)) end++;\n }\n const content = renderMarkRuns(nodes.slice(index, end), depth + 1, tail);\n const rendered =\n mark.type.name === \"link\" ? (\n <ChatLink href={mark.attrs.href} excerpt={mark.attrs.excerpt}>\n {content}\n </ChatLink>\n ) : mark.type.spec.toDOM ? (\n renderDOMOutputSpec(mark.type.spec.toDOM(mark, true), content)\n ) : (\n content\n );\n children.push(<Fragment key={index}>{rendered}</Fragment>);\n index = end;\n }\n return children;\n}\n"],"names":["useSyncExternalStore","useMemo","splitChatBlocks","jsx","CHAT_TEXT_CLASS","memo","ChatBlock","parseTypedChatText","parseChatText","StreamingCodeBlock","Fragment","useAthenaLinkClickHandler","useCallback","jsxs","ChatMention","ChatCodeBlock","renderDOMOutputSpec","ChatLink"],"mappings":"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"}
1
+ {"version":3,"file":"chat-message-body.cjs","sources":["../../../src/chat-ui/rich-text/chat-message-body.tsx"],"sourcesContent":["import type { Node as ProseMirrorNode } from \"@tiptap/pm/model\";\nimport {\n Fragment,\n memo,\n type MouseEvent,\n type ReactNode,\n useCallback,\n useMemo,\n useSyncExternalStore,\n} from \"react\";\nimport { useAthenaLinkClickHandler } from \"../../chat/athena-citation-links\";\nimport { type OpenFence, splitChatBlocks } from \"./chat-blocks\";\nimport { ChatLink } from \"./chat-link\";\nimport {\n ChatCodeBlock,\n ChatMention,\n StreamingCodeBlock,\n} from \"./chat-node-views\";\nimport {\n CHAT_TEXT_CLASS,\n parseChatText,\n parseTypedChatText,\n} from \"./chat-text-schema\";\nimport { renderDOMOutputSpec } from \"./dom-output-spec\";\n\nconst subscribe = () => () => {};\nconst browserSnapshot = () => true;\nconst serverSnapshot = () => false;\n\n/**\n * Render the composer's document schema through its own node/mark serializers.\n *\n * The text is split into its top-level blocks (`splitChatBlocks`), each parsed\n * and drawn by its own memoized component under one `CHAT_TEXT_CLASS`\n * wrapper. While a reply streams, only the trailing block re-parses with each\n * token; every finished block keeps its parse and its DOM. The trailing block\n * reveals its newest words with a settle tint and a caret, and a fenced code\n * block still being written renders as plain code until its fence closes.\n *\n * `typed` reads the text as a person typed it (`parseTypedChatText`: raw HTML\n * and underscore emphasis stay the characters they were typed with), for the\n * user bubble; assistant text keeps the full reading.\n */\nexport function ChatMessageBody({\n text,\n isRunning = false,\n typed = false,\n}: {\n text: string;\n isRunning?: boolean;\n typed?: boolean;\n}) {\n const hasDom = useSyncExternalStore(\n subscribe,\n browserSnapshot,\n serverSnapshot,\n );\n const onClick = useChatLinkClick();\n const { blocks, definitions } = useMemo(\n () => splitChatBlocks(text, { streaming: isRunning }),\n [text, isRunning],\n );\n return (\n // The click handler only delegates for the anchors inside, which are\n // themselves focusable and keyboard-operable; the wrapper is not a control.\n <div\n className={CHAT_TEXT_CLASS}\n data-chat-text=\"\"\n data-status={isRunning ? \"running\" : \"complete\"}\n onClick={onClick}\n >\n {hasDom\n ? blocks.map((block, index) => (\n <ChatBlock\n key={index}\n text={block.text}\n definitions={definitions}\n openFence={block.openFence}\n streaming={isRunning && index === blocks.length - 1}\n typed={typed}\n />\n ))\n : text}\n </div>\n );\n}\n\n/**\n * One top-level block. Memoized on its inputs: a finished block's `text` never\n * changes again, so React skips it on every token that lands in the block\n * after it. Only the trailing block carries `streaming` (and, while its code\n * fence is open, `openFence`).\n */\nconst ChatBlock = memo(function ChatBlock({\n text,\n definitions,\n openFence,\n streaming,\n typed,\n}: {\n text: string;\n definitions: string;\n openFence: OpenFence | null;\n streaming: boolean;\n typed: boolean;\n}) {\n // A fence is only \"open\" while the part streams. A message that ended\n // mid-fence parses as the code block it is (markdown-it closes it at the\n // end), copy action and all, with no caret.\n const open = streaming ? openFence : null;\n const doc = useMemo(() => {\n if (open) return null;\n const markdown = definitions ? `${text}\\n\\n${definitions}` : text;\n return typed ? parseTypedChatText(markdown) : parseChatText(markdown);\n }, [text, definitions, open, typed]);\n if (open) {\n return (\n <StreamingCodeBlock\n language={open.language}\n code={open.code}\n caret={<StreamingCaret />}\n />\n );\n }\n // A block of nothing but reference definitions parses to one empty\n // paragraph (the schema's default content); draw nothing for it.\n if (!doc || isEmptyDoc(doc)) return null;\n return <>{renderContent(doc, streaming ? findStreamingTail(doc) : null)}</>;\n});\n\nfunction isEmptyDoc(doc: ProseMirrorNode): boolean {\n if (doc.childCount === 0) return true;\n const only = doc.firstChild;\n return doc.childCount === 1 && only !== null && only.isTextblock && only.content.size === 0;\n}\n\n/**\n * One delegated click handler per body: a click on any rendered anchor —\n * source chip, asset chip, plain link — goes through the SDK's link-click\n * path (`linkClicks` on the provider, then the asset panel for an Athena\n * citation) and is cancelled when that path handled it; otherwise the\n * anchor's own `target=\"_blank\"` navigation runs. Only the native `click`\n * can be cancelled — a `mouseup` handler cannot stop the navigation. A\n * modified click (Cmd/Ctrl for a new tab, Shift for a new window, Alt for a\n * download) is the browser's and is never intercepted.\n */\nexport function useChatLinkClick() {\n const handleLinkClick = useAthenaLinkClickHandler();\n return useCallback(\n (event: MouseEvent<HTMLDivElement>) => {\n if (\n event.button !== 0 ||\n event.defaultPrevented ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey ||\n event.altKey\n ) {\n return;\n }\n const target = event.target;\n const element =\n target instanceof Element\n ? target\n : target instanceof Node\n ? target.parentElement\n : null;\n const anchor = element?.closest(\"a[href]\");\n if (\n !(anchor instanceof HTMLAnchorElement) ||\n !event.currentTarget.contains(anchor)\n ) {\n return;\n }\n const handled = handleLinkClick({\n href: anchor.getAttribute(\"href\") ?? anchor.href,\n // A citation chip's text ends with its place (`p.10`); the asset is named without it.\n linkText: anchor.getAttribute(\"data-mention-name\") ?? anchor.textContent,\n target: anchor.target,\n nativeEvent: event.nativeEvent,\n });\n if (handled) event.preventDefault();\n },\n [handleLinkClick],\n );\n}\n\n/** Where the block still being written ends: its last text block, and that block's last text node (none inside code). */\ninterface StreamingTail {\n textblock: ProseMirrorNode | null;\n text: ProseMirrorNode | null;\n}\n\nfunction findStreamingTail(doc: ProseMirrorNode): StreamingTail {\n const tail: StreamingTail = { textblock: null, text: null };\n doc.descendants((node) => {\n if (node.isTextblock) {\n tail.textblock = node;\n tail.text = null;\n } else if (\n node.isText &&\n tail.textblock &&\n tail.textblock.type.name !== \"codeBlock\"\n ) {\n tail.text = node;\n }\n return true;\n });\n return tail;\n}\n\n/** How many trailing words keep their own span, so a word's settle animation outlives the tokens after it. */\nconst FRESH_WORD_WINDOW = 6;\nconst WORD = /\\s*\\S+/g;\n\n/**\n * The newest words of the streaming block, each in a span keyed by its\n * position so the same element persists while the word grows and the words\n * after it arrive; a span mounts tinted and settles to ink through\n * `animate-chat-word-settle`. Words that leave the window render as text.\n */\nfunction renderFreshWords(text: string): ReactNode {\n const words = [...text.matchAll(WORD)];\n if (words.length === 0) return text;\n const from = Math.max(0, words.length - FRESH_WORD_WINDOW);\n const first = words[from]!;\n const last = words[words.length - 1]!;\n return (\n <>\n {text.slice(0, first.index)}\n {words.slice(from).map((word, offset) => (\n <span\n key={from + offset}\n className=\"animate-chat-word-settle motion-reduce:animate-none\"\n data-chat-fresh=\"\"\n >\n {word[0]}\n </span>\n ))}\n {text.slice(last.index + last[0].length)}\n </>\n );\n}\n\n/** A thin blinking caret after the newest text while the part streams; no layout of its own beyond a hairline. */\nfunction StreamingCaret() {\n return (\n <span\n aria-hidden=\"true\"\n data-chat-caret=\"\"\n className=\"ml-0.5 inline-block h-[1em] w-0.5 rounded-full bg-primary align-[-0.125em] animate-chat-caret-blink motion-reduce:animate-none\"\n />\n );\n}\n\nfunction renderNode(\n node: ProseMirrorNode,\n tail: StreamingTail | null,\n): ReactNode {\n if (node.isText) {\n return tail?.text === node ? renderFreshWords(node.text ?? \"\") : node.text;\n }\n if (node.type.name === \"mention\") return <ChatMention node={node} />;\n if (node.type.name === \"codeBlock\") {\n return (\n <ChatCodeBlock node={node}>\n <code>\n {node.textContent}\n {tail?.textblock === node ? <StreamingCaret /> : null}\n </code>\n </ChatCodeBlock>\n );\n }\n const toDOM = node.type.spec.toDOM;\n if (!toDOM) return null;\n if (node.isLeaf) return renderDOMOutputSpec(toDOM(node), null);\n const content = renderContent(node, tail);\n if (tail?.textblock === node) content.push(<StreamingCaret key=\"caret\" />);\n return renderDOMOutputSpec(toDOM(node), content);\n}\n\nfunction renderContent(\n parent: ProseMirrorNode,\n tail: StreamingTail | null,\n): ReactNode[] {\n const nodes: ProseMirrorNode[] = [];\n parent.forEach((node) => {\n nodes.push(node);\n });\n return renderMarkRuns(nodes, 0, tail);\n}\n\n/** Match ProseMirror's spanning marks: a link with bold words stays one link. */\nfunction renderMarkRuns(\n nodes: readonly ProseMirrorNode[],\n depth: number,\n tail: StreamingTail | null,\n): ReactNode[] {\n const children: ReactNode[] = [];\n let index = 0;\n while (index < nodes.length) {\n const node = nodes[index]!;\n const mark = node.marks[depth];\n if (!mark) {\n children.push(<Fragment key={index}>{renderNode(node, tail)}</Fragment>);\n index++;\n continue;\n }\n let end = index + 1;\n if (mark.type.spec.spanning !== false) {\n while (end < nodes.length && nodes[end]?.marks[depth]?.eq(mark)) end++;\n }\n const content = renderMarkRuns(nodes.slice(index, end), depth + 1, tail);\n const rendered =\n mark.type.name === \"link\" ? (\n <ChatLink href={mark.attrs.href} excerpt={mark.attrs.excerpt}>\n {content}\n </ChatLink>\n ) : mark.type.spec.toDOM ? (\n renderDOMOutputSpec(mark.type.spec.toDOM(mark, true), content)\n ) : (\n content\n );\n children.push(<Fragment key={index}>{rendered}</Fragment>);\n index = end;\n }\n return children;\n}\n"],"names":["useSyncExternalStore","useMemo","splitChatBlocks","jsx","CHAT_TEXT_CLASS","memo","ChatBlock","parseTypedChatText","parseChatText","StreamingCodeBlock","Fragment","useAthenaLinkClickHandler","useCallback","jsxs","ChatMention","ChatCodeBlock","renderDOMOutputSpec","ChatLink"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { type MouseEvent } from "react";
1
2
  /**
2
3
  * Render the composer's document schema through its own node/mark serializers.
3
4
  *
@@ -17,3 +18,14 @@ export declare function ChatMessageBody({ text, isRunning, typed, }: {
17
18
  isRunning?: boolean;
18
19
  typed?: boolean;
19
20
  }): import("react").JSX.Element;
21
+ /**
22
+ * One delegated click handler per body: a click on any rendered anchor —
23
+ * source chip, asset chip, plain link — goes through the SDK's link-click
24
+ * path (`linkClicks` on the provider, then the asset panel for an Athena
25
+ * citation) and is cancelled when that path handled it; otherwise the
26
+ * anchor's own `target="_blank"` navigation runs. Only the native `click`
27
+ * can be cancelled — a `mouseup` handler cannot stop the navigation. A
28
+ * modified click (Cmd/Ctrl for a new tab, Shift for a new window, Alt for a
29
+ * download) is the browser's and is never intercepted.
30
+ */
31
+ export declare function useChatLinkClick(): (event: MouseEvent<HTMLDivElement>) => void;
@@ -202,6 +202,7 @@ function renderMarkRuns(nodes, depth, tail) {
202
202
  return children;
203
203
  }
204
204
  export {
205
- ChatMessageBody
205
+ ChatMessageBody,
206
+ useChatLinkClick
206
207
  };
207
208
  //# sourceMappingURL=chat-message-body.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chat-message-body.js","sources":["../../../src/chat-ui/rich-text/chat-message-body.tsx"],"sourcesContent":["import type { Node as ProseMirrorNode } from \"@tiptap/pm/model\";\nimport {\n Fragment,\n memo,\n type MouseEvent,\n type ReactNode,\n useCallback,\n useMemo,\n useSyncExternalStore,\n} from \"react\";\nimport { useAthenaLinkClickHandler } from \"../../chat/athena-citation-links\";\nimport { type OpenFence, splitChatBlocks } from \"./chat-blocks\";\nimport { ChatLink } from \"./chat-link\";\nimport {\n ChatCodeBlock,\n ChatMention,\n StreamingCodeBlock,\n} from \"./chat-node-views\";\nimport {\n CHAT_TEXT_CLASS,\n parseChatText,\n parseTypedChatText,\n} from \"./chat-text-schema\";\nimport { renderDOMOutputSpec } from \"./dom-output-spec\";\n\nconst subscribe = () => () => {};\nconst browserSnapshot = () => true;\nconst serverSnapshot = () => false;\n\n/**\n * Render the composer's document schema through its own node/mark serializers.\n *\n * The text is split into its top-level blocks (`splitChatBlocks`), each parsed\n * and drawn by its own memoized component under one `CHAT_TEXT_CLASS`\n * wrapper. While a reply streams, only the trailing block re-parses with each\n * token; every finished block keeps its parse and its DOM. The trailing block\n * reveals its newest words with a settle tint and a caret, and a fenced code\n * block still being written renders as plain code until its fence closes.\n *\n * `typed` reads the text as a person typed it (`parseTypedChatText`: raw HTML\n * and underscore emphasis stay the characters they were typed with), for the\n * user bubble; assistant text keeps the full reading.\n */\nexport function ChatMessageBody({\n text,\n isRunning = false,\n typed = false,\n}: {\n text: string;\n isRunning?: boolean;\n typed?: boolean;\n}) {\n const hasDom = useSyncExternalStore(\n subscribe,\n browserSnapshot,\n serverSnapshot,\n );\n const onClick = useChatLinkClick();\n const { blocks, definitions } = useMemo(\n () => splitChatBlocks(text, { streaming: isRunning }),\n [text, isRunning],\n );\n return (\n // The click handler only delegates for the anchors inside, which are\n // themselves focusable and keyboard-operable; the wrapper is not a control.\n <div\n className={CHAT_TEXT_CLASS}\n data-chat-text=\"\"\n data-status={isRunning ? \"running\" : \"complete\"}\n onClick={onClick}\n >\n {hasDom\n ? blocks.map((block, index) => (\n <ChatBlock\n key={index}\n text={block.text}\n definitions={definitions}\n openFence={block.openFence}\n streaming={isRunning && index === blocks.length - 1}\n typed={typed}\n />\n ))\n : text}\n </div>\n );\n}\n\n/**\n * One top-level block. Memoized on its inputs: a finished block's `text` never\n * changes again, so React skips it on every token that lands in the block\n * after it. Only the trailing block carries `streaming` (and, while its code\n * fence is open, `openFence`).\n */\nconst ChatBlock = memo(function ChatBlock({\n text,\n definitions,\n openFence,\n streaming,\n typed,\n}: {\n text: string;\n definitions: string;\n openFence: OpenFence | null;\n streaming: boolean;\n typed: boolean;\n}) {\n // A fence is only \"open\" while the part streams. A message that ended\n // mid-fence parses as the code block it is (markdown-it closes it at the\n // end), copy action and all, with no caret.\n const open = streaming ? openFence : null;\n const doc = useMemo(() => {\n if (open) return null;\n const markdown = definitions ? `${text}\\n\\n${definitions}` : text;\n return typed ? parseTypedChatText(markdown) : parseChatText(markdown);\n }, [text, definitions, open, typed]);\n if (open) {\n return (\n <StreamingCodeBlock\n language={open.language}\n code={open.code}\n caret={<StreamingCaret />}\n />\n );\n }\n // A block of nothing but reference definitions parses to one empty\n // paragraph (the schema's default content); draw nothing for it.\n if (!doc || isEmptyDoc(doc)) return null;\n return <>{renderContent(doc, streaming ? findStreamingTail(doc) : null)}</>;\n});\n\nfunction isEmptyDoc(doc: ProseMirrorNode): boolean {\n if (doc.childCount === 0) return true;\n const only = doc.firstChild;\n return doc.childCount === 1 && only !== null && only.isTextblock && only.content.size === 0;\n}\n\n/**\n * One delegated click handler per body: a click on any rendered anchor —\n * source chip, asset chip, plain link — goes through the SDK's link-click\n * path (`linkClicks` on the provider, then the asset panel for an Athena\n * citation) and is cancelled when that path handled it; otherwise the\n * anchor's own `target=\"_blank\"` navigation runs. Only the native `click`\n * can be cancelled — a `mouseup` handler cannot stop the navigation. A\n * modified click (Cmd/Ctrl for a new tab, Shift for a new window, Alt for a\n * download) is the browser's and is never intercepted.\n */\nfunction useChatLinkClick() {\n const handleLinkClick = useAthenaLinkClickHandler();\n return useCallback(\n (event: MouseEvent<HTMLDivElement>) => {\n if (\n event.button !== 0 ||\n event.defaultPrevented ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey ||\n event.altKey\n ) {\n return;\n }\n const target = event.target;\n const element =\n target instanceof Element\n ? target\n : target instanceof Node\n ? target.parentElement\n : null;\n const anchor = element?.closest(\"a[href]\");\n if (\n !(anchor instanceof HTMLAnchorElement) ||\n !event.currentTarget.contains(anchor)\n ) {\n return;\n }\n const handled = handleLinkClick({\n href: anchor.getAttribute(\"href\") ?? anchor.href,\n // A citation chip's text ends with its place (`p.10`); the asset is named without it.\n linkText: anchor.getAttribute(\"data-mention-name\") ?? anchor.textContent,\n target: anchor.target,\n nativeEvent: event.nativeEvent,\n });\n if (handled) event.preventDefault();\n },\n [handleLinkClick],\n );\n}\n\n/** Where the block still being written ends: its last text block, and that block's last text node (none inside code). */\ninterface StreamingTail {\n textblock: ProseMirrorNode | null;\n text: ProseMirrorNode | null;\n}\n\nfunction findStreamingTail(doc: ProseMirrorNode): StreamingTail {\n const tail: StreamingTail = { textblock: null, text: null };\n doc.descendants((node) => {\n if (node.isTextblock) {\n tail.textblock = node;\n tail.text = null;\n } else if (\n node.isText &&\n tail.textblock &&\n tail.textblock.type.name !== \"codeBlock\"\n ) {\n tail.text = node;\n }\n return true;\n });\n return tail;\n}\n\n/** How many trailing words keep their own span, so a word's settle animation outlives the tokens after it. */\nconst FRESH_WORD_WINDOW = 6;\nconst WORD = /\\s*\\S+/g;\n\n/**\n * The newest words of the streaming block, each in a span keyed by its\n * position so the same element persists while the word grows and the words\n * after it arrive; a span mounts tinted and settles to ink through\n * `animate-chat-word-settle`. Words that leave the window render as text.\n */\nfunction renderFreshWords(text: string): ReactNode {\n const words = [...text.matchAll(WORD)];\n if (words.length === 0) return text;\n const from = Math.max(0, words.length - FRESH_WORD_WINDOW);\n const first = words[from]!;\n const last = words[words.length - 1]!;\n return (\n <>\n {text.slice(0, first.index)}\n {words.slice(from).map((word, offset) => (\n <span\n key={from + offset}\n className=\"animate-chat-word-settle motion-reduce:animate-none\"\n data-chat-fresh=\"\"\n >\n {word[0]}\n </span>\n ))}\n {text.slice(last.index + last[0].length)}\n </>\n );\n}\n\n/** A thin blinking caret after the newest text while the part streams; no layout of its own beyond a hairline. */\nfunction StreamingCaret() {\n return (\n <span\n aria-hidden=\"true\"\n data-chat-caret=\"\"\n className=\"ml-0.5 inline-block h-[1em] w-0.5 rounded-full bg-primary align-[-0.125em] animate-chat-caret-blink motion-reduce:animate-none\"\n />\n );\n}\n\nfunction renderNode(\n node: ProseMirrorNode,\n tail: StreamingTail | null,\n): ReactNode {\n if (node.isText) {\n return tail?.text === node ? renderFreshWords(node.text ?? \"\") : node.text;\n }\n if (node.type.name === \"mention\") return <ChatMention node={node} />;\n if (node.type.name === \"codeBlock\") {\n return (\n <ChatCodeBlock node={node}>\n <code>\n {node.textContent}\n {tail?.textblock === node ? <StreamingCaret /> : null}\n </code>\n </ChatCodeBlock>\n );\n }\n const toDOM = node.type.spec.toDOM;\n if (!toDOM) return null;\n if (node.isLeaf) return renderDOMOutputSpec(toDOM(node), null);\n const content = renderContent(node, tail);\n if (tail?.textblock === node) content.push(<StreamingCaret key=\"caret\" />);\n return renderDOMOutputSpec(toDOM(node), content);\n}\n\nfunction renderContent(\n parent: ProseMirrorNode,\n tail: StreamingTail | null,\n): ReactNode[] {\n const nodes: ProseMirrorNode[] = [];\n parent.forEach((node) => {\n nodes.push(node);\n });\n return renderMarkRuns(nodes, 0, tail);\n}\n\n/** Match ProseMirror's spanning marks: a link with bold words stays one link. */\nfunction renderMarkRuns(\n nodes: readonly ProseMirrorNode[],\n depth: number,\n tail: StreamingTail | null,\n): ReactNode[] {\n const children: ReactNode[] = [];\n let index = 0;\n while (index < nodes.length) {\n const node = nodes[index]!;\n const mark = node.marks[depth];\n if (!mark) {\n children.push(<Fragment key={index}>{renderNode(node, tail)}</Fragment>);\n index++;\n continue;\n }\n let end = index + 1;\n if (mark.type.spec.spanning !== false) {\n while (end < nodes.length && nodes[end]?.marks[depth]?.eq(mark)) end++;\n }\n const content = renderMarkRuns(nodes.slice(index, end), depth + 1, tail);\n const rendered =\n mark.type.name === \"link\" ? (\n <ChatLink href={mark.attrs.href} excerpt={mark.attrs.excerpt}>\n {content}\n </ChatLink>\n ) : mark.type.spec.toDOM ? (\n renderDOMOutputSpec(mark.type.spec.toDOM(mark, true), content)\n ) : (\n content\n );\n children.push(<Fragment key={index}>{rendered}</Fragment>);\n index = end;\n }\n return children;\n}\n"],"names":["ChatBlock","Fragment"],"mappings":"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"}
1
+ {"version":3,"file":"chat-message-body.js","sources":["../../../src/chat-ui/rich-text/chat-message-body.tsx"],"sourcesContent":["import type { Node as ProseMirrorNode } from \"@tiptap/pm/model\";\nimport {\n Fragment,\n memo,\n type MouseEvent,\n type ReactNode,\n useCallback,\n useMemo,\n useSyncExternalStore,\n} from \"react\";\nimport { useAthenaLinkClickHandler } from \"../../chat/athena-citation-links\";\nimport { type OpenFence, splitChatBlocks } from \"./chat-blocks\";\nimport { ChatLink } from \"./chat-link\";\nimport {\n ChatCodeBlock,\n ChatMention,\n StreamingCodeBlock,\n} from \"./chat-node-views\";\nimport {\n CHAT_TEXT_CLASS,\n parseChatText,\n parseTypedChatText,\n} from \"./chat-text-schema\";\nimport { renderDOMOutputSpec } from \"./dom-output-spec\";\n\nconst subscribe = () => () => {};\nconst browserSnapshot = () => true;\nconst serverSnapshot = () => false;\n\n/**\n * Render the composer's document schema through its own node/mark serializers.\n *\n * The text is split into its top-level blocks (`splitChatBlocks`), each parsed\n * and drawn by its own memoized component under one `CHAT_TEXT_CLASS`\n * wrapper. While a reply streams, only the trailing block re-parses with each\n * token; every finished block keeps its parse and its DOM. The trailing block\n * reveals its newest words with a settle tint and a caret, and a fenced code\n * block still being written renders as plain code until its fence closes.\n *\n * `typed` reads the text as a person typed it (`parseTypedChatText`: raw HTML\n * and underscore emphasis stay the characters they were typed with), for the\n * user bubble; assistant text keeps the full reading.\n */\nexport function ChatMessageBody({\n text,\n isRunning = false,\n typed = false,\n}: {\n text: string;\n isRunning?: boolean;\n typed?: boolean;\n}) {\n const hasDom = useSyncExternalStore(\n subscribe,\n browserSnapshot,\n serverSnapshot,\n );\n const onClick = useChatLinkClick();\n const { blocks, definitions } = useMemo(\n () => splitChatBlocks(text, { streaming: isRunning }),\n [text, isRunning],\n );\n return (\n // The click handler only delegates for the anchors inside, which are\n // themselves focusable and keyboard-operable; the wrapper is not a control.\n <div\n className={CHAT_TEXT_CLASS}\n data-chat-text=\"\"\n data-status={isRunning ? \"running\" : \"complete\"}\n onClick={onClick}\n >\n {hasDom\n ? blocks.map((block, index) => (\n <ChatBlock\n key={index}\n text={block.text}\n definitions={definitions}\n openFence={block.openFence}\n streaming={isRunning && index === blocks.length - 1}\n typed={typed}\n />\n ))\n : text}\n </div>\n );\n}\n\n/**\n * One top-level block. Memoized on its inputs: a finished block's `text` never\n * changes again, so React skips it on every token that lands in the block\n * after it. Only the trailing block carries `streaming` (and, while its code\n * fence is open, `openFence`).\n */\nconst ChatBlock = memo(function ChatBlock({\n text,\n definitions,\n openFence,\n streaming,\n typed,\n}: {\n text: string;\n definitions: string;\n openFence: OpenFence | null;\n streaming: boolean;\n typed: boolean;\n}) {\n // A fence is only \"open\" while the part streams. A message that ended\n // mid-fence parses as the code block it is (markdown-it closes it at the\n // end), copy action and all, with no caret.\n const open = streaming ? openFence : null;\n const doc = useMemo(() => {\n if (open) return null;\n const markdown = definitions ? `${text}\\n\\n${definitions}` : text;\n return typed ? parseTypedChatText(markdown) : parseChatText(markdown);\n }, [text, definitions, open, typed]);\n if (open) {\n return (\n <StreamingCodeBlock\n language={open.language}\n code={open.code}\n caret={<StreamingCaret />}\n />\n );\n }\n // A block of nothing but reference definitions parses to one empty\n // paragraph (the schema's default content); draw nothing for it.\n if (!doc || isEmptyDoc(doc)) return null;\n return <>{renderContent(doc, streaming ? findStreamingTail(doc) : null)}</>;\n});\n\nfunction isEmptyDoc(doc: ProseMirrorNode): boolean {\n if (doc.childCount === 0) return true;\n const only = doc.firstChild;\n return doc.childCount === 1 && only !== null && only.isTextblock && only.content.size === 0;\n}\n\n/**\n * One delegated click handler per body: a click on any rendered anchor —\n * source chip, asset chip, plain link — goes through the SDK's link-click\n * path (`linkClicks` on the provider, then the asset panel for an Athena\n * citation) and is cancelled when that path handled it; otherwise the\n * anchor's own `target=\"_blank\"` navigation runs. Only the native `click`\n * can be cancelled — a `mouseup` handler cannot stop the navigation. A\n * modified click (Cmd/Ctrl for a new tab, Shift for a new window, Alt for a\n * download) is the browser's and is never intercepted.\n */\nexport function useChatLinkClick() {\n const handleLinkClick = useAthenaLinkClickHandler();\n return useCallback(\n (event: MouseEvent<HTMLDivElement>) => {\n if (\n event.button !== 0 ||\n event.defaultPrevented ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey ||\n event.altKey\n ) {\n return;\n }\n const target = event.target;\n const element =\n target instanceof Element\n ? target\n : target instanceof Node\n ? target.parentElement\n : null;\n const anchor = element?.closest(\"a[href]\");\n if (\n !(anchor instanceof HTMLAnchorElement) ||\n !event.currentTarget.contains(anchor)\n ) {\n return;\n }\n const handled = handleLinkClick({\n href: anchor.getAttribute(\"href\") ?? anchor.href,\n // A citation chip's text ends with its place (`p.10`); the asset is named without it.\n linkText: anchor.getAttribute(\"data-mention-name\") ?? anchor.textContent,\n target: anchor.target,\n nativeEvent: event.nativeEvent,\n });\n if (handled) event.preventDefault();\n },\n [handleLinkClick],\n );\n}\n\n/** Where the block still being written ends: its last text block, and that block's last text node (none inside code). */\ninterface StreamingTail {\n textblock: ProseMirrorNode | null;\n text: ProseMirrorNode | null;\n}\n\nfunction findStreamingTail(doc: ProseMirrorNode): StreamingTail {\n const tail: StreamingTail = { textblock: null, text: null };\n doc.descendants((node) => {\n if (node.isTextblock) {\n tail.textblock = node;\n tail.text = null;\n } else if (\n node.isText &&\n tail.textblock &&\n tail.textblock.type.name !== \"codeBlock\"\n ) {\n tail.text = node;\n }\n return true;\n });\n return tail;\n}\n\n/** How many trailing words keep their own span, so a word's settle animation outlives the tokens after it. */\nconst FRESH_WORD_WINDOW = 6;\nconst WORD = /\\s*\\S+/g;\n\n/**\n * The newest words of the streaming block, each in a span keyed by its\n * position so the same element persists while the word grows and the words\n * after it arrive; a span mounts tinted and settles to ink through\n * `animate-chat-word-settle`. Words that leave the window render as text.\n */\nfunction renderFreshWords(text: string): ReactNode {\n const words = [...text.matchAll(WORD)];\n if (words.length === 0) return text;\n const from = Math.max(0, words.length - FRESH_WORD_WINDOW);\n const first = words[from]!;\n const last = words[words.length - 1]!;\n return (\n <>\n {text.slice(0, first.index)}\n {words.slice(from).map((word, offset) => (\n <span\n key={from + offset}\n className=\"animate-chat-word-settle motion-reduce:animate-none\"\n data-chat-fresh=\"\"\n >\n {word[0]}\n </span>\n ))}\n {text.slice(last.index + last[0].length)}\n </>\n );\n}\n\n/** A thin blinking caret after the newest text while the part streams; no layout of its own beyond a hairline. */\nfunction StreamingCaret() {\n return (\n <span\n aria-hidden=\"true\"\n data-chat-caret=\"\"\n className=\"ml-0.5 inline-block h-[1em] w-0.5 rounded-full bg-primary align-[-0.125em] animate-chat-caret-blink motion-reduce:animate-none\"\n />\n );\n}\n\nfunction renderNode(\n node: ProseMirrorNode,\n tail: StreamingTail | null,\n): ReactNode {\n if (node.isText) {\n return tail?.text === node ? renderFreshWords(node.text ?? \"\") : node.text;\n }\n if (node.type.name === \"mention\") return <ChatMention node={node} />;\n if (node.type.name === \"codeBlock\") {\n return (\n <ChatCodeBlock node={node}>\n <code>\n {node.textContent}\n {tail?.textblock === node ? <StreamingCaret /> : null}\n </code>\n </ChatCodeBlock>\n );\n }\n const toDOM = node.type.spec.toDOM;\n if (!toDOM) return null;\n if (node.isLeaf) return renderDOMOutputSpec(toDOM(node), null);\n const content = renderContent(node, tail);\n if (tail?.textblock === node) content.push(<StreamingCaret key=\"caret\" />);\n return renderDOMOutputSpec(toDOM(node), content);\n}\n\nfunction renderContent(\n parent: ProseMirrorNode,\n tail: StreamingTail | null,\n): ReactNode[] {\n const nodes: ProseMirrorNode[] = [];\n parent.forEach((node) => {\n nodes.push(node);\n });\n return renderMarkRuns(nodes, 0, tail);\n}\n\n/** Match ProseMirror's spanning marks: a link with bold words stays one link. */\nfunction renderMarkRuns(\n nodes: readonly ProseMirrorNode[],\n depth: number,\n tail: StreamingTail | null,\n): ReactNode[] {\n const children: ReactNode[] = [];\n let index = 0;\n while (index < nodes.length) {\n const node = nodes[index]!;\n const mark = node.marks[depth];\n if (!mark) {\n children.push(<Fragment key={index}>{renderNode(node, tail)}</Fragment>);\n index++;\n continue;\n }\n let end = index + 1;\n if (mark.type.spec.spanning !== false) {\n while (end < nodes.length && nodes[end]?.marks[depth]?.eq(mark)) end++;\n }\n const content = renderMarkRuns(nodes.slice(index, end), depth + 1, tail);\n const rendered =\n mark.type.name === \"link\" ? (\n <ChatLink href={mark.attrs.href} excerpt={mark.attrs.excerpt}>\n {content}\n </ChatLink>\n ) : mark.type.spec.toDOM ? (\n renderDOMOutputSpec(mark.type.spec.toDOM(mark, true), content)\n ) : (\n content\n );\n children.push(<Fragment key={index}>{rendered}</Fragment>);\n index = end;\n }\n return children;\n}\n"],"names":["ChatBlock","Fragment"],"mappings":"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"}