@athenaintel/react 0.22.4 → 0.23.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 (350) hide show
  1. package/README.md +394 -4
  2. package/dist/chat/AthenaChat.cjs +346 -244
  3. package/dist/chat/AthenaChat.cjs.map +1 -1
  4. package/dist/chat/AthenaChat.d.ts +19 -8
  5. package/dist/chat/AthenaChat.js +350 -248
  6. package/dist/chat/AthenaChat.js.map +1 -1
  7. package/dist/chat/StatewireApprovalCard.cjs +2 -1
  8. package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
  9. package/dist/chat/StatewireApprovalCard.js +2 -1
  10. package/dist/chat/StatewireApprovalCard.js.map +1 -1
  11. package/dist/chat/StatewireClientToolBridge.cjs +1 -0
  12. package/dist/chat/StatewireClientToolBridge.cjs.map +1 -1
  13. package/dist/chat/StatewireClientToolBridge.js +1 -0
  14. package/dist/chat/StatewireClientToolBridge.js.map +1 -1
  15. package/dist/chat/StatewireQueuedMessages.cjs +1 -0
  16. package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
  17. package/dist/chat/StatewireQueuedMessages.js +1 -0
  18. package/dist/chat/StatewireQueuedMessages.js.map +1 -1
  19. package/dist/chat/WaitingOnYouBanner.cjs +100 -0
  20. package/dist/chat/WaitingOnYouBanner.cjs.map +1 -0
  21. package/dist/chat/WaitingOnYouBanner.d.ts +29 -0
  22. package/dist/chat/WaitingOnYouBanner.js +100 -0
  23. package/dist/chat/WaitingOnYouBanner.js.map +1 -0
  24. package/dist/chat/athena-chat-welcome.cjs +148 -0
  25. package/dist/chat/athena-chat-welcome.cjs.map +1 -0
  26. package/dist/chat/athena-chat-welcome.d.ts +54 -0
  27. package/dist/chat/athena-chat-welcome.js +148 -0
  28. package/dist/chat/athena-chat-welcome.js.map +1 -0
  29. package/dist/chat/athena-composer-controls.cjs +76 -0
  30. package/dist/chat/athena-composer-controls.cjs.map +1 -0
  31. package/dist/chat/athena-composer-controls.d.ts +21 -0
  32. package/dist/chat/athena-composer-controls.js +76 -0
  33. package/dist/chat/athena-composer-controls.js.map +1 -0
  34. package/dist/chat/composer-keyboard-hint.cjs +61 -0
  35. package/dist/chat/composer-keyboard-hint.cjs.map +1 -0
  36. package/dist/chat/composer-keyboard-hint.d.ts +33 -0
  37. package/dist/chat/composer-keyboard-hint.js +61 -0
  38. package/dist/chat/composer-keyboard-hint.js.map +1 -0
  39. package/dist/chat/message-time.cjs +64 -0
  40. package/dist/chat/message-time.cjs.map +1 -0
  41. package/dist/chat/message-time.d.ts +45 -0
  42. package/dist/chat/message-time.js +64 -0
  43. package/dist/chat/message-time.js.map +1 -0
  44. package/dist/chat/run-activity.cjs +38 -7
  45. package/dist/chat/run-activity.cjs.map +1 -1
  46. package/dist/chat/run-activity.d.ts +14 -3
  47. package/dist/chat/run-activity.js +39 -8
  48. package/dist/chat/run-activity.js.map +1 -1
  49. package/dist/chat/statewire-client-tools.cjs +2 -1
  50. package/dist/chat/statewire-client-tools.cjs.map +1 -1
  51. package/dist/chat/statewire-client-tools.js +2 -1
  52. package/dist/chat/statewire-client-tools.js.map +1 -1
  53. package/dist/chat/super-grouping/activity-timeline.cjs +2 -1
  54. package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
  55. package/dist/chat/super-grouping/activity-timeline.js +2 -1
  56. package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
  57. package/dist/chat/thread-scroll-to-bottom.cjs +24 -0
  58. package/dist/chat/thread-scroll-to-bottom.cjs.map +1 -0
  59. package/dist/chat/thread-scroll-to-bottom.d.ts +11 -0
  60. package/dist/chat/thread-scroll-to-bottom.js +24 -0
  61. package/dist/chat/thread-scroll-to-bottom.js.map +1 -0
  62. package/dist/chat/tiptap-text.cjs +26 -95
  63. package/dist/chat/tiptap-text.cjs.map +1 -1
  64. package/dist/chat/tiptap-text.d.ts +13 -0
  65. package/dist/chat/tiptap-text.js +26 -95
  66. package/dist/chat/tiptap-text.js.map +1 -1
  67. package/dist/chat/use-athena-run-live.cjs +2 -1
  68. package/dist/chat/use-athena-run-live.cjs.map +1 -1
  69. package/dist/chat/use-athena-run-live.js +2 -1
  70. package/dist/chat/use-athena-run-live.js.map +1 -1
  71. package/dist/chat/use-statewire-approval-request.cjs +1 -0
  72. package/dist/chat/use-statewire-approval-request.cjs.map +1 -1
  73. package/dist/chat/use-statewire-approval-request.js +1 -0
  74. package/dist/chat/use-statewire-approval-request.js.map +1 -1
  75. package/dist/chat-ui/composer/tiptap-composer.cjs +39 -4
  76. package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
  77. package/dist/chat-ui/composer/tiptap-composer.d.ts +21 -0
  78. package/dist/chat-ui/composer/tiptap-composer.js +39 -4
  79. package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
  80. package/dist/chat-ui/index.cjs +15 -1
  81. package/dist/chat-ui/index.cjs.map +1 -1
  82. package/dist/chat-ui/index.d.ts +6 -3
  83. package/dist/chat-ui/index.js +16 -2
  84. package/dist/chat-ui/index.js.map +1 -1
  85. package/dist/chat-ui/mentions/mention-chip.cjs +31 -4
  86. package/dist/chat-ui/mentions/mention-chip.cjs.map +1 -1
  87. package/dist/chat-ui/mentions/mention-chip.js +31 -4
  88. package/dist/chat-ui/mentions/mention-chip.js.map +1 -1
  89. package/dist/chat-ui/messages/message-components.cjs +19 -1
  90. package/dist/chat-ui/messages/message-components.cjs.map +1 -1
  91. package/dist/chat-ui/messages/message-components.d.ts +13 -0
  92. package/dist/chat-ui/messages/message-components.js +19 -1
  93. package/dist/chat-ui/messages/message-components.js.map +1 -1
  94. package/dist/chat-ui/messages/runtime-message.cjs +2 -1
  95. package/dist/chat-ui/messages/runtime-message.cjs.map +1 -1
  96. package/dist/chat-ui/messages/runtime-message.js +2 -1
  97. package/dist/chat-ui/messages/runtime-message.js.map +1 -1
  98. package/dist/chat-ui/messages/statewire-continue-run.cjs +1 -0
  99. package/dist/chat-ui/messages/statewire-continue-run.cjs.map +1 -1
  100. package/dist/chat-ui/messages/statewire-continue-run.js +1 -0
  101. package/dist/chat-ui/messages/statewire-continue-run.js.map +1 -1
  102. package/dist/chat-ui/messages/user-edit-composer.cjs +3 -1
  103. package/dist/chat-ui/messages/user-edit-composer.cjs.map +1 -1
  104. package/dist/chat-ui/messages/user-edit-composer.js +3 -1
  105. package/dist/chat-ui/messages/user-edit-composer.js.map +1 -1
  106. package/dist/chat-ui/messages/wake-marker.cjs +84 -0
  107. package/dist/chat-ui/messages/wake-marker.cjs.map +1 -0
  108. package/dist/chat-ui/messages/wake-marker.d.ts +16 -0
  109. package/dist/chat-ui/messages/wake-marker.js +84 -0
  110. package/dist/chat-ui/messages/wake-marker.js.map +1 -0
  111. package/dist/chat-ui/rich-text/chat-blocks.cjs +177 -0
  112. package/dist/chat-ui/rich-text/chat-blocks.cjs.map +1 -0
  113. package/dist/chat-ui/rich-text/chat-blocks.d.ts +58 -0
  114. package/dist/chat-ui/rich-text/chat-blocks.js +177 -0
  115. package/dist/chat-ui/rich-text/chat-blocks.js.map +1 -0
  116. package/dist/chat-ui/rich-text/chat-link.cjs +69 -0
  117. package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -0
  118. package/dist/chat-ui/rich-text/chat-link.d.ts +24 -0
  119. package/dist/chat-ui/rich-text/chat-link.js +69 -0
  120. package/dist/chat-ui/rich-text/chat-link.js.map +1 -0
  121. package/dist/chat-ui/rich-text/chat-message-body.cjs +144 -18
  122. package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
  123. package/dist/chat-ui/rich-text/chat-message-body.d.ts +10 -1
  124. package/dist/chat-ui/rich-text/chat-message-body.js +149 -23
  125. package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
  126. package/dist/chat-ui/rich-text/chat-node-views.cjs +38 -5
  127. package/dist/chat-ui/rich-text/chat-node-views.cjs.map +1 -1
  128. package/dist/chat-ui/rich-text/chat-node-views.d.ts +11 -0
  129. package/dist/chat-ui/rich-text/chat-node-views.js +39 -6
  130. package/dist/chat-ui/rich-text/chat-node-views.js.map +1 -1
  131. package/dist/chat-ui/rich-text/chat-text-schema.cjs +20 -3
  132. package/dist/chat-ui/rich-text/chat-text-schema.cjs.map +1 -1
  133. package/dist/chat-ui/rich-text/chat-text-schema.d.ts +15 -2
  134. package/dist/chat-ui/rich-text/chat-text-schema.js +20 -3
  135. package/dist/chat-ui/rich-text/chat-text-schema.js.map +1 -1
  136. package/dist/chat-ui/rich-text/citation-anchors.cjs +139 -0
  137. package/dist/chat-ui/rich-text/citation-anchors.cjs.map +1 -0
  138. package/dist/chat-ui/rich-text/citation-anchors.d.ts +74 -0
  139. package/dist/chat-ui/rich-text/citation-anchors.js +139 -0
  140. package/dist/chat-ui/rich-text/citation-anchors.js.map +1 -0
  141. package/dist/chat-ui/statewire.cjs +8 -1
  142. package/dist/chat-ui/statewire.cjs.map +1 -1
  143. package/dist/chat-ui/statewire.d.ts +2 -0
  144. package/dist/chat-ui/statewire.js +8 -1
  145. package/dist/chat-ui/statewire.js.map +1 -1
  146. package/dist/chat-ui/thread/chat-composer-dock.cjs +27 -6
  147. package/dist/chat-ui/thread/chat-composer-dock.cjs.map +1 -1
  148. package/dist/chat-ui/thread/chat-composer-dock.d.ts +21 -1
  149. package/dist/chat-ui/thread/chat-composer-dock.js +28 -7
  150. package/dist/chat-ui/thread/chat-composer-dock.js.map +1 -1
  151. package/dist/chat-ui/thread/statewire-history-edge.cjs +12 -13
  152. package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
  153. package/dist/chat-ui/thread/statewire-history-edge.d.ts +11 -10
  154. package/dist/chat-ui/thread/statewire-history-edge.js +13 -14
  155. package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
  156. package/dist/chat-ui/thread/thread-loading-overlay.cjs +31 -0
  157. package/dist/chat-ui/thread/thread-loading-overlay.cjs.map +1 -0
  158. package/dist/chat-ui/thread/thread-loading-overlay.d.ts +14 -0
  159. package/dist/chat-ui/thread/thread-loading-overlay.js +31 -0
  160. package/dist/chat-ui/thread/thread-loading-overlay.js.map +1 -0
  161. package/dist/composer/ComposerDropZone.cjs +8 -1
  162. package/dist/composer/ComposerDropZone.cjs.map +1 -1
  163. package/dist/composer/ComposerDropZone.d.ts +6 -0
  164. package/dist/composer/ComposerDropZone.js +9 -2
  165. package/dist/composer/ComposerDropZone.js.map +1 -1
  166. package/dist/diagnostics/errors.cjs +20 -0
  167. package/dist/diagnostics/errors.cjs.map +1 -1
  168. package/dist/diagnostics/errors.d.ts +20 -0
  169. package/dist/diagnostics/errors.js +20 -0
  170. package/dist/diagnostics/errors.js.map +1 -1
  171. package/dist/index.cjs +47 -1
  172. package/dist/index.cjs.map +1 -1
  173. package/dist/index.d.ts +26 -7
  174. package/dist/index.js +51 -5
  175. package/dist/index.js.map +1 -1
  176. package/dist/lib/use-prefers-reduced-motion.cjs +29 -0
  177. package/dist/lib/use-prefers-reduced-motion.cjs.map +1 -0
  178. package/dist/lib/use-prefers-reduced-motion.d.ts +6 -0
  179. package/dist/lib/use-prefers-reduced-motion.js +29 -0
  180. package/dist/lib/use-prefers-reduced-motion.js.map +1 -0
  181. package/dist/packages/agent-runtime/src/conversation-index.cjs.map +1 -1
  182. package/dist/packages/agent-runtime/src/conversation-index.js.map +1 -1
  183. package/dist/packages/agent-runtime/src/conversation-list.cjs +59 -0
  184. package/dist/packages/agent-runtime/src/conversation-list.cjs.map +1 -1
  185. package/dist/packages/agent-runtime/src/conversation-list.js +60 -1
  186. package/dist/packages/agent-runtime/src/conversation-list.js.map +1 -1
  187. package/dist/packages/agent-runtime/src/deep-agent-transport-recipe.cjs +5 -3
  188. package/dist/packages/agent-runtime/src/deep-agent-transport-recipe.cjs.map +1 -1
  189. package/dist/packages/agent-runtime/src/deep-agent-transport-recipe.js +5 -3
  190. package/dist/packages/agent-runtime/src/deep-agent-transport-recipe.js.map +1 -1
  191. package/dist/packages/agent-runtime/src/edit-command.cjs +21 -17
  192. package/dist/packages/agent-runtime/src/edit-command.cjs.map +1 -1
  193. package/dist/packages/agent-runtime/src/edit-command.js +22 -18
  194. package/dist/packages/agent-runtime/src/edit-command.js.map +1 -1
  195. package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs +9 -11
  196. package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
  197. package/dist/packages/agent-runtime/src/generated/runconfig-contract.js +9 -11
  198. package/dist/packages/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
  199. package/dist/packages/agent-runtime/src/github-wake-event.cjs +86 -0
  200. package/dist/packages/agent-runtime/src/github-wake-event.cjs.map +1 -0
  201. package/dist/packages/agent-runtime/src/github-wake-event.js +86 -0
  202. package/dist/packages/agent-runtime/src/github-wake-event.js.map +1 -0
  203. package/dist/packages/agent-runtime/src/message-window.cjs +150 -42
  204. package/dist/packages/agent-runtime/src/message-window.cjs.map +1 -1
  205. package/dist/packages/agent-runtime/src/message-window.js +151 -43
  206. package/dist/packages/agent-runtime/src/message-window.js.map +1 -1
  207. package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.cjs +13 -9
  208. package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -1
  209. package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.js +13 -9
  210. package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -1
  211. package/dist/packages/agent-runtime/src/statewire-suspended.cjs +1 -3
  212. package/dist/packages/agent-runtime/src/statewire-suspended.cjs.map +1 -1
  213. package/dist/packages/agent-runtime/src/statewire-suspended.js +1 -3
  214. package/dist/packages/agent-runtime/src/statewire-suspended.js.map +1 -1
  215. package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.cjs.map +1 -1
  216. package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.js.map +1 -1
  217. package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs +61 -30
  218. package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
  219. package/dist/packages/agent-runtime/src/use-deep-agent-thread.js +59 -28
  220. package/dist/packages/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
  221. package/dist/packages/agent-runtime/src/use-deep-agent-window-sync.cjs +56 -36
  222. package/dist/packages/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -1
  223. package/dist/packages/agent-runtime/src/use-deep-agent-window-sync.js +59 -39
  224. package/dist/packages/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -1
  225. package/dist/packages/react/package.json.cjs +1 -1
  226. package/dist/packages/react/package.json.js +1 -1
  227. package/dist/provider/AthenaContext.cjs.map +1 -1
  228. package/dist/provider/AthenaContext.d.ts +3 -0
  229. package/dist/provider/AthenaContext.js.map +1 -1
  230. package/dist/provider/AthenaProvider.cjs +93 -45
  231. package/dist/provider/AthenaProvider.cjs.map +1 -1
  232. package/dist/provider/AthenaProvider.d.ts +6 -1
  233. package/dist/provider/AthenaProvider.js +97 -49
  234. package/dist/provider/AthenaProvider.js.map +1 -1
  235. package/dist/runtime/statewire-lifecycle.cjs +2 -1
  236. package/dist/runtime/statewire-lifecycle.cjs.map +1 -1
  237. package/dist/runtime/statewire-lifecycle.js +2 -1
  238. package/dist/runtime/statewire-lifecycle.js.map +1 -1
  239. package/dist/runtime/useAthenaStatewireRuntime.cjs +3 -2
  240. package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
  241. package/dist/runtime/useAthenaStatewireRuntime.d.ts +8 -12
  242. package/dist/runtime/useAthenaStatewireRuntime.js +3 -2
  243. package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
  244. package/dist/search/useAthenaRepoSearch.cjs +94 -0
  245. package/dist/search/useAthenaRepoSearch.cjs.map +1 -0
  246. package/dist/search/useAthenaRepoSearch.d.ts +95 -0
  247. package/dist/search/useAthenaRepoSearch.js +94 -0
  248. package/dist/search/useAthenaRepoSearch.js.map +1 -0
  249. package/dist/styles.css +1 -1
  250. package/dist/threads/SessionList.cjs +486 -0
  251. package/dist/threads/SessionList.cjs.map +1 -0
  252. package/dist/threads/SessionList.d.ts +60 -0
  253. package/dist/threads/SessionList.js +486 -0
  254. package/dist/threads/SessionList.js.map +1 -0
  255. package/dist/threads/api.cjs +112 -4
  256. package/dist/threads/api.cjs.map +1 -1
  257. package/dist/threads/api.d.ts +128 -1
  258. package/dist/threads/api.js +113 -5
  259. package/dist/threads/api.js.map +1 -1
  260. package/dist/threads/conversation-list-fetcher.cjs +27 -3
  261. package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
  262. package/dist/threads/conversation-list-fetcher.d.ts +16 -3
  263. package/dist/threads/conversation-list-fetcher.js +27 -3
  264. package/dist/threads/conversation-list-fetcher.js.map +1 -1
  265. package/dist/threads/index.d.ts +5 -1
  266. package/dist/threads/session-list-model.cjs +130 -0
  267. package/dist/threads/session-list-model.cjs.map +1 -0
  268. package/dist/threads/session-list-model.d.ts +62 -0
  269. package/dist/threads/session-list-model.js +130 -0
  270. package/dist/threads/session-list-model.js.map +1 -0
  271. package/dist/threads/statewire-thread-list.cjs +220 -38
  272. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  273. package/dist/threads/statewire-thread-list.d.ts +152 -2
  274. package/dist/threads/statewire-thread-list.js +222 -40
  275. package/dist/threads/statewire-thread-list.js.map +1 -1
  276. package/dist/threads/thread-prefetcher.cjs +63 -0
  277. package/dist/threads/thread-prefetcher.cjs.map +1 -0
  278. package/dist/threads/thread-prefetcher.d.ts +54 -0
  279. package/dist/threads/thread-prefetcher.js +63 -0
  280. package/dist/threads/thread-prefetcher.js.map +1 -0
  281. package/dist/threads/use-athena-session-attention.cjs +22 -0
  282. package/dist/threads/use-athena-session-attention.cjs.map +1 -0
  283. package/dist/threads/use-athena-session-attention.d.ts +24 -0
  284. package/dist/threads/use-athena-session-attention.js +22 -0
  285. package/dist/threads/use-athena-session-attention.js.map +1 -0
  286. package/dist/threads/use-rest-conversation-history.cjs +25 -2
  287. package/dist/threads/use-rest-conversation-history.cjs.map +1 -1
  288. package/dist/threads/use-rest-conversation-history.d.ts +8 -1
  289. package/dist/threads/use-rest-conversation-history.js +25 -2
  290. package/dist/threads/use-rest-conversation-history.js.map +1 -1
  291. package/dist/threads/use-statewire-conversation-history.cjs +40 -16
  292. package/dist/threads/use-statewire-conversation-history.cjs.map +1 -1
  293. package/dist/threads/use-statewire-conversation-history.d.ts +13 -0
  294. package/dist/threads/use-statewire-conversation-history.js +40 -16
  295. package/dist/threads/use-statewire-conversation-history.js.map +1 -1
  296. package/dist/threads/use-thread-archive.cjs +128 -0
  297. package/dist/threads/use-thread-archive.cjs.map +1 -0
  298. package/dist/threads/use-thread-archive.d.ts +26 -0
  299. package/dist/threads/use-thread-archive.js +128 -0
  300. package/dist/threads/use-thread-archive.js.map +1 -0
  301. package/dist/threads/use-thread-favorite.cjs +53 -0
  302. package/dist/threads/use-thread-favorite.cjs.map +1 -0
  303. package/dist/threads/use-thread-favorite.d.ts +49 -0
  304. package/dist/threads/use-thread-favorite.js +53 -0
  305. package/dist/threads/use-thread-favorite.js.map +1 -0
  306. package/dist/threads/use-thread-field-override.cjs +184 -0
  307. package/dist/threads/use-thread-field-override.cjs.map +1 -0
  308. package/dist/threads/use-thread-field-override.d.ts +55 -0
  309. package/dist/threads/use-thread-field-override.js +184 -0
  310. package/dist/threads/use-thread-field-override.js.map +1 -0
  311. package/dist/threads/use-thread-rename.cjs +53 -0
  312. package/dist/threads/use-thread-rename.cjs.map +1 -0
  313. package/dist/threads/use-thread-rename.d.ts +48 -0
  314. package/dist/threads/use-thread-rename.js +53 -0
  315. package/dist/threads/use-thread-rename.js.map +1 -0
  316. package/dist/tools/index.cjs +5 -1
  317. package/dist/tools/index.cjs.map +1 -1
  318. package/dist/tools/index.d.ts +1 -1
  319. package/dist/tools/index.js +7 -3
  320. package/dist/tools/index.js.map +1 -1
  321. package/dist/tools/misc-tool-uis.cjs +2 -1
  322. package/dist/tools/misc-tool-uis.cjs.map +1 -1
  323. package/dist/tools/misc-tool-uis.js +2 -1
  324. package/dist/tools/misc-tool-uis.js.map +1 -1
  325. package/dist/tools/primitives.cjs +25 -2
  326. package/dist/tools/primitives.cjs.map +1 -1
  327. package/dist/tools/primitives.d.ts +9 -2
  328. package/dist/tools/primitives.js +26 -3
  329. package/dist/tools/primitives.js.map +1 -1
  330. package/dist/tools/tool-appearance.cjs +11 -0
  331. package/dist/tools/tool-appearance.cjs.map +1 -0
  332. package/dist/tools/tool-appearance.d.ts +29 -0
  333. package/dist/tools/tool-appearance.js +11 -0
  334. package/dist/tools/tool-appearance.js.map +1 -0
  335. package/dist/tools/tool-fallback.cjs +211 -13
  336. package/dist/tools/tool-fallback.cjs.map +1 -1
  337. package/dist/tools/tool-fallback.d.ts +31 -4
  338. package/dist/tools/tool-fallback.js +214 -16
  339. package/dist/tools/tool-fallback.js.map +1 -1
  340. package/dist/tools/use-elapsed-seconds.cjs +24 -0
  341. package/dist/tools/use-elapsed-seconds.cjs.map +1 -0
  342. package/dist/tools/use-elapsed-seconds.d.ts +10 -0
  343. package/dist/tools/use-elapsed-seconds.js +24 -0
  344. package/dist/tools/use-elapsed-seconds.js.map +1 -0
  345. package/dist/tools/voice-update-tool-ui.cjs +2 -1
  346. package/dist/tools/voice-update-tool-ui.cjs.map +1 -1
  347. package/dist/tools/voice-update-tool-ui.js +2 -1
  348. package/dist/tools/voice-update-tool-ui.js.map +1 -1
  349. package/package.json +3 -3
  350. package/src/chat-ui/tailwind-preset.js +14 -1
