@djangocfg/ui-tools 2.1.404 → 2.1.408

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 (336) hide show
  1. package/README.md +9 -11
  2. package/dist/file-icon/index.cjs +449 -61
  3. package/dist/file-icon/index.cjs.map +1 -1
  4. package/dist/file-icon/index.d.cts +56 -18
  5. package/dist/file-icon/index.d.ts +56 -18
  6. package/dist/file-icon/index.mjs +448 -62
  7. package/dist/file-icon/index.mjs.map +1 -1
  8. package/dist/tree/index.cjs +49 -22
  9. package/dist/tree/index.cjs.map +1 -1
  10. package/dist/tree/index.d.cts +9 -3
  11. package/dist/tree/index.d.ts +9 -3
  12. package/dist/tree/index.mjs +49 -22
  13. package/dist/tree/index.mjs.map +1 -1
  14. package/dist/{types-B_zhyAqR.d.cts → types-eEu8SeiQ.d.cts} +4 -0
  15. package/dist/{types-B_zhyAqR.d.ts → types-eEu8SeiQ.d.ts} +4 -0
  16. package/package.json +13 -16
  17. package/src/components/FloatingToolbar/index.tsx +37 -3
  18. package/src/lib/page-snapshot/__tests__/capture-integration.test.ts +85 -0
  19. package/src/lib/page-snapshot/__tests__/engine.test.ts +36 -0
  20. package/src/lib/page-snapshot/__tests__/redaction-integration.test.ts +99 -0
  21. package/src/lib/page-snapshot/__tests__/tokens.test.ts +17 -0
  22. package/src/lib/page-snapshot/capture/__tests__/budget.test.ts +49 -0
  23. package/src/lib/page-snapshot/capture/__tests__/chrome-filter.test.ts +47 -0
  24. package/src/lib/page-snapshot/capture/__tests__/fold.test.ts +66 -0
  25. package/src/lib/page-snapshot/capture/__tests__/scope.test.ts +74 -0
  26. package/src/lib/page-snapshot/capture/__tests__/walk.test.ts +129 -0
  27. package/src/lib/page-snapshot/capture/accessible-name.ts +73 -0
  28. package/src/lib/page-snapshot/capture/budget.ts +95 -0
  29. package/src/lib/page-snapshot/capture/chrome-filter.ts +81 -0
  30. package/src/lib/page-snapshot/capture/classify.ts +111 -0
  31. package/src/lib/page-snapshot/capture/dom-utils.ts +111 -0
  32. package/src/lib/page-snapshot/capture/fold.ts +96 -0
  33. package/src/lib/page-snapshot/capture/scope.ts +169 -0
  34. package/src/lib/page-snapshot/capture/walk.ts +250 -0
  35. package/src/lib/page-snapshot/cst/__tests__/serialize.test.ts +50 -0
  36. package/src/lib/page-snapshot/cst/directives.ts +47 -0
  37. package/src/lib/page-snapshot/cst/payload.ts +50 -0
  38. package/src/lib/page-snapshot/cst/serialize.ts +84 -0
  39. package/src/lib/page-snapshot/cst/types.ts +115 -0
  40. package/src/lib/page-snapshot/engine.ts +176 -0
  41. package/src/lib/page-snapshot/index.ts +93 -0
  42. package/src/lib/page-snapshot/react/PageSnapshotChip.tsx +72 -0
  43. package/src/lib/page-snapshot/react/PageSnapshotPreview.tsx +78 -0
  44. package/src/lib/page-snapshot/react/__tests__/PageSnapshotChip.test.tsx +54 -0
  45. package/src/lib/page-snapshot/react/__tests__/provider.test.tsx +103 -0
  46. package/src/lib/page-snapshot/react/__tests__/use-page-snapshot-toggle.test.tsx +62 -0
  47. package/src/lib/page-snapshot/react/provider.tsx +162 -0
  48. package/src/lib/page-snapshot/react/use-page-snapshot-toggle.ts +47 -0
  49. package/src/lib/page-snapshot/react/use-page-snapshot.ts +67 -0
  50. package/src/lib/page-snapshot/redaction/__tests__/audit.test.ts +25 -0
  51. package/src/lib/page-snapshot/redaction/__tests__/heuristics.test.ts +73 -0
  52. package/src/lib/page-snapshot/redaction/__tests__/luhn.test.ts +26 -0
  53. package/src/lib/page-snapshot/redaction/__tests__/patterns.test.ts +60 -0
  54. package/src/lib/page-snapshot/redaction/audit.ts +58 -0
  55. package/src/lib/page-snapshot/redaction/heuristics.ts +75 -0
  56. package/src/lib/page-snapshot/redaction/index.ts +75 -0
  57. package/src/lib/page-snapshot/redaction/luhn.ts +25 -0
  58. package/src/lib/page-snapshot/redaction/patterns.ts +111 -0
  59. package/src/lib/page-snapshot/refs/__tests__/registry.test.ts +24 -0
  60. package/src/lib/page-snapshot/refs/registry.ts +46 -0
  61. package/src/lib/page-snapshot/staleness/__tests__/hash.test.ts +34 -0
  62. package/src/lib/page-snapshot/staleness/hash.ts +20 -0
  63. package/src/lib/page-snapshot/tokens.ts +15 -0
  64. package/src/tools/AudioPlayer/context/PlayerProvider.tsx +13 -14
  65. package/src/tools/AudioPlayer/hooks/useAudioElementEvents.ts +55 -6
  66. package/src/tools/AudioPlayer/lazy.tsx +13 -27
  67. package/src/tools/AudioPlayer/parts/Meta/TimeDisplay.tsx +2 -5
  68. package/src/tools/Chat/README.md +267 -39
  69. package/src/tools/Chat/composer/Composer.tsx +471 -0
  70. package/src/tools/Chat/composer/ComposerActionBar.tsx +65 -0
  71. package/src/tools/Chat/composer/ComposerBanner.tsx +128 -0
  72. package/src/tools/Chat/composer/ComposerButton.tsx +64 -0
  73. package/src/tools/Chat/composer/ComposerFooter.tsx +90 -0
  74. package/src/tools/Chat/composer/ComposerMenuButton.tsx +62 -0
  75. package/src/tools/Chat/composer/ComposerModelPicker.tsx +104 -0
  76. package/src/tools/Chat/composer/ComposerRichTextarea.tsx +88 -0
  77. package/src/tools/Chat/composer/ComposerToolPill.tsx +95 -0
  78. package/src/tools/Chat/composer/index.ts +45 -0
  79. package/src/tools/Chat/composer/size-context.tsx +26 -0
  80. package/src/tools/Chat/composer/types.ts +143 -0
  81. package/src/tools/Chat/composer/useComposerActions.tsx +164 -0
  82. package/src/tools/Chat/context/ChatProvider.tsx +54 -3
  83. package/src/tools/Chat/core/__tests__/metadata.test.ts +69 -0
  84. package/src/tools/Chat/core/index.ts +23 -1
  85. package/src/tools/Chat/core/markdown.ts +1 -1
  86. package/src/tools/Chat/core/metadata.ts +47 -0
  87. package/src/tools/Chat/core/payload-dispatch.ts +1 -1
  88. package/src/tools/Chat/core/transport/http.ts +71 -32
  89. package/src/tools/Chat/core/transport/sse.ts +18 -10
  90. package/src/tools/Chat/highlight/HighlightOverlay.tsx +101 -0
  91. package/src/tools/Chat/highlight/README.md +103 -0
  92. package/src/tools/Chat/highlight/SpotlightCanvas.tsx +153 -0
  93. package/src/tools/Chat/highlight/__tests__/HighlightOverlay.test.tsx +112 -0
  94. package/src/tools/Chat/highlight/__tests__/resolveRef.test.ts +55 -0
  95. package/src/tools/Chat/highlight/index.ts +21 -0
  96. package/src/tools/Chat/highlight/resolveRef.ts +42 -0
  97. package/src/tools/Chat/highlight/types.ts +49 -0
  98. package/src/tools/Chat/highlight/useHighlightTargets.ts +128 -0
  99. package/src/tools/Chat/hooks/index.ts +0 -5
  100. package/src/tools/Chat/hooks/useAutoFocusOnStreamEnd.ts +28 -47
  101. package/src/tools/Chat/hooks/useChat.ts +47 -14
  102. package/src/tools/Chat/hooks/useChatComposer.ts +2 -2
  103. package/src/tools/Chat/hooks/useChatLayout.ts +1 -1
  104. package/src/tools/Chat/hooks/useStreamEndFocus.ts +54 -0
  105. package/src/tools/Chat/index.ts +25 -219
  106. package/src/tools/Chat/launcher/ChatDock.tsx +1 -1
  107. package/src/tools/Chat/launcher/ChatLauncher.tsx +1 -1
  108. package/src/tools/Chat/launcher/{ChatHeader.tsx → header/ChatHeader.tsx} +24 -11
  109. package/src/tools/Chat/launcher/{ChatHeaderActionButton.tsx → header/ChatHeaderActionButton.tsx} +34 -3
  110. package/src/tools/Chat/launcher/{ChatHeaderLanguageButton.tsx → header/ChatHeaderLanguageButton.tsx} +2 -2
  111. package/src/tools/Chat/launcher/{ChatHeaderModeToggle.tsx → header/ChatHeaderModeToggle.tsx} +1 -1
  112. package/src/tools/Chat/launcher/{ChatHeaderResetButton.tsx → header/ChatHeaderResetButton.tsx} +2 -1
  113. package/src/tools/Chat/launcher/{HeaderSlots.tsx → header/HeaderSlots.tsx} +3 -3
  114. package/src/tools/Chat/launcher/header/index.ts +26 -0
  115. package/src/tools/Chat/launcher/index.ts +3 -10
  116. package/src/tools/Chat/lazy.tsx +38 -284
  117. package/src/tools/Chat/{components → messages}/MessageBubble.tsx +58 -5
  118. package/src/tools/Chat/{components → messages}/MessageList.tsx +8 -25
  119. package/src/tools/Chat/messages/blocks/MessageBlocks.tsx +131 -0
  120. package/src/tools/Chat/messages/blocks/builtin.tsx +91 -0
  121. package/src/tools/Chat/messages/blocks/index.ts +12 -0
  122. package/src/tools/Chat/messages/blocks/registry.tsx +42 -0
  123. package/src/tools/Chat/messages/blocks/renderers/AudioBlock.tsx +20 -0
  124. package/src/tools/Chat/messages/blocks/renderers/CodeBlock.tsx +19 -0
  125. package/src/tools/Chat/messages/blocks/renderers/GalleryBlock.tsx +26 -0
  126. package/src/tools/Chat/messages/blocks/renderers/ImageBlock.tsx +27 -0
  127. package/src/tools/Chat/messages/blocks/renderers/JsonBlock.tsx +12 -0
  128. package/src/tools/Chat/messages/blocks/renderers/LottieBlock.tsx +11 -0
  129. package/src/tools/Chat/messages/blocks/renderers/MapBlock.tsx +36 -0
  130. package/src/tools/Chat/messages/blocks/renderers/MermaidBlock.tsx +11 -0
  131. package/src/tools/Chat/messages/blocks/renderers/VideoBlock.tsx +24 -0
  132. package/src/tools/Chat/messages/blocks/renderers/types.ts +8 -0
  133. package/src/tools/Chat/{components → messages}/index.ts +11 -5
  134. package/src/tools/Chat/public.ts +212 -0
  135. package/src/tools/Chat/shell/ChatRoot.tsx +326 -0
  136. package/src/tools/Chat/{components → shell}/EmptyState.tsx +4 -2
  137. package/src/tools/Chat/shell/index.ts +15 -0
  138. package/src/tools/Chat/types/block.ts +120 -0
  139. package/src/tools/Chat/types/config.ts +0 -5
  140. package/src/tools/Chat/types/index.ts +17 -0
  141. package/src/tools/Chat/types/message.ts +3 -0
  142. package/src/tools/Chat/utils/index.ts +4 -0
  143. package/src/tools/CodeEditor/README.md +4 -6
  144. package/src/tools/CodeEditor/components/DiffEditor.tsx +48 -13
  145. package/src/tools/CodeEditor/components/Editor.tsx +96 -44
  146. package/src/tools/CodeEditor/context/EditorProvider.tsx +34 -17
  147. package/src/tools/CodeEditor/hooks/useEditorTheme.ts +92 -99
  148. package/src/tools/CodeEditor/hooks/useMonaco.ts +37 -22
  149. package/src/tools/CodeEditor/lazy.tsx +6 -0
  150. package/src/tools/CodeEditor/lib/index.ts +1 -1
  151. package/src/tools/CodeEditor/lib/themes.ts +3 -39
  152. package/src/tools/CronScheduler/CronScheduler.client.tsx +230 -61
  153. package/src/tools/CronScheduler/components/CustomInput.tsx +21 -4
  154. package/src/tools/CronScheduler/components/DayChips.tsx +13 -11
  155. package/src/tools/CronScheduler/components/MonthDayGrid.tsx +4 -4
  156. package/src/tools/CronScheduler/components/SchedulePreview.tsx +7 -3
  157. package/src/tools/CronScheduler/components/TimeSelector.tsx +1 -1
  158. package/src/tools/CronScheduler/index.tsx +1 -1
  159. package/src/tools/CronScheduler/types/index.ts +8 -3
  160. package/src/tools/CronScheduler/utils/cron-humanize.ts +61 -16
  161. package/src/tools/CronScheduler/utils/cron-parser.ts +13 -4
  162. package/src/tools/FileIcon/FileIcon.tsx +24 -39
  163. package/src/tools/FileIcon/get-file-icon.ts +73 -0
  164. package/src/tools/FileIcon/icons/icon-data.ts +399 -0
  165. package/src/tools/FileIcon/index.ts +4 -0
  166. package/src/tools/FileIcon/loader.ts +17 -35
  167. package/src/tools/FileIcon/specialFolders.ts +18 -0
  168. package/src/tools/Gallery/components/lightbox/GalleryLightbox.tsx +112 -35
  169. package/src/tools/Gallery/components/media/GalleryVideo.tsx +21 -2
  170. package/src/tools/Gallery/components/preview/GalleryCarousel.tsx +11 -1
  171. package/src/tools/Gallery/hooks/usePreloadImages.ts +54 -7
  172. package/src/tools/ImageViewer/components/ImageInfo.tsx +12 -1
  173. package/src/tools/ImageViewer/components/ImageToolbar.tsx +51 -43
  174. package/src/tools/ImageViewer/components/ImageViewer.tsx +106 -26
  175. package/src/tools/ImageViewer/hooks/useImageLoading.ts +13 -0
  176. package/src/tools/ImageViewer/utils/constants.ts +3 -0
  177. package/src/tools/ImageViewer/utils/index.ts +1 -0
  178. package/src/tools/JsonForm/JsonSchemaForm.tsx +4 -1
  179. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +5 -3
  180. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +7 -4
  181. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +3 -1
  182. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +23 -3
  183. package/src/tools/JsonForm/widgets/ColorWidget.tsx +20 -12
  184. package/src/tools/JsonForm/widgets/NumberWidget.tsx +14 -9
  185. package/src/tools/JsonForm/widgets/RadioWidget.tsx +78 -0
  186. package/src/tools/JsonForm/widgets/SelectWidget.tsx +1 -0
  187. package/src/tools/JsonForm/widgets/SliderWidget.tsx +7 -4
  188. package/src/tools/JsonForm/widgets/TextWidget.tsx +41 -17
  189. package/src/tools/JsonForm/widgets/index.ts +1 -0
  190. package/src/tools/JsonTree/components/JsonContent.tsx +115 -40
  191. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +177 -72
  192. package/src/tools/LottiePlayer/index.tsx +14 -4
  193. package/src/tools/LottiePlayer/lazy.tsx +11 -3
  194. package/src/tools/LottiePlayer/types.ts +31 -1
  195. package/src/tools/LottiePlayer/useLottie.ts +32 -9
  196. package/src/tools/LottiePlayer/usePrefersReducedMotion.ts +46 -0
  197. package/src/tools/Map/components/LayerSwitcher.tsx +54 -21
  198. package/src/tools/Map/components/MapCluster.tsx +28 -21
  199. package/src/tools/Map/components/MapContainer.tsx +11 -4
  200. package/src/tools/Map/components/MapLegend.tsx +46 -15
  201. package/src/tools/Map/components/MapMarker.tsx +31 -2
  202. package/src/tools/Map/hooks/useMapEvents.ts +64 -105
  203. package/src/tools/MarkdownEditor/MarkdownEditor.tsx +61 -6
  204. package/src/tools/MarkdownEditor/MentionList.tsx +37 -4
  205. package/src/tools/MarkdownEditor/createMentionSuggestion.ts +11 -0
  206. package/src/tools/MarkdownEditor/lazy.tsx +32 -7
  207. package/src/tools/MarkdownEditor/styles.css +13 -0
  208. package/src/tools/MarkdownMessage/CodeBlock.tsx +40 -17
  209. package/src/tools/MarkdownMessage/MarkdownMessage.tsx +26 -6
  210. package/src/tools/MarkdownMessage/components.tsx +22 -9
  211. package/src/tools/MarkdownMessage/types.ts +24 -1
  212. package/src/tools/Mermaid/Mermaid.client.tsx +27 -5
  213. package/src/tools/Mermaid/components/MermaidErrorPanel.tsx +31 -0
  214. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +14 -17
  215. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +264 -168
  216. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +76 -10
  217. package/src/tools/Mermaid/index.tsx +6 -0
  218. package/src/tools/Mermaid/utils/mermaid-helpers.ts +141 -18
  219. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +11 -1
  220. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +49 -20
  221. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +7 -0
  222. package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +7 -4
  223. package/src/tools/OpenapiViewer/constants.ts +3 -0
  224. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +73 -11
  225. package/src/tools/OpenapiViewer/utils/schemaExport.ts +26 -6
  226. package/src/tools/PrettyCode/PrettyCode.client.tsx +23 -16
  227. package/src/tools/PrettyCode/lazy.tsx +1 -1
  228. package/src/tools/SpeechRecognition/README.md +1 -1
  229. package/src/tools/SpeechRecognition/__tests__/language.test.ts +9 -3
  230. package/src/tools/SpeechRecognition/components/RecordingPulse.tsx +59 -0
  231. package/src/tools/SpeechRecognition/components/index.ts +2 -0
  232. package/src/tools/SpeechRecognition/core/engine/external.ts +24 -7
  233. package/src/tools/SpeechRecognition/core/language.ts +23 -6
  234. package/src/tools/SpeechRecognition/hooks/usePushToTalk.ts +36 -5
  235. package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +18 -11
  236. package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +94 -26
  237. package/src/tools/SpeechRecognition/widgets/index.ts +1 -1
  238. package/src/tools/Tree/README.md +4 -8
  239. package/src/tools/Tree/TreeRoot.tsx +22 -10
  240. package/src/tools/Tree/components/TreeContent.tsx +24 -4
  241. package/src/tools/Tree/components/TreeLabel.tsx +8 -2
  242. package/src/tools/Tree/components/TreeRow.tsx +16 -6
  243. package/src/tools/Tree/data/flatten.ts +10 -4
  244. package/src/tools/Tree/types.ts +4 -0
  245. package/src/tools/Uploader/components/UploadAddButton.tsx +29 -6
  246. package/src/tools/Uploader/components/UploadDropzone.tsx +63 -7
  247. package/src/tools/Uploader/components/UploadPageDropOverlay.tsx +19 -5
  248. package/src/tools/Uploader/components/UploadPreviewItem.tsx +47 -17
  249. package/src/tools/Uploader/components/UploadPreviewList.tsx +24 -12
  250. package/src/tools/Uploader/utils/formatters.ts +8 -3
  251. package/src/tools/VideoPlayer/README.md +87 -230
  252. package/src/tools/VideoPlayer/VideoPlayer.tsx +82 -0
  253. package/src/tools/VideoPlayer/canvas/canvas-dispatcher.tsx +34 -0
  254. package/src/tools/VideoPlayer/canvas/hls-canvas.tsx +39 -0
  255. package/src/tools/VideoPlayer/canvas/iframe-canvas.tsx +33 -0
  256. package/src/tools/VideoPlayer/canvas/index.ts +12 -0
  257. package/src/tools/VideoPlayer/canvas/jsx-augmentation.ts +47 -0
  258. package/src/tools/VideoPlayer/canvas/native-canvas.tsx +38 -0
  259. package/src/tools/VideoPlayer/canvas/vimeo-canvas.tsx +40 -0
  260. package/src/tools/VideoPlayer/canvas/youtube-canvas.tsx +78 -0
  261. package/src/tools/VideoPlayer/index.ts +51 -65
  262. package/src/tools/VideoPlayer/lazy.tsx +11 -54
  263. package/src/tools/VideoPlayer/parts/controls-bar.tsx +35 -0
  264. package/src/tools/VideoPlayer/parts/fullscreen.tsx +19 -0
  265. package/src/tools/VideoPlayer/parts/index.ts +15 -0
  266. package/src/tools/VideoPlayer/parts/pip.tsx +19 -0
  267. package/src/tools/VideoPlayer/parts/play-button.tsx +19 -0
  268. package/src/tools/VideoPlayer/parts/playback-rate.tsx +31 -0
  269. package/src/tools/VideoPlayer/parts/poster.tsx +3 -0
  270. package/src/tools/VideoPlayer/parts/seek-bar.tsx +26 -0
  271. package/src/tools/VideoPlayer/parts/volume.tsx +32 -0
  272. package/src/tools/VideoPlayer/styles/video-player.css +141 -0
  273. package/src/tools/VideoPlayer/types.ts +82 -0
  274. package/src/tools/VideoPlayer/utils/parse-embed-url.ts +70 -0
  275. package/src/tools/VideoPlayer/utils/vimeo-id.ts +24 -0
  276. package/src/tools/VideoPlayer/utils/youtube-id.ts +64 -0
  277. package/src/tools/index.ts +37 -29
  278. package/src/tools/Chat/components/AudioToggle.tsx +0 -78
  279. package/src/tools/Chat/components/ChatRoot.tsx +0 -305
  280. package/src/tools/Chat/components/Composer.tsx +0 -216
  281. package/src/tools/Chat/hooks/useChatScroll.ts +0 -145
  282. package/src/tools/Chat/types.ts +0 -9
  283. package/src/tools/JsonTree/components/JsonToolbar.tsx +0 -95
  284. package/src/tools/JsonTree/hooks/useElementCorner.ts +0 -84
  285. package/src/tools/JsonTree/hooks/useNavbarHeight.ts +0 -83
  286. package/src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts +0 -121
  287. package/src/tools/Tour/README.md +0 -373
  288. package/src/tools/Tour/components/Tour.tsx +0 -12
  289. package/src/tools/Tour/components/TourContent.tsx +0 -171
  290. package/src/tools/Tour/components/TourNavigation.tsx +0 -77
  291. package/src/tools/Tour/components/TourProgress.tsx +0 -88
  292. package/src/tools/Tour/components/TourSpotlight.tsx +0 -199
  293. package/src/tools/Tour/components/index.ts +0 -5
  294. package/src/tools/Tour/context/TourContext.ts +0 -19
  295. package/src/tools/Tour/context/TourProvider.tsx +0 -292
  296. package/src/tools/Tour/context/index.ts +0 -2
  297. package/src/tools/Tour/hooks/index.ts +0 -3
  298. package/src/tools/Tour/hooks/useKeyboardNavigation.ts +0 -59
  299. package/src/tools/Tour/hooks/useStepTarget.ts +0 -121
  300. package/src/tools/Tour/hooks/useTour.ts +0 -42
  301. package/src/tools/Tour/index.ts +0 -38
  302. package/src/tools/Tour/types/index.ts +0 -224
  303. package/src/tools/Tour/utils/dom.ts +0 -98
  304. package/src/tools/Tour/utils/index.ts +0 -3
  305. package/src/tools/Tour/utils/logger.ts +0 -3
  306. package/src/tools/Tour/utils/scrollIntoView.ts +0 -24
  307. package/src/tools/VideoPlayer/components/VideoControls.tsx +0 -138
  308. package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +0 -172
  309. package/src/tools/VideoPlayer/components/VideoPlayer.tsx +0 -201
  310. package/src/tools/VideoPlayer/components/index.ts +0 -14
  311. package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +0 -52
  312. package/src/tools/VideoPlayer/context/index.ts +0 -8
  313. package/src/tools/VideoPlayer/hooks/index.ts +0 -12
  314. package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +0 -71
  315. package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +0 -117
  316. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +0 -284
  317. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +0 -505
  318. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +0 -397
  319. package/src/tools/VideoPlayer/providers/index.ts +0 -8
  320. package/src/tools/VideoPlayer/types/index.ts +0 -38
  321. package/src/tools/VideoPlayer/types/player.ts +0 -116
  322. package/src/tools/VideoPlayer/types/provider.ts +0 -93
  323. package/src/tools/VideoPlayer/types/sources.ts +0 -97
  324. package/src/tools/VideoPlayer/utils/debug.ts +0 -14
  325. package/src/tools/VideoPlayer/utils/fileSource.ts +0 -78
  326. package/src/tools/VideoPlayer/utils/index.ts +0 -12
  327. package/src/tools/VideoPlayer/utils/resolvers.ts +0 -75
  328. /package/src/tools/Chat/{config.ts → constants.ts} +0 -0
  329. /package/src/tools/Chat/launcher/{ChatHeaderAudioToggle.tsx → header/ChatHeaderAudioToggle.tsx} +0 -0
  330. /package/src/tools/Chat/{components → messages}/Attachments.tsx +0 -0
  331. /package/src/tools/Chat/{components → messages}/JumpToLatest.tsx +0 -0
  332. /package/src/tools/Chat/{components → messages}/MessageActions.tsx +0 -0
  333. /package/src/tools/Chat/{components → messages}/Sources.tsx +0 -0
  334. /package/src/tools/Chat/{components → messages}/StreamingIndicator.tsx +0 -0
  335. /package/src/tools/Chat/{components → messages}/ToolCalls.tsx +0 -0
  336. /package/src/tools/Chat/{components → shell}/ErrorBanner.tsx +0 -0