@@ -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 { Fragment, type ReactNode, useMemo, useSyncExternalStore } from \"react\";\nimport {\n MentionLink,\n normalizeAthenaMentionHtml,\n} from \"../mentions/mention-chip\";\nimport { ChatCodeBlock, ChatMention } from \"./chat-node-views\";\nimport { CHAT_TEXT_CLASS, parseChatText } from \"./chat-text-schema\";\nimport { renderDOMOutputSpec } from \"./dom-output-spec\";\n\nconst subscribe = () => () => {};\nconst browserSnapshot = () => true;\nconst serverSnapshot = () => false;\n\n/** Render the composer's document schema through its own node/mark serializers. */\nexport function ChatMessageBody({\n text,\n isRunning = false,\n}: {\n text: string;\n isRunning?: boolean;\n}) {\n const hasDom = useSyncExternalStore(\n subscribe,\n browserSnapshot,\n serverSnapshot,\n );\n const doc = useMemo(\n () => (hasDom ? parseChatText(normalizeAthenaMentionHtml(text)) : null),\n [hasDom, text],\n );\n return (\n <div\n className={CHAT_TEXT_CLASS}\n data-chat-text=\"\"\n data-status={isRunning ? \"running\" : \"complete\"}\n >\n {doc ? renderContent(doc) : text}\n </div>\n );\n}\n\nfunction renderNode(node: ProseMirrorNode): ReactNode {\n if (node.isText) return node.text;\n if (node.type.name === \"mention\") return <ChatMention node={node} />;\n if (node.type.name === \"codeBlock\") {\n return (\n <ChatCodeBlock node={node}>\n <code>{node.textContent}</code>\n </ChatCodeBlock>\n );\n }\n const toDOM = node.type.spec.toDOM;\n return toDOM\n ? renderDOMOutputSpec(toDOM(node), node.isLeaf ? null : renderContent(node))\n : null;\n}\n\nfunction renderContent(parent: ProseMirrorNode): ReactNode[] {\n const nodes: ProseMirrorNode[] = [];\n parent.forEach((node) => {\n nodes.push(node);\n });\n return renderMarkRuns(nodes, 0);\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): 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)}</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);\n const rendered =\n mark.type.name === \"link\" ? (\n <MentionLink href={mark.attrs.href}>{content}</MentionLink>\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","parseChatText","normalizeAthenaMentionHtml","jsx","CHAT_TEXT_CLASS","ChatMention","ChatCodeBlock","renderDOMOutputSpec","Fragment","MentionLink"],"mappings":";;;;;;;;AAUA,MAAM,YAAY,MAAM,MAAM;AAAC;AAC/B,MAAM,kBAAkB,MAAM;AAC9B,MAAM,iBAAiB,MAAM;AAGtB,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,YAAY;AACd,GAGG;AACD,QAAM,SAASA,MAAAA;AAAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEF,QAAM,MAAMC,MAAAA;AAAAA,IACV,MAAO,SAASC,eAAAA,cAAcC,YAAAA,2BAA2B,IAAI,CAAC,IAAI;AAAA,IAClE,CAAC,QAAQ,IAAI;AAAA,EAAA;AAEf,SACEC,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWC,eAAAA;AAAAA,MACX,kBAAe;AAAA,MACf,eAAa,YAAY,YAAY;AAAA,MAEpC,UAAA,MAAM,cAAc,GAAG,IAAI;AAAA,IAAA;AAAA,EAAA;AAGlC;AAEA,SAAS,WAAW,MAAkC;AACpD,MAAI,KAAK,OAAQ,QAAO,KAAK;AAC7B,MAAI,KAAK,KAAK,SAAS,UAAW,QAAOD,+BAACE,cAAAA,eAAY,MAAY;AAClE,MAAI,KAAK,KAAK,SAAS,aAAa;AAClC,0CACGC,cAAAA,eAAA,EAAc,MACb,yCAAC,QAAA,EAAM,UAAA,KAAK,aAAY,EAAA,CAC1B;AAAA,EAEJ;AACA,QAAM,QAAQ,KAAK,KAAK,KAAK;AAC7B,SAAO,QACHC,cAAAA,oBAAoB,MAAM,IAAI,GAAG,KAAK,SAAS,OAAO,cAAc,IAAI,CAAC,IACzE;AACN;AAEA,SAAS,cAAc,QAAsC;AAC3D,QAAM,QAA2B,CAAA;AACjC,SAAO,QAAQ,CAAC,SAAS;AACvB,UAAM,KAAK,IAAI;AAAA,EACjB,CAAC;AACD,SAAO,eAAe,OAAO,CAAC;AAChC;AAGA,SAAS,eACP,OACA,OACa;;AACb,QAAM,WAAwB,CAAA;AAC9B,MAAI,QAAQ;AACZ,SAAO,QAAQ,MAAM,QAAQ;AAC3B,UAAM,OAAO,MAAM,KAAK;AACxB,UAAM,OAAO,KAAK,MAAM,KAAK;AAC7B,QAAI,CAAC,MAAM;AACT,eAAS,KAAKJ,2BAAAA,IAACK,gBAAA,EAAsB,qBAAW,IAAI,KAAvB,KAAyB,CAAW;AACjE;AACA;AAAA,IACF;AACA,QAAI,MAAM,QAAQ;AAClB,QAAI,KAAK,KAAK,KAAK,aAAa,OAAO;AACrC,aAAO,MAAM,MAAM,YAAU,iBAAM,GAAG,MAAT,mBAAY,MAAM,WAAlB,mBAA0B,GAAG,OAAO;AAAA,IACnE;AACA,UAAM,UAAU,eAAe,MAAM,MAAM,OAAO,GAAG,GAAG,QAAQ,CAAC;AACjE,UAAM,WACJ,KAAK,KAAK,SAAS,SACjBL,2BAAAA,IAACM,yBAAA,EAAY,MAAM,KAAK,MAAM,MAAO,UAAA,QAAA,CAAQ,IAC3C,KAAK,KAAK,KAAK,QACjBF,cAAAA,oBAAoB,KAAK,KAAK,KAAK,MAAM,MAAM,IAAI,GAAG,OAAO,IAE7D;AAEJ,aAAS,KAAKJ,2BAAAA,IAACK,MAAAA,UAAA,EAAsB,UAAA,SAAA,GAAR,KAAiB,CAAW;AACzD,YAAQ;AAAA,EACV;AACA,SAAO;AACT;;"}
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 { CHAT_TEXT_CLASS, parseChatText } 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 */\nexport function ChatMessageBody({\n text,\n isRunning = false,\n}: {\n text: string;\n isRunning?: 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 />\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}: {\n text: string;\n definitions: string;\n openFence: OpenFence | null;\n streaming: 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 () =>\n open ? null : parseChatText(definitions ? `${text}\\n\\n${definitions}` : text),\n [text, definitions, open],\n );\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 linkText: 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","parseChatText","StreamingCodeBlock","Fragment","useAthenaLinkClickHandler","useCallback","jsxs","ChatMention","ChatCodeBlock","renderDOMOutputSpec","ChatLink"],"mappings":"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"}
@@ -1,4 +1,13 @@
1
- /** Render the composer's document schema through its own node/mark serializers. */
1
+ /**
2
+ * Render the composer's document schema through its own node/mark serializers.
3
+ *
4
+ * The text is split into its top-level blocks (`splitChatBlocks`), each parsed
5
+ * and drawn by its own memoized component under one `CHAT_TEXT_CLASS`
6
+ * wrapper. While a reply streams, only the trailing block re-parses with each
7
+ * token; every finished block keeps its parse and its DOM. The trailing block
8
+ * reveals its newest words with a settle tint and a caret, and a fenced code
9
+ * block still being written renders as plain code until its fence closes.
10
+ */
2
11
  export declare function ChatMessageBody({ text, isRunning, }: {
3
12
  text: string;
4
13
  isRunning?: boolean;
@@ -1,8 +1,10 @@
1
- import { jsx } from "react/jsx-runtime";
2
- import { useSyncExternalStore, useMemo, Fragment } from "react";
3
- import { normalizeAthenaMentionHtml, MentionLink } from "../mentions/mention-chip.js";
4
- import { ChatMention, ChatCodeBlock } from "./chat-node-views.js";
5
- import { parseChatText, CHAT_TEXT_CLASS } from "./chat-text-schema.js";
1
+ import { jsx, Fragment, jsxs } from "react/jsx-runtime";
2
+ import { useSyncExternalStore, useMemo, useCallback, memo, Fragment as Fragment$1 } from "react";
3
+ import { useAthenaLinkClickHandler } from "../../chat/athena-citation-links.js";
4
+ import { splitChatBlocks } from "./chat-blocks.js";
5
+ import { ChatLink } from "./chat-link.js";
6
+ import { StreamingCodeBlock, ChatMention, ChatCodeBlock } from "./chat-node-views.js";
7
+ import { CHAT_TEXT_CLASS, parseChatText } from "./chat-text-schema.js";
6
8
  import { renderDOMOutputSpec } from "./dom-output-spec.js";
7
9
  const subscribe = () => () => {
8
10
  };
@@ -17,37 +19,161 @@ function ChatMessageBody({
17
19
  browserSnapshot,
18
20
  serverSnapshot
19
21
  );
22
+ const onClick = useChatLinkClick();
23
+ const { blocks, definitions } = useMemo(
24
+ () => splitChatBlocks(text, { streaming: isRunning }),
25
+ [text, isRunning]
26
+ );
27
+ return (
28
+ // The click handler only delegates for the anchors inside, which are
29
+ // themselves focusable and keyboard-operable; the wrapper is not a control.
30
+ /* @__PURE__ */ jsx(
31
+ "div",
32
+ {
33
+ className: CHAT_TEXT_CLASS,
34
+ "data-chat-text": "",
35
+ "data-status": isRunning ? "running" : "complete",
36
+ onClick,
37
+ children: hasDom ? blocks.map((block, index) => /* @__PURE__ */ jsx(
38
+ ChatBlock,
39
+ {
40
+ text: block.text,
41
+ definitions,
42
+ openFence: block.openFence,
43
+ streaming: isRunning && index === blocks.length - 1
44
+ },
45
+ index
46
+ )) : text
47
+ }
48
+ )
49
+ );
50
+ }
51
+ const ChatBlock = memo(function ChatBlock2({
52
+ text,
53
+ definitions,
54
+ openFence,
55
+ streaming
56
+ }) {
57
+ const open = streaming ? openFence : null;
20
58
  const doc = useMemo(
21
- () => hasDom ? parseChatText(normalizeAthenaMentionHtml(text)) : null,
22
- [hasDom, text]
59
+ () => open ? null : parseChatText(definitions ? `${text}
60
+
61
+ ${definitions}` : text),
62
+ [text, definitions, open]
63
+ );
64
+ if (open) {
65
+ return /* @__PURE__ */ jsx(
66
+ StreamingCodeBlock,
67
+ {
68
+ language: open.language,
69
+ code: open.code,
70
+ caret: /* @__PURE__ */ jsx(StreamingCaret, {})
71
+ }
72
+ );
73
+ }
74
+ if (!doc || isEmptyDoc(doc)) return null;
75
+ return /* @__PURE__ */ jsx(Fragment, { children: renderContent(doc, streaming ? findStreamingTail(doc) : null) });
76
+ });
77
+ function isEmptyDoc(doc) {
78
+ if (doc.childCount === 0) return true;
79
+ const only = doc.firstChild;
80
+ return doc.childCount === 1 && only !== null && only.isTextblock && only.content.size === 0;
81
+ }
82
+ function useChatLinkClick() {
83
+ const handleLinkClick = useAthenaLinkClickHandler();
84
+ return useCallback(
85
+ (event) => {
86
+ if (event.button !== 0 || event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
87
+ return;
88
+ }
89
+ const target = event.target;
90
+ const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
91
+ const anchor = element == null ? void 0 : element.closest("a[href]");
92
+ if (!(anchor instanceof HTMLAnchorElement) || !event.currentTarget.contains(anchor)) {
93
+ return;
94
+ }
95
+ const handled = handleLinkClick({
96
+ href: anchor.getAttribute("href") ?? anchor.href,
97
+ linkText: anchor.textContent,
98
+ target: anchor.target,
99
+ nativeEvent: event.nativeEvent
100
+ });
101
+ if (handled) event.preventDefault();
102
+ },
103
+ [handleLinkClick]
23
104
  );
105
+ }
106
+ function findStreamingTail(doc) {
107
+ const tail = { textblock: null, text: null };
108
+ doc.descendants((node) => {
109
+ if (node.isTextblock) {
110
+ tail.textblock = node;
111
+ tail.text = null;
112
+ } else if (node.isText && tail.textblock && tail.textblock.type.name !== "codeBlock") {
113
+ tail.text = node;
114
+ }
115
+ return true;
116
+ });
117
+ return tail;
118
+ }
119
+ const FRESH_WORD_WINDOW = 6;
120
+ const WORD = /\s*\S+/g;
121
+ function renderFreshWords(text) {
122
+ const words = [...text.matchAll(WORD)];
123
+ if (words.length === 0) return text;
124
+ const from = Math.max(0, words.length - FRESH_WORD_WINDOW);
125
+ const first = words[from];
126
+ const last = words[words.length - 1];
127
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
128
+ text.slice(0, first.index),
129
+ words.slice(from).map((word, offset) => /* @__PURE__ */ jsx(
130
+ "span",
131
+ {
132
+ className: "animate-chat-word-settle motion-reduce:animate-none",
133
+ "data-chat-fresh": "",
134
+ children: word[0]
135
+ },
136
+ from + offset
137
+ )),
138
+ text.slice(last.index + last[0].length)
139
+ ] });
140
+ }
141
+ function StreamingCaret() {
24
142
  return /* @__PURE__ */ jsx(
25
- "div",
143
+ "span",
26
144
  {
27
- className: CHAT_TEXT_CLASS,
28
- "data-chat-text": "",
29
- "data-status": isRunning ? "running" : "complete",
30
- children: doc ? renderContent(doc) : text
145
+ "aria-hidden": "true",
146
+ "data-chat-caret": "",
147
+ 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"
31
148
  }
32
149
  );
33
150
  }