@@ -0,0 +1,143 @@
1
+ 'use client';
2
+
3
+ import type { ComponentType, ReactNode } from 'react';
4
+
5
+ import type { UseChatComposerReturn } from '../hooks/useChatComposer';
6
+
7
+ /** Composer visual size — shared across the `<Composer>` API. */
8
+ export type ComposerSize = 'sm' | 'md' | 'lg';
9
+
10
+ /**
11
+ * Spaciousness of the input surface — orthogonal to `size`:
12
+ * - `compact` — default; embedded composer (FAB panel, dock).
13
+ * - `full` — full-page chat; roomier surface, bigger textarea + padding.
14
+ */
15
+ export type ComposerAppearance = 'compact' | 'full';
16
+
17
+ /**
18
+ * Geometry of the input surface:
19
+ * - `stacked` — textarea on top, action bar pinned below, one bordered surface.
20
+ * - `inline` — single horizontal row (compact; `size="sm"` defaults to this).
21
+ */
22
+ export type ComposerLayout = 'stacked' | 'inline';
23
+
24
+ /** Conditional visibility of an action, evaluated against composer state. */
25
+ export type ComposerActionVisibility =
26
+ | 'streaming'
27
+ | 'empty'
28
+ | 'hasText'
29
+ | 'disabled';
30
+
31
+ /**
32
+ * Declarative descriptor for one action-bar button (Tier A). The Composer
33
+ * renders these into consistent `<ComposerButton>`s — size, aria and tooltip
34
+ * are handled centrally so hosts cannot misalign anything.
35
+ */
36
+ export interface ComposerAction {
37
+ /** Stable key. */
38
+ id: string;
39
+ /** Lucide icon element. */
40
+ icon: ReactNode;
41
+ /** Used as `aria-label` and tooltip — required for a11y. */
42
+ label: string;
43
+ onClick: () => void;
44
+ disabled?: boolean;
45
+ /** Toggle state → emits `aria-pressed`. */
46
+ pressed?: boolean;
47
+ /** `send` — theme-inverting filled circle (ChatGPT send button). */
48
+ variant?: 'ghost' | 'secondary' | 'primary' | 'send';
49
+ /** Hide the action when the composer is in this state. */
50
+ hideWhen?: ComposerActionVisibility;
51
+ /** Sort weight within its cluster (ascending). Built-ins reserve high values. */
52
+ order?: number;
53
+ }
54
+
55
+ /**
56
+ * Tier A — declarative slot arrays + raw-node escape hatches. Hosts supply
57
+ * action descriptors; the Composer owns the rendering.
58
+ */
59
+ export interface ComposerSlots {
60
+ /** Bottom-left cluster. Built-in attach prepends here when enabled. */
61
+ actionsStart?: ComposerAction[];
62
+ /** Bottom-right cluster. Built-in mic/send append here. */
63
+ actionsEnd?: ComposerAction[];
64
+ /** Full-width row above the textarea (reply banner, custom tray). */
65
+ blockStart?: ReactNode;
66
+ /** Raw nodes placed left of the textarea in the `inline` layout. */
67
+ inlineStart?: ReactNode;
68
+ /** Raw nodes placed right of the textarea in the `inline` layout. */
69
+ inlineEnd?: ReactNode;
70
+ }
71
+
72
+ // ── Tier B — full slot replacement ─────────────────────────────────────────
73
+
74
+ export interface SendButtonProps {
75
+ /** True while a reply is streaming — render the stop affordance. */
76
+ streaming: boolean;
77
+ disabled: boolean;
78
+ size: ComposerSize;
79
+ onSend: () => void;
80
+ onCancel?: () => void;
81
+ }
82
+
83
+ export interface AttachButtonProps {
84
+ disabled: boolean;
85
+ size: ComposerSize;
86
+ onClick: () => void;
87
+ }
88
+
89
+ export interface ComposerTextareaProps {
90
+ composer: UseChatComposerReturn;
91
+ placeholder: string;
92
+ disabled: boolean;
93
+ size: ComposerSize;
94
+ className?: string;
95
+ }
96
+
97
+ export interface ActionBarProps {
98
+ /** Already filtered + sorted by `useComposerActions`. */
99
+ actionsStart: ComposerAction[];
100
+ actionsEnd: ComposerAction[];
101
+ size: ComposerSize;
102
+ layout: ComposerLayout;
103
+ inlineStart?: ReactNode;
104
+ inlineEnd?: ReactNode;
105
+ }
106
+
107
+ /** Tier B — swap a primitive entirely. Each is optional. */
108
+ export interface ComposerSlotComponents {
109
+ SendButton?: ComponentType<SendButtonProps>;
110
+ AttachButton?: ComponentType<AttachButtonProps>;
111
+ Textarea?: ComponentType<ComposerTextareaProps>;
112
+ /** Replace the whole `blockEnd` action-bar row. */
113
+ ActionBar?: ComponentType<ActionBarProps>;
114
+ }
115
+
116
+ /** Per-slot prop overrides forwarded to the built-in primitives. */
117
+ export interface ComposerSlotProps {
118
+ send?: Partial<SendButtonProps>;
119
+ attach?: Partial<AttachButtonProps>;
120
+ textarea?: { className?: string };
121
+ actionBar?: { className?: string };
122
+ }
123
+
124
+ // ── Footer ─────────────────────────────────────────────────────────────────
125
+
126
+ /** Props for the strip below the input surface. */
127
+ export interface ComposerFooterProps {
128
+ start?: ReactNode;
129
+ center?: ReactNode;
130
+ end?: ReactNode;
131
+ /** Auto char counter — reads `composer.value` + `maxLength`. */
132
+ showCounter?: boolean;
133
+ /** Auto keyboard-shortcut hint — reads `submitOn`. @default false */
134
+ showHint?: boolean;
135
+ /** Live value, for the counter. */
136
+ value?: string;
137
+ /** Character cap, for the counter. */
138
+ maxLength?: number;
139
+ /** Submit binding, for the hint. */
140
+ submitOn?: 'enter' | 'cmd+enter';
141
+ size?: ComposerSize;
142
+ className?: string;
143
+ }
@@ -0,0 +1,164 @@
1
+ 'use client';
2
+
3
+ import { useMemo } from 'react';
4
+ import { ArrowUp, Paperclip, Square } from 'lucide-react';
5
+
6
+ import type { UseChatComposerReturn } from '../hooks/useChatComposer';
7
+ import type { ComposerAction } from './types';
8
+
9
+ /** `order` values reserved for built-in actions so host extras land between. */
10
+ const ORDER = {
11
+ attach: 100,
12
+ hostStart: 200,
13
+ hostEnd: 200,
14
+ mic: 800,
15
+ send: 900,
16
+ } as const;
17
+
18
+ export interface UseComposerActionsParams {
19
+ composer: UseChatComposerReturn;
20
+ isStreaming: boolean;
21
+ isDisabled: boolean;
22
+ /** Built-in attach action wiring. */
23
+ showAttachmentButton?: boolean;
24
+ onPickFiles?: () => void;
25
+ attachLabel?: string;
26
+ /** Host-supplied declarative clusters. */
27
+ actionsStart?: ComposerAction[];
28
+ actionsEnd?: ComposerAction[];
29
+ /** Send / stop wiring. */
30
+ onSend: () => void;
31
+ onCancel?: () => void;
32
+ sendLabel?: string;
33
+ stopLabel?: string;
34
+ /** Telegram-style mic↔send swap. When true (default), mic and send never
35
+ * show together — mic when the draft is empty, send once there is text. */
36
+ micSendSwap?: boolean;
37
+ }
38
+
39
+ export interface ComposerActionClusters {
40
+ actionsStart: ComposerAction[];
41
+ actionsEnd: ComposerAction[];
42
+ }
43
+
44
+ /**
45
+ * Merges built-in send/stop/attach descriptors with host-provided arrays,
46
+ * applies `hideWhen` against live composer state, resolves the mic↔send
47
+ * swap, and sorts each cluster by `order`.
48
+ */
49
+ export function useComposerActions(params: UseComposerActionsParams): ComposerActionClusters {
50
+ const {
51
+ composer,
52
+ isStreaming,
53
+ isDisabled,
54
+ showAttachmentButton,
55
+ onPickFiles,
56
+ attachLabel = 'Attach files',
57
+ actionsStart,
58
+ actionsEnd,
59
+ onSend,
60
+ onCancel,
61
+ sendLabel = 'Send',
62
+ stopLabel = 'Stop',
63
+ micSendSwap = true,
64
+ } = params;
65
+
66
+ const hasText = composer.canSubmit;
67
+ const canSubmit = composer.canSubmit;
68
+
69
+ return useMemo(() => {
70
+ const start: ComposerAction[] = [];
71
+ const end: ComposerAction[] = [];
72
+
73
+ if (showAttachmentButton) {
74
+ start.push({
75
+ id: 'attach',
76
+ icon: <Paperclip aria-hidden />,
77
+ label: attachLabel,
78
+ onClick: () => onPickFiles?.(),
79
+ disabled: isDisabled,
80
+ variant: 'ghost',
81
+ order: ORDER.attach,
82
+ });
83
+ }
84
+
85
+ for (const a of actionsStart ?? []) {
86
+ start.push({ order: ORDER.hostStart, ...a });
87
+ }
88
+ for (const a of actionsEnd ?? []) {
89
+ end.push({ order: ORDER.hostEnd, ...a });
90
+ }
91
+
92
+ // Send / stop. While streaming → stop; otherwise → send. Both are
93
+ // round, theme-inverting buttons (ChatGPT/Gemini style).
94
+ if (isStreaming) {
95
+ end.push({
96
+ id: 'stop',
97
+ icon: <Square aria-hidden />,
98
+ label: stopLabel,
99
+ onClick: () => onCancel?.(),
100
+ variant: 'send',
101
+ order: ORDER.send,
102
+ });
103
+ } else {
104
+ end.push({
105
+ id: 'send',
106
+ icon: <ArrowUp aria-hidden />,
107
+ label: sendLabel,
108
+ onClick: onSend,
109
+ disabled: !canSubmit,
110
+ variant: 'send',
111
+ order: ORDER.send,
112
+ });
113
+ }
114
+
115
+ // Apply `hideWhen` against the live composer state.
116
+ const visible = (a: ComposerAction): boolean => {
117
+ switch (a.hideWhen) {
118
+ case 'streaming':
119
+ return !isStreaming;
120
+ case 'empty':
121
+ return hasText;
122
+ case 'hasText':
123
+ return !hasText;
124
+ case 'disabled':
125
+ return !isDisabled;
126
+ default:
127
+ return true;
128
+ }
129
+ };
130
+
131
+ let filteredEnd = end.filter(visible);
132
+ const filteredStart = start.filter(visible);
133
+
134
+ // Mic↔send swap: when there is text and a mic action exists, drop the
135
+ // mic so only send shows. The host marks its mic with `id: 'mic'`
136
+ // (or `hideWhen: 'hasText'`); this is a safety net for the common id.
137
+ if (micSendSwap && hasText) {
138
+ filteredEnd = filteredEnd.filter((a) => a.id !== 'mic');
139
+ }
140
+
141
+ const byOrder = (a: ComposerAction, b: ComposerAction) =>
142
+ (a.order ?? 0) - (b.order ?? 0);
143
+
144
+ return {
145
+ actionsStart: filteredStart.sort(byOrder),
146
+ actionsEnd: filteredEnd.sort(byOrder),
147
+ };
148
+ }, [
149
+ showAttachmentButton,
150
+ attachLabel,
151
+ onPickFiles,
152
+ isDisabled,
153
+ actionsStart,
154
+ actionsEnd,
155
+ isStreaming,
156
+ stopLabel,
157
+ onCancel,
158
+ sendLabel,
159
+ onSend,
160
+ canSubmit,
161
+ hasText,
162
+ micSendSwap,
163
+ ]);
164
+ }
@@ -13,10 +13,12 @@ import {
13
13
 
14
14
  import type { ChatConfig, ChatLabels, ChatTransport } from '../types';
15
15
  import { DEFAULT_LABELS } from '../types';
16
+ import type { BlockRegistry } from '../messages/blocks';
16
17
 
17
18
  import { useChat, type UseChatReturn } from '../hooks/useChat';
18
19
  import { useChatLayout, type UseChatLayoutReturn } from '../hooks/useChatLayout';
19
20
  import { useChatAudio } from '../hooks/useChatAudio';
21
+ import { useStreamEndFocus } from '../hooks/useStreamEndFocus';
20
22
  import type { ChatAudioConfig, UseChatAudioReturn } from '../core/audio/types';
21
23
 
22
24
  /** Imperative handle a composer (built-in or custom) registers so
@@ -60,6 +62,9 @@ export interface ChatContextValue extends UseChatReturn {
60
62
  * null until any composer has mounted. Plan64 follow-up. */
61
63
  composer: ComposerHandle | null;
62
64
  registerComposer: (handle: ComposerHandle | null) => void;
65
+ /** Registry of `kind` → renderer for `message.blocks`. `null` when the
66
+ * host wired none — `<MessageBubble>` then uses `BUILTIN_BLOCK_REGISTRY`. */
67
+ blockRegistry: BlockRegistry | null;
63
68
  }