34
- function renderNode(node) {
35
- if (node.isText) return node.text;
151
+ function renderNode(node, tail) {
152
+ if (node.isText) {
153
+ return (tail == null ? void 0 : tail.text) === node ? renderFreshWords(node.text ?? "") : node.text;
154
+ }
36
155
  if (node.type.name === "mention") return /* @__PURE__ */ jsx(ChatMention, { node });
37
156
  if (node.type.name === "codeBlock") {
38
- return /* @__PURE__ */ jsx(ChatCodeBlock, { node, children: /* @__PURE__ */ jsx("code", { children: node.textContent }) });
157
+ return /* @__PURE__ */ jsx(ChatCodeBlock, { node, children: /* @__PURE__ */ jsxs("code", { children: [
158
+ node.textContent,
159
+ (tail == null ? void 0 : tail.textblock) === node ? /* @__PURE__ */ jsx(StreamingCaret, {}) : null
160
+ ] }) });
39
161
  }
40
162
  const toDOM = node.type.spec.toDOM;
41
- return toDOM ? renderDOMOutputSpec(toDOM(node), node.isLeaf ? null : renderContent(node)) : null;
163
+ if (!toDOM) return null;
164
+ if (node.isLeaf) return renderDOMOutputSpec(toDOM(node), null);
165
+ const content = renderContent(node, tail);
166
+ if ((tail == null ? void 0 : tail.textblock) === node) content.push(/* @__PURE__ */ jsx(StreamingCaret, {}, "caret"));
167
+ return renderDOMOutputSpec(toDOM(node), content);
42
168
  }
43
- function renderContent(parent) {
169
+ function renderContent(parent, tail) {
44
170
  const nodes = [];
45
171
  parent.forEach((node) => {
46
172
  nodes.push(node);
47
173
  });
48
- return renderMarkRuns(nodes, 0);
174
+ return renderMarkRuns(nodes, 0, tail);
49
175
  }
50
- function renderMarkRuns(nodes, depth) {
176
+ function renderMarkRuns(nodes, depth, tail) {
51
177
  var _a, _b;
52
178
  const children = [];
53
179
  let index = 0;
@@ -55,7 +181,7 @@ function renderMarkRuns(nodes, depth) {
55
181
  const node = nodes[index];
56
182
  const mark = node.marks[depth];
57
183
  if (!mark) {
58
- children.push(/* @__PURE__ */ jsx(Fragment, { children: renderNode(node) }, index));
184
+ children.push(/* @__PURE__ */ jsx(Fragment$1, { children: renderNode(node, tail) }, index));
59
185
  index++;
60
186
  continue;
61
187
  }
@@ -63,9 +189,9 @@ function renderMarkRuns(nodes, depth) {
63
189
  if (mark.type.spec.spanning !== false) {
64
190
  while (end < nodes.length && ((_b = (_a = nodes[end]) == null ? void 0 : _a.marks[depth]) == null ? void 0 : _b.eq(mark))) end++;
65
191
  }
66
- const content = renderMarkRuns(nodes.slice(index, end), depth + 1);
67
- const rendered = mark.type.name === "link" ? /* @__PURE__ */ jsx(MentionLink, { href: mark.attrs.href, children: content }) : mark.type.spec.toDOM ? renderDOMOutputSpec(mark.type.spec.toDOM(mark, true), content) : content;
68
- children.push(/* @__PURE__ */ jsx(Fragment, { children: rendered }, index));
192
+ const content = renderMarkRuns(nodes.slice(index, end), depth + 1, tail);
193
+ const rendered = mark.type.name === "link" ? /* @__PURE__ */ jsx(ChatLink, { href: mark.attrs.href, excerpt: mark.attrs.excerpt, children: content }) : mark.type.spec.toDOM ? renderDOMOutputSpec(mark.type.spec.toDOM(mark, true), content) : content;
194
+ children.push(/* @__PURE__ */ jsx(Fragment$1, { children: rendered }, index));
69
195
  index = end;
70
196
  }
71
197
  return children;
@@ -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 { Fragment, type ReactNode, useMemo, useSyncExternalStore } from \"react\";\nimport {\n MentionLink,\n normalizeAthenaMentionHtml,\n} from \"../mentions/mention-chip\";\nimport { ChatCodeBlock, ChatMention } from \"./chat-node-views\";\nimport { CHAT_TEXT_CLASS, parseChatText } from \"./chat-text-schema\";\nimport { renderDOMOutputSpec } from \"./dom-output-spec\";\n\nconst subscribe = () => () => {};\nconst browserSnapshot = () => true;\nconst serverSnapshot = () => false;\n\n/** Render the composer's document schema through its own node/mark serializers. */\nexport function ChatMessageBody({\n text,\n isRunning = false,\n}: {\n text: string;\n isRunning?: boolean;\n}) {\n const hasDom = useSyncExternalStore(\n subscribe,\n browserSnapshot,\n serverSnapshot,\n );\n const doc = useMemo(\n () => (hasDom ? parseChatText(normalizeAthenaMentionHtml(text)) : null),\n [hasDom, text],\n );\n return (\n <div\n className={CHAT_TEXT_CLASS}\n data-chat-text=\"\"\n data-status={isRunning ? \"running\" : \"complete\"}\n >\n {doc ? renderContent(doc) : text}\n </div>\n );\n}\n\nfunction renderNode(node: ProseMirrorNode): ReactNode {\n if (node.isText) return node.text;\n if (node.type.name === \"mention\") return <ChatMention node={node} />;\n if (node.type.name === \"codeBlock\") {\n return (\n <ChatCodeBlock node={node}>\n <code>{node.textContent}</code>\n </ChatCodeBlock>\n );\n }\n const toDOM = node.type.spec.toDOM;\n return toDOM\n ? renderDOMOutputSpec(toDOM(node), node.isLeaf ? null : renderContent(node))\n : null;\n}\n\nfunction renderContent(parent: ProseMirrorNode): ReactNode[] {\n const nodes: ProseMirrorNode[] = [];\n parent.forEach((node) => {\n nodes.push(node);\n });\n return renderMarkRuns(nodes, 0);\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): 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)}</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);\n const rendered =\n mark.type.name === \"link\" ? (\n <MentionLink href={mark.attrs.href}>{content}</MentionLink>\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":[],"mappings":";;;;;;AAUA,MAAM,YAAY,MAAM,MAAM;AAAC;AAC/B,MAAM,kBAAkB,MAAM;AAC9B,MAAM,iBAAiB,MAAM;AAGtB,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,YAAY;AACd,GAGG;AACD,QAAM,SAAS;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEF,QAAM,MAAM;AAAA,IACV,MAAO,SAAS,cAAc,2BAA2B,IAAI,CAAC,IAAI;AAAA,IAClE,CAAC,QAAQ,IAAI;AAAA,EAAA;AAEf,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA,MACX,kBAAe;AAAA,MACf,eAAa,YAAY,YAAY;AAAA,MAEpC,UAAA,MAAM,cAAc,GAAG,IAAI;AAAA,IAAA;AAAA,EAAA;AAGlC;AAEA,SAAS,WAAW,MAAkC;AACpD,MAAI,KAAK,OAAQ,QAAO,KAAK;AAC7B,MAAI,KAAK,KAAK,SAAS,UAAW,QAAO,oBAAC,eAAY,MAAY;AAClE,MAAI,KAAK,KAAK,SAAS,aAAa;AAClC,+BACG,eAAA,EAAc,MACb,8BAAC,QAAA,EAAM,UAAA,KAAK,aAAY,EAAA,CAC1B;AAAA,EAEJ;AACA,QAAM,QAAQ,KAAK,KAAK,KAAK;AAC7B,SAAO,QACH,oBAAoB,MAAM,IAAI,GAAG,KAAK,SAAS,OAAO,cAAc,IAAI,CAAC,IACzE;AACN;AAEA,SAAS,cAAc,QAAsC;AAC3D,QAAM,QAA2B,CAAA;AACjC,SAAO,QAAQ,CAAC,SAAS;AACvB,UAAM,KAAK,IAAI;AAAA,EACjB,CAAC;AACD,SAAO,eAAe,OAAO,CAAC;AAChC;AAGA,SAAS,eACP,OACA,OACa;;AACb,QAAM,WAAwB,CAAA;AAC9B,MAAI,QAAQ;AACZ,SAAO,QAAQ,MAAM,QAAQ;AAC3B,UAAM,OAAO,MAAM,KAAK;AACxB,UAAM,OAAO,KAAK,MAAM,KAAK;AAC7B,QAAI,CAAC,MAAM;AACT,eAAS,KAAK,oBAAC,UAAA,EAAsB,qBAAW,IAAI,KAAvB,KAAyB,CAAW;AACjE;AACA;AAAA,IACF;AACA,QAAI,MAAM,QAAQ;AAClB,QAAI,KAAK,KAAK,KAAK,aAAa,OAAO;AACrC,aAAO,MAAM,MAAM,YAAU,iBAAM,GAAG,MAAT,mBAAY,MAAM,WAAlB,mBAA0B,GAAG,OAAO;AAAA,IACnE;AACA,UAAM,UAAU,eAAe,MAAM,MAAM,OAAO,GAAG,GAAG,QAAQ,CAAC;AACjE,UAAM,WACJ,KAAK,KAAK,SAAS,SACjB,oBAAC,aAAA,EAAY,MAAM,KAAK,MAAM,MAAO,UAAA,QAAA,CAAQ,IAC3C,KAAK,KAAK,KAAK,QACjB,oBAAoB,KAAK,KAAK,KAAK,MAAM,MAAM,IAAI,GAAG,OAAO,IAE7D;AAEJ,aAAS,KAAK,oBAAC,UAAA,EAAsB,UAAA,SAAA,GAAR,KAAiB,CAAW;AACzD,YAAQ;AAAA,EACV;AACA,SAAO;AACT;"}
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 { CHAT_TEXT_CLASS, parseChatText } 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 */\nexport function ChatMessageBody({\n text,\n isRunning = false,\n}: {\n text: string;\n isRunning?: 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 />\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}: {\n text: string;\n definitions: string;\n openFence: OpenFence | null;\n streaming: 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 () =>\n open ? null : parseChatText(definitions ? `${text}\\n\\n${definitions}` : text),\n [text, definitions, open],\n );\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 linkText: 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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"}
@@ -78,15 +78,47 @@ function CodeBlockCopyButton({ node }) {
78
78
  }
79
79
  );
80
80
  }
81
+ function CodeBlockFrame({
82
+ language,
83
+ action,
84
+ children,
85
+ ...attributes
86
+ }) {
87
+ return /* @__PURE__ */ jsxRuntime.jsxs(
88
+ "div",
89
+ {
90
+ className: "not-prose group/code-block relative my-2 overflow-hidden rounded-lg border border-border bg-muted/30",
91
+ ...attributes,
92
+ children: [
93
+ language && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-border bg-muted/50 px-3 py-1 text-[10px] font-medium text-muted-foreground", children: language }),
94
+ action && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-1 right-1", contentEditable: false, children: action }),
95
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "overflow-x-auto p-3 pr-9 font-mono text-xs leading-relaxed text-foreground", children })
96
+ ]
97
+ }
98
+ );
99
+ }
81
100
  function ChatCodeBlock({
82
101
  node,
83
102
  children
84
103
  }) {
85
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "not-prose group/code-block relative my-2 overflow-hidden rounded-lg border border-border bg-muted/30", children: [
86
- node.attrs.language && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-border bg-muted/50 px-3 py-1 text-[10px] font-medium text-muted-foreground", children: node.attrs.language }),
87
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-1 right-1", contentEditable: false, children: /* @__PURE__ */ jsxRuntime.jsx(CodeBlockCopyButton, { node }) }),
88
- /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "overflow-x-auto p-3 pr-9 font-mono text-xs leading-relaxed text-foreground", children })
89
- ] });
104
+ return /* @__PURE__ */ jsxRuntime.jsx(
105
+ CodeBlockFrame,
106
+ {
107
+ language: node.attrs.language,
108
+ action: /* @__PURE__ */ jsxRuntime.jsx(CodeBlockCopyButton, { node }),
109
+ children
110
+ }
111
+ );
112
+ }
113
+ function StreamingCodeBlock({
114
+ language,
115
+ code,
116
+ caret
117
+ }) {
118
+ return /* @__PURE__ */ jsxRuntime.jsx(CodeBlockFrame, { language: language || null, "data-chat-open-fence": "", children: /* @__PURE__ */ jsxRuntime.jsxs("code", { children: [
119
+ code,
120
+ caret
121
+ ] }) });
90
122
  }