64
69
 
65
70
  const Ctx = createContext<ChatContextValue | null>(null);
@@ -116,12 +121,31 @@ export interface ChatProviderProps {
116
121
  audio?: ChatAudioConfig;
117
122
  /** Enable verbose dev logging via consola. Defaults to `isDev`. */
118
123
  debug?: boolean;
124
+ /** Registry of `kind` → renderer for `message.blocks`. Stored in
125
+ * context so nested `<ChatRoot>` / `<MessageBubble>` pick it up. */
126
+ blockRegistry?: BlockRegistry;
119
127
  /**
120
128
  * Rewrite outgoing message content before it hits the transport.
121
129
  * History bubble keeps the original; useful for stripping rich-
122
130
  * display chips so the LLM sees plain text. See `useChat`.
123
131
  */
124
132
  onBeforeSend?: (content: string) => string | Promise<string>;
133
+ /**
134
+ * Contribute extra transport metadata, computed fresh per send.
135
+ * Merged over the static metadata right before `transport.stream/send`.
136
+ * Keeps `Chat` decoupled from any specific metadata source — the host
137
+ * supplies the function (e.g. the page-context snapshot from
138
+ * `usePageSnapshot().getChatMetadata`).
139
+ */
140
+ getDynamicMetadata?: () => Record<string, unknown> | undefined;
141
+ /**
142
+ * Re-focus the registered composer on the streaming → idle edge —
143
+ * the standard chat UX (type → send → read → keep typing without
144
+ * reaching for the mouse). Default `true`. Works for every usage
145
+ * pattern (`ChatRoot`, hand-rolled `Composer` layout, headless) as
146
+ * long as a composer is registered. Set `false` to opt out.
147
+ */
148
+ autoFocusOnStreamEnd?: boolean;
125
149
  children?: ReactNode;
126
150
  }
127
151
 
@@ -133,7 +157,10 @@ export function ChatProvider({
133
157
  streaming,
134
158
  audio,
135
159
  debug,
160
+ blockRegistry,
136
161
  onBeforeSend,
162
+ getDynamicMetadata,
163
+ autoFocusOnStreamEnd = true,
137
164
  children,
138
165
  }: ChatProviderProps) {
139
166
  const audioApi = useChatAudio(audio ?? {});
@@ -155,7 +182,7 @@ export function ChatProvider({
155
182
  streaming,
156
183
  debug,
157
184
  metadata: {
158
- locale: config.locale ?? config.prefs?.locale,
185
+ locale: config.prefs?.locale,
159
186
  slug: config.slug,
160
187
  },
161
188
  userPersona: config.user,
@@ -164,6 +191,7 @@ export function ChatProvider({
164
191
  onStreamStart,
165
192
  onError,
166
193
  onBeforeSend,
194
+ getDynamicMetadata,
167
195
  });
168
196
  const layout = useChatLayout({ defaultMode: 'embedded' });
169
197
 
@@ -206,9 +234,32 @@ export function ChatProvider({
206
234
  setComposer(handle);
207
235
  }, []);
208
236
 
237
+ // Re-focus the composer on the streaming → idle edge. Lives here (not
238
+ // in `ChatRoot`) so it works for *every* usage pattern — `ChatRoot`,
239
+ // a hand-rolled `ChatProvider` + `Composer` layout, or headless — as
240
+ // long as a composer registered its handle.
241
+ const composerRef = useRef<ComposerHandle | null>(composer);
242
+ composerRef.current = composer;
243
+ useStreamEndFocus({
244
+ isStreaming: chat.isStreaming,
245
+ enabled: autoFocusOnStreamEnd,
246
+ delayMs: 0,
247
+ resolveTarget: () => composerRef.current,
248
+ });
249
+
209
250
  const value = useMemo<ChatContextValue>(
210
- () => ({ ...chat, layout, config, labels, audio: audioApi, hasAudio, composer, registerComposer }),
211
- [chat, layout, config, labels, audioApi, hasAudio, composer, registerComposer],
251
+ () => ({
252
+ ...chat,
253
+ layout,
254
+ config,
255
+ labels,
256
+ audio: audioApi,
257
+ hasAudio,
258
+ composer,
259
+ registerComposer,
260
+ blockRegistry: blockRegistry ?? null,
261
+ }),
262
+ [chat, layout, config, labels, audioApi, hasAudio, composer, registerComposer, blockRegistry],
212
263
  );
213
264
 
214
265
  return (
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { resolveSendMetadata, safeDynamicMetadata } from '../metadata';
4
+
5
+ describe('resolveSendMetadata', () => {
6
+ it('passes static metadata through when there is no contributor', () => {
7
+ const result = resolveSendMetadata({ locale: 'en' }, undefined);
8
+ expect(result).toEqual({ locale: 'en' });
9
+ });
10
+
11
+ it('returns undefined when both static and dynamic are absent', () => {
12
+ expect(resolveSendMetadata(undefined, undefined)).toBeUndefined();
13
+ });
14
+
15
+ it('merges dynamic metadata over static metadata', () => {
16
+ const result = resolveSendMetadata(
17
+ { locale: 'en', slug: 'proj' },
18
+ () => ({ pageContext: { url: '/x' } }),
19
+ );
20
+ expect(result).toEqual({
21
+ locale: 'en',
22
+ slug: 'proj',
23
+ pageContext: { url: '/x' },
24
+ });
25
+ });
26
+
27
+ it('lets dynamic keys win on collision', () => {
28
+ const result = resolveSendMetadata(
29
+ { locale: 'en' },
30
+ () => ({ locale: 'fr' }),
31
+ );
32
+ expect(result).toEqual({ locale: 'fr' });
33
+ });
34
+
35
+ it('passes static through when the contributor returns undefined', () => {
36
+ const result = resolveSendMetadata({ locale: 'en' }, () => undefined);
37
+ expect(result).toEqual({ locale: 'en' });
38
+ });
39
+
40
+ it('does not break the send when the contributor throws', () => {
41
+ const result = resolveSendMetadata({ locale: 'en' }, () => {
42
+ throw new Error('capture failed');
43
+ });
44
+ // Falls back to static metadata — the send must still proceed.
45
+ expect(result).toEqual({ locale: 'en' });
46
+ });
47
+
48
+ it('produces a fresh object each call (no shared mutation)', () => {
49
+ const getDynamic = () => ({ n: 1 });
50
+ const a = resolveSendMetadata({ locale: 'en' }, getDynamic);
51
+ const b = resolveSendMetadata({ locale: 'en' }, getDynamic);
52
+ expect(a).not.toBe(b);
53
+ expect(a).toEqual(b);
54
+ });
55
+ });
56
+
57
+ describe('safeDynamicMetadata', () => {
58
+ it('returns the contributor result', () => {
59
+ expect(safeDynamicMetadata(() => ({ a: 1 }))).toEqual({ a: 1 });
60
+ });
61
+
62
+ it('swallows a throw and returns undefined', () => {
63
+ expect(
64
+ safeDynamicMetadata(() => {
65
+ throw new Error('boom');
66
+ }),
67
+ ).toBeUndefined();
68
+ });
69
+ });
@@ -2,4 +2,26 @@ export { reducer, initialState, type ChatState, type ChatAction } from './reduce
2
2
  export { createId } from './ids';
3
3
  export { createTokenBuffer, type TokenBuffer } from './markdown';
4
4
  export { resolvePersona, deriveInitials } from './persona';
5
- export * from './transport';
5
+ export {
6
+ createHttpTransport,
7
+ createMockTransport,
8
+ parseSSE,
9
+ TransportError,
10
+ createPydanticAIChatTransport,
11
+ createToolIdQueue,
12
+ mapPydanticAIEvent,
13
+ createPydanticAISSEMap,
14
+ type HttpTransportConfig,
15
+ type MockTransportOptions,
16
+ type ParseSSEOptions,
17
+ type ChatTransport,
18
+ type ChatStreamEvent,
19
+ type CreateSessionOptions,
20
+ type SessionInfo,
21
+ type HistoryPage,
22
+ type StreamOptions,
23
+ type SendOptions,
24
+ type PydanticAIChatTransportOpts,
25
+ type PydanticAIEvent,
26
+ type ToolIdQueue,
27
+ } from './transport';
@@ -4,7 +4,7 @@
4
4
  * on fast streams.
5
5
  */
6
6
 
7
- import { LIMITS } from '../config';
7
+ import { LIMITS } from '../constants';
8
8
 
9
9
  export interface TokenBuffer {
10
10
  /** Append a delta. Returns immediately. */
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Transport-metadata assembly.
3
+ *
4
+ * A chat send carries two kinds of metadata:
5
+ * - static — fixed per session (locale, slug), set once in config.
6
+ * - dynamic — recomputed for every send by an optional contributor.
7
+ *
8
+ * The dynamic contributor is what enables "capture-on-submit" features
9
+ * (e.g. a fresh page-context snapshot per message): it runs synchronously
10
+ * at send time, and its result is merged over the static metadata.
11
+ */
12
+
13
+ /** A function that produces extra metadata, freshly, at send time. */
14
+ export type DynamicMetadataFn = () => Record<string, unknown> | undefined;
15
+
16
+ /**
17
+ * Invoke a dynamic-metadata contributor, swallowing any throw.
18
+ *
19
+ * A contributor that fails (e.g. a snapshot capture errors) must never
20
+ * break a chat send — the send proceeds with the static metadata only.
21
+ */
22
+ export function safeDynamicMetadata(
23
+ fn: DynamicMetadataFn,
24
+ ): Record<string, unknown> | undefined {
25
+ try {
26
+ return fn();
27
+ } catch {
28
+ return undefined;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Merge the static metadata with a per-send dynamic contributor.
34
+ *
35
+ * Called at each `transport.stream/send` so the contributor runs fresh.
36
+ * Dynamic keys win over static keys on collision. When there is no
37
+ * contributor (or it returns nothing) the static metadata passes
38
+ * through unchanged.
39
+ */
40
+ export function resolveSendMetadata(
41
+ staticMetadata: Record<string, unknown> | undefined,
42
+ getDynamic: DynamicMetadataFn | undefined,
43
+ ): Record<string, unknown> | undefined {
44
+ const dynamic = getDynamic ? safeDynamicMetadata(getDynamic) : undefined;
45
+ if (!dynamic) return staticMetadata;
46
+ return { ...staticMetadata, ...dynamic };
47
+ }
@@ -8,7 +8,7 @@
8
8
  import type { ReactNode } from 'react';
9
9
 
10
10
  import type { ChatToolCall } from '../types';
11
- import type { ToolPayloadKind } from '../components/ToolCalls';
11
+ import type { ToolPayloadKind } from '../messages/ToolCalls';
12
12
 
13
13
  export interface ToolPayloadMatcher {
14
14
  /** Cheap predicate. First match wins. */