91
123
  function ChatCodeBlockNodeView({ node }) {
92
124
  return /* @__PURE__ */ jsxRuntime.jsx(react.NodeViewWrapper, { children: /* @__PURE__ */ jsxRuntime.jsx(ChatCodeBlock, { node, children: /* @__PURE__ */ jsxRuntime.jsx(react.NodeViewContent, { as: "code" }) }) });
@@ -95,4 +127,5 @@ exports.ChatCodeBlock = ChatCodeBlock;
95
127
  exports.ChatCodeBlockNodeView = ChatCodeBlockNodeView;
96
128
  exports.ChatMention = ChatMention;
97
129
  exports.ChatMentionNodeView = ChatMentionNodeView;
130
+ exports.StreamingCodeBlock = StreamingCodeBlock;
98
131
  //# sourceMappingURL=chat-node-views.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"chat-node-views.cjs","sources":["../../../src/chat-ui/rich-text/chat-node-views.tsx"],"sourcesContent":["import type { Node as ProseMirrorNode } from \"@tiptap/pm/model\";\nimport {\n NodeViewContent,\n NodeViewWrapper,\n type NodeViewProps,\n} from \"@tiptap/react\";\nimport { CheckIcon, CopyIcon, XIcon } from \"lucide-react\";\nimport { type ReactNode, useCallback, useEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { MentionLink } from \"../mentions/mention-chip\";\nimport {\n makeMentionUrl,\n type MentionNodeAttrs,\n} from \"../mentions/mention-extension\";\n\nexport function ChatMention({\n node,\n interactive = true,\n}: {\n node: ProseMirrorNode;\n interactive?: boolean;\n}) {\n return (\n <span\n className=\"mention-chip\"\n data-mention=\"\"\n data-type={node.attrs.type}\n data-name={node.attrs.name}\n data-id={node.attrs.id}\n data-asset-type={node.attrs.assetType}\n data-display-as-preview={String(node.attrs.displayAsPreview === true)}\n >\n <MentionLink\n href={makeMentionUrl(node.attrs as MentionNodeAttrs)}\n interactive={interactive}\n >\n {node.attrs.name}\n </MentionLink>\n </span>\n );\n}\n\nexport function ChatMentionNodeView({ node, editor }: NodeViewProps) {\n return (\n <NodeViewWrapper as=\"span\">\n <ChatMention node={node} interactive={!editor.isEditable} />\n </NodeViewWrapper>\n );\n}\n\nconst COPIED_RESET_MS = 1500;\n\n/**\n * Copies the block's source. Reads `node.textContent` at click time so a\n * still-streaming block copies what is on screen, not a stale snapshot.\n */\nfunction CodeBlockCopyButton({ node }: { node: ProseMirrorNode }) {\n const [status, setStatus] = useState<\"idle\" | \"copied\" | \"failed\">(\"idle\");\n const resetTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(\n () => () => {\n if (resetTimer.current) clearTimeout(resetTimer.current);\n },\n [],\n );\n const flash = useCallback((next: \"copied\" | \"failed\") => {\n setStatus(next);\n if (resetTimer.current) clearTimeout(resetTimer.current);\n resetTimer.current = setTimeout(() => setStatus(\"idle\"), COPIED_RESET_MS);\n }, []);\n const onCopy = useCallback(() => {\n // writeText rejects when clipboard-write is denied (permissions, an\n // embedding iframe without the allow attribute); surface that instead of\n // leaving an unhandled rejection and a button that looks like it worked.\n if (typeof navigator === \"undefined\" || !navigator.clipboard) {\n flash(\"failed\");\n return;\n }\n navigator.clipboard.writeText(node.textContent).then(\n () => flash(\"copied\"),\n () => flash(\"failed\"),\n );\n }, [node, flash]);\n const label =\n status === \"copied\" ? \"Copied\" : status === \"failed\" ? \"Copy failed\" : \"Copy code\";\n return (\n <button\n type=\"button\"\n title={label}\n aria-label={label}\n onClick={onCopy}\n className={cn(\n \"inline-flex size-6 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground\",\n \"opacity-0 transition-opacity group-hover/code-block:opacity-100 focus-visible:opacity-100\",\n status !== \"idle\" && \"opacity-100\",\n status === \"failed\" && \"text-destructive\",\n )}\n >\n {status === \"copied\" ? (\n <CheckIcon className=\"size-3.5\" />\n ) : status === \"failed\" ? (\n <XIcon className=\"size-3.5\" />\n ) : (\n <CopyIcon className=\"size-3.5\" />\n )}\n </button>\n );\n}\n\nexport function ChatCodeBlock({\n node,\n children,\n}: {\n node: ProseMirrorNode;\n children: ReactNode;\n}) {\n return (\n <div className=\"not-prose group/code-block relative my-2 overflow-hidden rounded-lg border border-border bg-muted/30\">\n {node.attrs.language && (\n <div className=\"border-b border-border bg-muted/50 px-3 py-1 text-[10px] font-medium text-muted-foreground\">\n {node.attrs.language}\n </div>\n )}\n <div className=\"absolute top-1 right-1\" contentEditable={false}>\n <CodeBlockCopyButton node={node} />\n </div>\n <pre className=\"overflow-x-auto p-3 pr-9 font-mono text-xs leading-relaxed text-foreground\">\n {children}\n </pre>\n </div>\n );\n}\n\nexport function ChatCodeBlockNodeView({ node }: NodeViewProps) {\n return (\n <NodeViewWrapper>\n <ChatCodeBlock node={node}>\n <NodeViewContent as=\"code\" />\n </ChatCodeBlock>\n </NodeViewWrapper>\n );\n}\n"],"names":["jsx","MentionLink","makeMentionUrl","NodeViewWrapper","useState","useRef","useEffect","useCallback","cn","CheckIcon","XIcon","CopyIcon","jsxs","NodeViewContent"],"mappings":"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"}
1
+ {"version":3,"file":"chat-node-views.cjs","sources":["../../../src/chat-ui/rich-text/chat-node-views.tsx"],"sourcesContent":["import type { Node as ProseMirrorNode } from \"@tiptap/pm/model\";\nimport {\n NodeViewContent,\n NodeViewWrapper,\n type NodeViewProps,\n} from \"@tiptap/react\";\nimport { CheckIcon, CopyIcon, XIcon } from \"lucide-react\";\nimport { type ReactNode, useCallback, useEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { MentionLink } from \"../mentions/mention-chip\";\nimport {\n makeMentionUrl,\n type MentionNodeAttrs,\n} from \"../mentions/mention-extension\";\n\nexport function ChatMention({\n node,\n interactive = true,\n}: {\n node: ProseMirrorNode;\n interactive?: boolean;\n}) {\n return (\n <span\n className=\"mention-chip\"\n data-mention=\"\"\n data-type={node.attrs.type}\n data-name={node.attrs.name}\n data-id={node.attrs.id}\n data-asset-type={node.attrs.assetType}\n data-display-as-preview={String(node.attrs.displayAsPreview === true)}\n >\n <MentionLink\n href={makeMentionUrl(node.attrs as MentionNodeAttrs)}\n interactive={interactive}\n >\n {node.attrs.name}\n </MentionLink>\n </span>\n );\n}\n\nexport function ChatMentionNodeView({ node, editor }: NodeViewProps) {\n return (\n <NodeViewWrapper as=\"span\">\n <ChatMention node={node} interactive={!editor.isEditable} />\n </NodeViewWrapper>\n );\n}\n\nconst COPIED_RESET_MS = 1500;\n\n/**\n * Copies the block's source. Reads `node.textContent` at click time so a\n * still-streaming block copies what is on screen, not a stale snapshot.\n */\nfunction CodeBlockCopyButton({ node }: { node: ProseMirrorNode }) {\n const [status, setStatus] = useState<\"idle\" | \"copied\" | \"failed\">(\"idle\");\n const resetTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(\n () => () => {\n if (resetTimer.current) clearTimeout(resetTimer.current);\n },\n [],\n );\n const flash = useCallback((next: \"copied\" | \"failed\") => {\n setStatus(next);\n if (resetTimer.current) clearTimeout(resetTimer.current);\n resetTimer.current = setTimeout(() => setStatus(\"idle\"), COPIED_RESET_MS);\n }, []);\n const onCopy = useCallback(() => {\n // writeText rejects when clipboard-write is denied (permissions, an\n // embedding iframe without the allow attribute); surface that instead of\n // leaving an unhandled rejection and a button that looks like it worked.\n if (typeof navigator === \"undefined\" || !navigator.clipboard) {\n flash(\"failed\");\n return;\n }\n navigator.clipboard.writeText(node.textContent).then(\n () => flash(\"copied\"),\n () => flash(\"failed\"),\n );\n }, [node, flash]);\n const label =\n status === \"copied\" ? \"Copied\" : status === \"failed\" ? \"Copy failed\" : \"Copy code\";\n return (\n <button\n type=\"button\"\n title={label}\n aria-label={label}\n onClick={onCopy}\n className={cn(\n \"inline-flex size-6 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground\",\n \"opacity-0 transition-opacity group-hover/code-block:opacity-100 focus-visible:opacity-100\",\n status !== \"idle\" && \"opacity-100\",\n status === \"failed\" && \"text-destructive\",\n )}\n >\n {status === \"copied\" ? (\n <CheckIcon className=\"size-3.5\" />\n ) : status === \"failed\" ? (\n <XIcon className=\"size-3.5\" />\n ) : (\n <CopyIcon className=\"size-3.5\" />\n )}\n </button>\n );\n}\n\n/** The frame both code renderings share: language label, an optional corner action, the `<pre>`. */\nfunction CodeBlockFrame({\n language,\n action,\n children,\n ...attributes\n}: {\n language: string | null | undefined;\n action?: ReactNode;\n children: ReactNode;\n \"data-chat-open-fence\"?: string;\n}) {\n return (\n <div\n className=\"not-prose group/code-block relative my-2 overflow-hidden rounded-lg border border-border bg-muted/30\"\n {...attributes}\n >\n {language && (\n <div className=\"border-b border-border bg-muted/50 px-3 py-1 text-[10px] font-medium text-muted-foreground\">\n {language}\n </div>\n )}\n {action && (\n <div className=\"absolute top-1 right-1\" contentEditable={false}>\n {action}\n </div>\n )}\n <pre className=\"overflow-x-auto p-3 pr-9 font-mono text-xs leading-relaxed text-foreground\">\n {children}\n </pre>\n </div>\n );\n}\n\nexport function ChatCodeBlock({\n node,\n children,\n}: {\n node: ProseMirrorNode;\n children: ReactNode;\n}) {\n return (\n <CodeBlockFrame\n language={node.attrs.language}\n action={<CodeBlockCopyButton node={node} />}\n >\n {children}\n </CodeBlockFrame>\n );\n}\n\n/**\n * A fenced block whose closing fence has not arrived: the same frame with the\n * code as plain text and no copy action (there is no settled source to copy\n * yet). Anything that decorates code — highlighting, a language pick — belongs\n * after the fence closes, when the block parses into a `codeBlock` node.\n */\nexport function StreamingCodeBlock({\n language,\n code,\n caret,\n}: {\n language: string;\n code: string;\n caret?: ReactNode;\n}) {\n return (\n <CodeBlockFrame language={language || null} data-chat-open-fence=\"\">\n <code>\n {code}\n {caret}\n </code>\n </CodeBlockFrame>\n );\n}\n\nexport function ChatCodeBlockNodeView({ node }: NodeViewProps) {\n return (\n <NodeViewWrapper>\n <ChatCodeBlock node={node}>\n <NodeViewContent as=\"code\" />\n </ChatCodeBlock>\n </NodeViewWrapper>\n );\n}\n"],"names":["jsx","MentionLink","makeMentionUrl","NodeViewWrapper","useState","useRef","useEffect","useCallback","cn","CheckIcon","XIcon","CopyIcon","jsxs","NodeViewContent"],"mappings":"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"}
@@ -10,4 +10,15 @@ export declare function ChatCodeBlock({ node, children, }: {
10
10
  node: ProseMirrorNode;
11
11
  children: ReactNode;
12
12
  }): import("react/jsx-runtime").JSX.Element;
13
+ /**
14
+ * A fenced block whose closing fence has not arrived: the same frame with the
15
+ * code as plain text and no copy action (there is no settled source to copy
16
+ * yet). Anything that decorates code — highlighting, a language pick — belongs
17
+ * after the fence closes, when the block parses into a `codeBlock` node.
18
+ */
19
+ export declare function StreamingCodeBlock({ language, code, caret, }: {
20
+ language: string;
21
+ code: string;
22
+ caret?: ReactNode;
23
+ }): import("react/jsx-runtime").JSX.Element;
13
24
  export declare function ChatCodeBlockNodeView({ node }: NodeViewProps): import("react/jsx-runtime").JSX.Element;
@@ -76,15 +76,47 @@ function CodeBlockCopyButton({ node }) {
76
76
  }
77
77
  );
78
78
  }
79
+ function CodeBlockFrame({
80
+ language,
81
+ action,
82
+ children,
83
+ ...attributes
84
+ }) {
85
+ return /* @__PURE__ */ jsxs(
86
+ "div",
87
+ {
88
+ className: "not-prose group/code-block relative my-2 overflow-hidden rounded-lg border border-border bg-muted/30",
89
+ ...attributes,
90
+ children: [
91
+ language && /* @__PURE__ */ jsx("div", { className: "border-b border-border bg-muted/50 px-3 py-1 text-[10px] font-medium text-muted-foreground", children: language }),
92
+ action && /* @__PURE__ */ jsx("div", { className: "absolute top-1 right-1", contentEditable: false, children: action }),
93
+ /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto p-3 pr-9 font-mono text-xs leading-relaxed text-foreground", children })
94
+ ]
95
+ }
96
+ );
97
+ }
79
98
  function ChatCodeBlock({
80
99
  node,
81
100
  children
82
101
  }) {
83
- return /* @__PURE__ */ jsxs("div", { className: "not-prose group/code-block relative my-2 overflow-hidden rounded-lg border border-border bg-muted/30", children: [
84
- node.attrs.language && /* @__PURE__ */ jsx("div", { className: "border-b border-border bg-muted/50 px-3 py-1 text-[10px] font-medium text-muted-foreground", children: node.attrs.language }),
85
- /* @__PURE__ */ jsx("div", { className: "absolute top-1 right-1", contentEditable: false, children: /* @__PURE__ */ jsx(CodeBlockCopyButton, { node }) }),
86
- /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto p-3 pr-9 font-mono text-xs leading-relaxed text-foreground", children })
87
- ] });
102
+ return /* @__PURE__ */ jsx(
103
+ CodeBlockFrame,
104
+ {
105
+ language: node.attrs.language,
106
+ action: /* @__PURE__ */ jsx(CodeBlockCopyButton, { node }),
107
+ children
108
+ }
109
+ );
110
+ }
111
+ function StreamingCodeBlock({
112
+ language,
113
+ code,
114
+ caret
115
+ }) {
116
+ return /* @__PURE__ */ jsx(CodeBlockFrame, { language: language || null, "data-chat-open-fence": "", children: /* @__PURE__ */ jsxs("code", { children: [
117
+ code,
118
+ caret
119
+ ] }) });
88
120
  }
89
121
  function ChatCodeBlockNodeView({ node }) {
90
122
  return /* @__PURE__ */ jsx(NodeViewWrapper, { children: /* @__PURE__ */ jsx(ChatCodeBlock, { node, children: /* @__PURE__ */ jsx(NodeViewContent, { as: "code" }) }) });
@@ -93,6 +125,7 @@ export {
93
125
  ChatCodeBlock,
94
126
  ChatCodeBlockNodeView,
95
127
  ChatMention,
96
- ChatMentionNodeView
128
+ ChatMentionNodeView,
129
+ StreamingCodeBlock
97
130
  };
98
131
  //# sourceMappingURL=chat-node-views.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chat-node-views.js","sources":["../../../src/chat-ui/rich-text/chat-node-views.tsx"],"sourcesContent":["import type { Node as ProseMirrorNode } from \"@tiptap/pm/model\";\nimport {\n NodeViewContent,\n NodeViewWrapper,\n type NodeViewProps,\n} from \"@tiptap/react\";\nimport { CheckIcon, CopyIcon, XIcon } from \"lucide-react\";\nimport { type ReactNode, useCallback, useEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { MentionLink } from \"../mentions/mention-chip\";\nimport {\n makeMentionUrl,\n type MentionNodeAttrs,\n} from \"../mentions/mention-extension\";\n\nexport function ChatMention({\n node,\n interactive = true,\n}: {\n node: ProseMirrorNode;\n interactive?: boolean;\n}) {\n return (\n <span\n className=\"mention-chip\"\n data-mention=\"\"\n data-type={node.attrs.type}\n data-name={node.attrs.name}\n data-id={node.attrs.id}\n data-asset-type={node.attrs.assetType}\n data-display-as-preview={String(node.attrs.displayAsPreview === true)}\n >\n <MentionLink\n href={makeMentionUrl(node.attrs as MentionNodeAttrs)}\n interactive={interactive}\n >\n {node.attrs.name}\n </MentionLink>\n </span>\n );\n}\n\nexport function ChatMentionNodeView({ node, editor }: NodeViewProps) {\n return (\n <NodeViewWrapper as=\"span\">\n <ChatMention node={node} interactive={!editor.isEditable} />\n </NodeViewWrapper>\n );\n}\n\nconst COPIED_RESET_MS = 1500;\n\n/**\n * Copies the block's source. Reads `node.textContent` at click time so a\n * still-streaming block copies what is on screen, not a stale snapshot.\n */\nfunction CodeBlockCopyButton({ node }: { node: ProseMirrorNode }) {\n const [status, setStatus] = useState<\"idle\" | \"copied\" | \"failed\">(\"idle\");\n const resetTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(\n () => () => {\n if (resetTimer.current) clearTimeout(resetTimer.current);\n },\n [],\n );\n const flash = useCallback((next: \"copied\" | \"failed\") => {\n setStatus(next);\n if (resetTimer.current) clearTimeout(resetTimer.current);\n resetTimer.current = setTimeout(() => setStatus(\"idle\"), COPIED_RESET_MS);\n }, []);\n const onCopy = useCallback(() => {\n // writeText rejects when clipboard-write is denied (permissions, an\n // embedding iframe without the allow attribute); surface that instead of\n // leaving an unhandled rejection and a button that looks like it worked.\n if (typeof navigator === \"undefined\" || !navigator.clipboard) {\n flash(\"failed\");\n return;\n }\n navigator.clipboard.writeText(node.textContent).then(\n () => flash(\"copied\"),\n () => flash(\"failed\"),\n );\n }, [node, flash]);\n const label =\n status === \"copied\" ? \"Copied\" : status === \"failed\" ? \"Copy failed\" : \"Copy code\";\n return (\n <button\n type=\"button\"\n title={label}\n aria-label={label}\n onClick={onCopy}\n className={cn(\n \"inline-flex size-6 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground\",\n \"opacity-0 transition-opacity group-hover/code-block:opacity-100 focus-visible:opacity-100\",\n status !== \"idle\" && \"opacity-100\",\n status === \"failed\" && \"text-destructive\",\n )}\n >\n {status === \"copied\" ? (\n <CheckIcon className=\"size-3.5\" />\n ) : status === \"failed\" ? (\n <XIcon className=\"size-3.5\" />\n ) : (\n <CopyIcon className=\"size-3.5\" />\n )}\n </button>\n );\n}\n\nexport function ChatCodeBlock({\n node,\n children,\n}: {\n node: ProseMirrorNode;\n children: ReactNode;\n}) {\n return (\n <div className=\"not-prose group/code-block relative my-2 overflow-hidden rounded-lg border border-border bg-muted/30\">\n {node.attrs.language && (\n <div className=\"border-b border-border bg-muted/50 px-3 py-1 text-[10px] font-medium text-muted-foreground\">\n {node.attrs.language}\n </div>\n )}\n <div className=\"absolute top-1 right-1\" contentEditable={false}>\n <CodeBlockCopyButton node={node} />\n </div>\n <pre className=\"overflow-x-auto p-3 pr-9 font-mono text-xs leading-relaxed text-foreground\">\n {children}\n </pre>\n </div>\n );\n}\n\nexport function ChatCodeBlockNodeView({ node }: NodeViewProps) {\n return (\n <NodeViewWrapper>\n <ChatCodeBlock node={node}>\n <NodeViewContent as=\"code\" />\n </ChatCodeBlock>\n </NodeViewWrapper>\n );\n}\n"],"names":[],"mappings":";;;;;;;AAeO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,cAAc;AAChB,GAGG;AACD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAU;AAAA,MACV,gBAAa;AAAA,MACb,aAAW,KAAK,MAAM;AAAA,MACtB,aAAW,KAAK,MAAM;AAAA,MACtB,WAAS,KAAK,MAAM;AAAA,MACpB,mBAAiB,KAAK,MAAM;AAAA,MAC5B,2BAAyB,OAAO,KAAK,MAAM,qBAAqB,IAAI;AAAA,MAEpE,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAM,eAAe,KAAK,KAAyB;AAAA,UACnD;AAAA,UAEC,eAAK,MAAM;AAAA,QAAA;AAAA,MAAA;AAAA,IACd;AAAA,EAAA;AAGN;AAEO,SAAS,oBAAoB,EAAE,MAAM,UAAyB;AACnE,SACE,oBAAC,iBAAA,EAAgB,IAAG,QAClB,UAAA,oBAAC,aAAA,EAAY,MAAY,aAAa,CAAC,OAAO,WAAA,CAAY,EAAA,CAC5D;AAEJ;AAEA,MAAM,kBAAkB;AAMxB,SAAS,oBAAoB,EAAE,QAAmC;AAChE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAuC,MAAM;AACzE,QAAM,aAAa,OAA6C,IAAI;AACpE;AAAA,IACE,MAAM,MAAM;AACV,UAAI,WAAW,QAAS,cAAa,WAAW,OAAO;AAAA,IACzD;AAAA,IACA,CAAA;AAAA,EAAC;AAEH,QAAM,QAAQ,YAAY,CAAC,SAA8B;AACvD,cAAU,IAAI;AACd,QAAI,WAAW,QAAS,cAAa,WAAW,OAAO;AACvD,eAAW,UAAU,WAAW,MAAM,UAAU,MAAM,GAAG,eAAe;AAAA,EAC1E,GAAG,CAAA,CAAE;AACL,QAAM,SAAS,YAAY,MAAM;AAI/B,QAAI,OAAO,cAAc,eAAe,CAAC,UAAU,WAAW;AAC5D,YAAM,QAAQ;AACd;AAAA,IACF;AACA,cAAU,UAAU,UAAU,KAAK,WAAW,EAAE;AAAA,MAC9C,MAAM,MAAM,QAAQ;AAAA,MACpB,MAAM,MAAM,QAAQ;AAAA,IAAA;AAAA,EAExB,GAAG,CAAC,MAAM,KAAK,CAAC;AAChB,QAAM,QACJ,WAAW,WAAW,WAAW,WAAW,WAAW,gBAAgB;AACzE,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,OAAO;AAAA,MACP,cAAY;AAAA,MACZ,SAAS;AAAA,MACT,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA,WAAW,UAAU;AAAA,QACrB,WAAW,YAAY;AAAA,MAAA;AAAA,MAGxB,qBAAW,WACV,oBAAC,WAAA,EAAU,WAAU,YAAW,IAC9B,WAAW,WACb,oBAAC,SAAM,WAAU,WAAA,CAAW,IAE5B,oBAAC,UAAA,EAAS,WAAU,WAAA,CAAW;AAAA,IAAA;AAAA,EAAA;AAIvC;AAEO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AACF,GAGG;AACD,SACE,qBAAC,OAAA,EAAI,WAAU,wGACZ,UAAA;AAAA,IAAA,KAAK,MAAM,YACV,oBAAC,OAAA,EAAI,WAAU,8FACZ,UAAA,KAAK,MAAM,SAAA,CACd;AAAA,IAEF,oBAAC,SAAI,WAAU,0BAAyB,iBAAiB,OACvD,UAAA,oBAAC,qBAAA,EAAoB,KAAA,CAAY,EAAA,CACnC;AAAA,IACA,oBAAC,OAAA,EAAI,WAAU,8EACZ,SAAA,CACH;AAAA,EAAA,GACF;AAEJ;AAEO,SAAS,sBAAsB,EAAE,QAAuB;AAC7D,SACE,oBAAC,iBAAA,EACC,UAAA,oBAAC,eAAA,EAAc,MACb,8BAAC,iBAAA,EAAgB,IAAG,OAAA,CAAO,EAAA,CAC7B,GACF;AAEJ;"}
1
+ {"version":3,"file":"chat-node-views.js","sources":["../../../src/chat-ui/rich-text/chat-node-views.tsx"],"sourcesContent":["import type { Node as ProseMirrorNode } from \"@tiptap/pm/model\";\nimport {\n NodeViewContent,\n NodeViewWrapper,\n type NodeViewProps,\n} from \"@tiptap/react\";\nimport { CheckIcon, CopyIcon, XIcon } from \"lucide-react\";\nimport { type ReactNode, useCallback, useEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { MentionLink } from \"../mentions/mention-chip\";\nimport {\n makeMentionUrl,\n type MentionNodeAttrs,\n} from \"../mentions/mention-extension\";\n\nexport function ChatMention({\n node,\n interactive = true,\n}: {\n node: ProseMirrorNode;\n interactive?: boolean;\n}) {\n return (\n <span\n className=\"mention-chip\"\n data-mention=\"\"\n data-type={node.attrs.type}\n data-name={node.attrs.name}\n data-id={node.attrs.id}\n data-asset-type={node.attrs.assetType}\n data-display-as-preview={String(node.attrs.displayAsPreview === true)}\n >\n <MentionLink\n href={makeMentionUrl(node.attrs as MentionNodeAttrs)}\n interactive={interactive}\n >\n {node.attrs.name}\n </MentionLink>\n </span>\n );\n}\n\nexport function ChatMentionNodeView({ node, editor }: NodeViewProps) {\n return (\n <NodeViewWrapper as=\"span\">\n <ChatMention node={node} interactive={!editor.isEditable} />\n </NodeViewWrapper>\n );\n}\n\nconst COPIED_RESET_MS = 1500;\n\n/**\n * Copies the block's source. Reads `node.textContent` at click time so a\n * still-streaming block copies what is on screen, not a stale snapshot.\n */\nfunction CodeBlockCopyButton({ node }: { node: ProseMirrorNode }) {\n const [status, setStatus] = useState<\"idle\" | \"copied\" | \"failed\">(\"idle\");\n const resetTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(\n () => () => {\n if (resetTimer.current) clearTimeout(resetTimer.current);\n },\n [],\n );\n const flash = useCallback((next: \"copied\" | \"failed\") => {\n setStatus(next);\n if (resetTimer.current) clearTimeout(resetTimer.current);\n resetTimer.current = setTimeout(() => setStatus(\"idle\"), COPIED_RESET_MS);\n }, []);\n const onCopy = useCallback(() => {\n // writeText rejects when clipboard-write is denied (permissions, an\n // embedding iframe without the allow attribute); surface that instead of\n // leaving an unhandled rejection and a button that looks like it worked.\n if (typeof navigator === \"undefined\" || !navigator.clipboard) {\n flash(\"failed\");\n return;\n }\n navigator.clipboard.writeText(node.textContent).then(\n () => flash(\"copied\"),\n () => flash(\"failed\"),\n );\n }, [node, flash]);\n const label =\n status === \"copied\" ? \"Copied\" : status === \"failed\" ? \"Copy failed\" : \"Copy code\";\n return (\n <button\n type=\"button\"\n title={label}\n aria-label={label}\n onClick={onCopy}\n className={cn(\n \"inline-flex size-6 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground\",\n \"opacity-0 transition-opacity group-hover/code-block:opacity-100 focus-visible:opacity-100\",\n status !== \"idle\" && \"opacity-100\",\n status === \"failed\" && \"text-destructive\",\n )}\n >\n {status === \"copied\" ? (\n <CheckIcon className=\"size-3.5\" />\n ) : status === \"failed\" ? (\n <XIcon className=\"size-3.5\" />\n ) : (\n <CopyIcon className=\"size-3.5\" />\n )}\n </button>\n );\n}\n\n/** The frame both code renderings share: language label, an optional corner action, the `<pre>`. */\nfunction CodeBlockFrame({\n language,\n action,\n children,\n ...attributes\n}: {\n language: string | null | undefined;\n action?: ReactNode;\n children: ReactNode;\n \"data-chat-open-fence\"?: string;\n}) {\n return (\n <div\n className=\"not-prose group/code-block relative my-2 overflow-hidden rounded-lg border border-border bg-muted/30\"\n {...attributes}\n >\n {language && (\n <div className=\"border-b border-border bg-muted/50 px-3 py-1 text-[10px] font-medium text-muted-foreground\">\n {language}\n </div>\n )}\n {action && (\n <div className=\"absolute top-1 right-1\" contentEditable={false}>\n {action}\n </div>\n )}\n <pre className=\"overflow-x-auto p-3 pr-9 font-mono text-xs leading-relaxed text-foreground\">\n {children}\n </pre>\n </div>\n );\n}\n\nexport function ChatCodeBlock({\n node,\n children,\n}: {\n node: ProseMirrorNode;\n children: ReactNode;\n}) {\n return (\n <CodeBlockFrame\n language={node.attrs.language}\n action={<CodeBlockCopyButton node={node} />}\n >\n {children}\n </CodeBlockFrame>\n );\n}\n\n/**\n * A fenced block whose closing fence has not arrived: the same frame with the\n * code as plain text and no copy action (there is no settled source to copy\n * yet). Anything that decorates code — highlighting, a language pick — belongs\n * after the fence closes, when the block parses into a `codeBlock` node.\n */\nexport function StreamingCodeBlock({\n language,\n code,\n caret,\n}: {\n language: string;\n code: string;\n caret?: ReactNode;\n}) {\n return (\n <CodeBlockFrame language={language || null} data-chat-open-fence=\"\">\n <code>\n {code}\n {caret}\n </code>\n </CodeBlockFrame>\n );\n}\n\nexport function ChatCodeBlockNodeView({ node }: NodeViewProps) {\n return (\n <NodeViewWrapper>\n <ChatCodeBlock node={node}>\n <NodeViewContent as=\"code\" />\n </ChatCodeBlock>\n </NodeViewWrapper>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -16,21 +16,36 @@ const TaskItem = require("@tiptap/extension-task-item");
16
16
  const tiptapMarkdown = require("tiptap-markdown");
17
17
  const mentionExtension = require("../mentions/mention-extension.cjs");
18
18
  const chatNodeViews = require("./chat-node-views.cjs");
19
+ const citationAnchors = require("./citation-anchors.cjs");
19
20
  const ChatMentionExtension = mentionExtension.MentionExtension.extend({
20
21
  addNodeView: () => react.ReactNodeViewRenderer(chatNodeViews.ChatMentionNodeView, { as: "span" })
21
22
  });
22
23
  const ChatCodeBlockExtension = CodeBlock.extend({
23
24
  addNodeView: () => react.ReactNodeViewRenderer(chatNodeViews.ChatCodeBlockNodeView)
24
25
  });
26
+ const ChatLinkExtension = Link.extend({
27
+ addAttributes() {
28
+ var _a;
29
+ return {
30
+ ...(_a = this.parent) == null ? void 0 : _a.call(this),
31
+ excerpt: {
32
+ default: null,
33
+ parseHTML: (element) => element.getAttribute("data-excerpt"),
34
+ renderHTML: (attributes) => typeof attributes.excerpt === "string" && attributes.excerpt ? { "data-excerpt": attributes.excerpt } : {}
35
+ }
36
+ };
37
+ }
38
+ });
25
39
  function createChatTextExtensions() {
26
40
  return [
27
41
  StarterKit.configure({ codeBlock: false }),
28
42
  ChatCodeBlockExtension,
29
- Link.configure({
43
+ ChatLinkExtension.configure({
30
44
  openOnClick: false,
31
45
  autolink: true,
32
46
  linkOnPaste: true,
33
- protocols: ["athena-point"]
47
+ protocols: ["athena-point"],
48
+ isAllowedUri: (url) => citationAnchors.isAllowedChatHref(url)
34
49
  }),
35
50
  Image.configure({ inline: true }),
36
51
  Table.configure({ resizable: false }),
@@ -61,11 +76,13 @@ function parseChatText(markdown) {
61
76
  }));
62
77
  const html = parserEditor.storage.markdown.parser.parse(markdown);
63
78
  const body = new window.DOMParser().parseFromString(html, "text/html").body;
79
+ citationAnchors.normalizeCitationAnchors(body);
64
80
  return model.DOMParser.fromSchema(parserEditor.schema).parse(body);
65
81
  }
66
- const CHAT_TEXT_CLASS = "aui-md prose prose-sm max-w-none min-w-0 break-words text-sm leading-snug text-foreground prose-p:my-1.5 prose-p:leading-snug prose-headings:mt-3 prose-headings:mb-1.5 prose-headings:font-semibold prose-h1:text-sm prose-h2:text-sm prose-h3:text-xs prose-ul:my-1.5 prose-ol:my-1.5 prose-li:my-0.5 prose-blockquote:border-l-2 prose-blockquote:border-border prose-blockquote:text-muted-foreground prose-code:rounded prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:text-xs prose-code:font-mono prose-code:before:content-none prose-code:after:content-none prose-pre:my-2 prose-pre:overflow-x-auto prose-pre:rounded-lg prose-pre:border prose-pre:border-border prose-pre:bg-muted/30 prose-pre:p-3 prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-strong:font-semibold prose-hr:border-border [&>:first-child]:mt-0 [&>:last-child]:mb-0 [&_table]:block [&_table]:overflow-x-auto [&_td]:border [&_td]:border-border [&_td]:px-2 [&_th]:border [&_th]:border-border [&_th]:px-2 [&_ul[data-type=taskList]]:list-none [&_ul[data-type=taskList]]:pl-0 [&_li[data-type=taskItem]]:flex [&_li[data-type=taskItem]]:gap-2";
82
+ const CHAT_TEXT_CLASS = "aui-md prose prose-sm max-w-none min-w-0 break-words text-sm leading-snug text-foreground prose-p:my-1.5 prose-p:leading-snug prose-headings:mt-3 prose-headings:mb-1.5 prose-headings:font-semibold prose-h1:text-sm prose-h2:text-sm prose-h3:text-xs prose-ul:my-1.5 prose-ol:my-1.5 prose-li:my-0.5 prose-blockquote:border-l-2 prose-blockquote:border-border prose-blockquote:text-muted-foreground prose-code:rounded prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:text-xs prose-code:font-mono prose-code:before:content-none prose-code:after:content-none prose-pre:my-2 prose-pre:overflow-x-auto prose-pre:rounded-lg prose-pre:border prose-pre:border-border prose-pre:bg-muted/30 prose-pre:p-3 prose-a:text-primary prose-a:underline prose-a:decoration-primary/40 prose-a:underline-offset-2 hover:prose-a:decoration-primary prose-strong:font-semibold prose-hr:border-border [&>:first-child]:mt-0 [&>:last-child]:mb-0 [&_table]:block [&_table]:overflow-x-auto [&_td]:border [&_td]:border-border [&_td]:px-2 [&_th]:border [&_th]:border-border [&_th]:px-2 [&_ul[data-type=taskList]]:list-none [&_ul[data-type=taskList]]:pl-0 [&_li[data-type=taskItem]]:flex [&_li[data-type=taskItem]]:gap-2";
67
83
  exports.CHAT_TEXT_CLASS = CHAT_TEXT_CLASS;
68
84
  exports.ChatCodeBlockExtension = ChatCodeBlockExtension;
85
+ exports.ChatLinkExtension = ChatLinkExtension;
69
86
  exports.createChatTextExtensions = createChatTextExtensions;
70
87
  exports.parseChatText = parseChatText;
71
88
  //# sourceMappingURL=chat-text-schema.cjs.map