@djangocfg/ui-tools 2.1.407 → 2.1.409

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 (297) hide show
  1. package/README.md +9 -10
  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 +8 -13
  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/parts/Meta/TimeDisplay.tsx +2 -5
  67. package/src/tools/Chat/README.md +277 -39
  68. package/src/tools/Chat/composer/Composer.tsx +471 -0
  69. package/src/tools/Chat/composer/ComposerActionBar.tsx +65 -0
  70. package/src/tools/Chat/composer/ComposerBanner.tsx +128 -0
  71. package/src/tools/Chat/composer/ComposerButton.tsx +64 -0
  72. package/src/tools/Chat/composer/ComposerFooter.tsx +90 -0
  73. package/src/tools/Chat/composer/ComposerMenuButton.tsx +62 -0
  74. package/src/tools/Chat/composer/ComposerModelPicker.tsx +104 -0
  75. package/src/tools/Chat/composer/ComposerRichTextarea.tsx +88 -0
  76. package/src/tools/Chat/composer/ComposerToolPill.tsx +95 -0
  77. package/src/tools/Chat/composer/index.ts +45 -0
  78. package/src/tools/Chat/composer/size-context.tsx +26 -0
  79. package/src/tools/Chat/composer/types.ts +143 -0
  80. package/src/tools/Chat/composer/useComposerActions.tsx +164 -0
  81. package/src/tools/Chat/context/ChatProvider.tsx +54 -3
  82. package/src/tools/Chat/core/__tests__/metadata.test.ts +69 -0
  83. package/src/tools/Chat/core/index.ts +23 -1
  84. package/src/tools/Chat/core/markdown.ts +1 -1
  85. package/src/tools/Chat/core/metadata.ts +47 -0
  86. package/src/tools/Chat/core/payload-dispatch.ts +1 -1
  87. package/src/tools/Chat/core/transport/http.ts +71 -32
  88. package/src/tools/Chat/core/transport/sse.ts +18 -10
  89. package/src/tools/Chat/highlight/HighlightOverlay.tsx +101 -0
  90. package/src/tools/Chat/highlight/README.md +103 -0
  91. package/src/tools/Chat/highlight/SpotlightCanvas.tsx +153 -0
  92. package/src/tools/Chat/highlight/__tests__/HighlightOverlay.test.tsx +112 -0
  93. package/src/tools/Chat/highlight/__tests__/resolveRef.test.ts +55 -0
  94. package/src/tools/Chat/highlight/index.ts +21 -0
  95. package/src/tools/Chat/highlight/resolveRef.ts +42 -0
  96. package/src/tools/Chat/highlight/types.ts +49 -0
  97. package/src/tools/Chat/highlight/useHighlightTargets.ts +128 -0
  98. package/src/tools/Chat/hooks/index.ts +0 -5
  99. package/src/tools/Chat/hooks/useAutoFocusOnStreamEnd.ts +28 -47
  100. package/src/tools/Chat/hooks/useChat.ts +47 -14
  101. package/src/tools/Chat/hooks/useChatComposer.ts +2 -2
  102. package/src/tools/Chat/hooks/useChatLayout.ts +1 -1
  103. package/src/tools/Chat/hooks/useStreamEndFocus.ts +54 -0
  104. package/src/tools/Chat/index.ts +25 -219
  105. package/src/tools/Chat/launcher/ChatDock.tsx +1 -1
  106. package/src/tools/Chat/launcher/ChatLauncher.tsx +1 -1
  107. package/src/tools/Chat/launcher/{ChatHeader.tsx → header/ChatHeader.tsx} +24 -11
  108. package/src/tools/Chat/launcher/{ChatHeaderActionButton.tsx → header/ChatHeaderActionButton.tsx} +34 -3
  109. package/src/tools/Chat/launcher/{ChatHeaderLanguageButton.tsx → header/ChatHeaderLanguageButton.tsx} +2 -2
  110. package/src/tools/Chat/launcher/{ChatHeaderModeToggle.tsx → header/ChatHeaderModeToggle.tsx} +1 -1
  111. package/src/tools/Chat/launcher/{ChatHeaderResetButton.tsx → header/ChatHeaderResetButton.tsx} +2 -1
  112. package/src/tools/Chat/launcher/{HeaderSlots.tsx → header/HeaderSlots.tsx} +3 -3
  113. package/src/tools/Chat/launcher/header/index.ts +26 -0
  114. package/src/tools/Chat/launcher/index.ts +3 -10
  115. package/src/tools/Chat/lazy.tsx +38 -284
  116. package/src/tools/Chat/{components → messages}/MessageBubble.tsx +58 -5
  117. package/src/tools/Chat/{components → messages}/MessageList.tsx +8 -25
  118. package/src/tools/Chat/messages/blocks/MessageBlocks.tsx +131 -0
  119. package/src/tools/Chat/messages/blocks/builtin.tsx +91 -0
  120. package/src/tools/Chat/messages/blocks/index.ts +12 -0
  121. package/src/tools/Chat/messages/blocks/registry.tsx +42 -0
  122. package/src/tools/Chat/messages/blocks/renderers/AudioBlock.tsx +20 -0
  123. package/src/tools/Chat/messages/blocks/renderers/CodeBlock.tsx +19 -0
  124. package/src/tools/Chat/messages/blocks/renderers/GalleryBlock.tsx +26 -0
  125. package/src/tools/Chat/messages/blocks/renderers/ImageBlock.tsx +27 -0
  126. package/src/tools/Chat/messages/blocks/renderers/JsonBlock.tsx +12 -0
  127. package/src/tools/Chat/messages/blocks/renderers/LottieBlock.tsx +11 -0
  128. package/src/tools/Chat/messages/blocks/renderers/MapBlock.tsx +36 -0
  129. package/src/tools/Chat/messages/blocks/renderers/MermaidBlock.tsx +11 -0
  130. package/src/tools/Chat/messages/blocks/renderers/VideoBlock.tsx +24 -0
  131. package/src/tools/Chat/messages/blocks/renderers/types.ts +8 -0
  132. package/src/tools/Chat/{components → messages}/index.ts +11 -5
  133. package/src/tools/Chat/public.ts +212 -0
  134. package/src/tools/Chat/shell/ChatRoot.tsx +345 -0
  135. package/src/tools/Chat/{components → shell}/EmptyState.tsx +4 -2
  136. package/src/tools/Chat/shell/index.ts +15 -0
  137. package/src/tools/Chat/types/block.ts +120 -0
  138. package/src/tools/Chat/types/config.ts +0 -5
  139. package/src/tools/Chat/types/index.ts +17 -0
  140. package/src/tools/Chat/types/message.ts +3 -0
  141. package/src/tools/Chat/utils/index.ts +4 -0
  142. package/src/tools/CodeEditor/README.md +4 -6
  143. package/src/tools/CodeEditor/components/DiffEditor.tsx +48 -13
  144. package/src/tools/CodeEditor/components/Editor.tsx +96 -44
  145. package/src/tools/CodeEditor/context/EditorProvider.tsx +34 -17
  146. package/src/tools/CodeEditor/hooks/useEditorTheme.ts +92 -99
  147. package/src/tools/CodeEditor/hooks/useMonaco.ts +37 -22
  148. package/src/tools/CodeEditor/lazy.tsx +6 -0
  149. package/src/tools/CodeEditor/lib/index.ts +1 -1
  150. package/src/tools/CodeEditor/lib/themes.ts +3 -39
  151. package/src/tools/CronScheduler/CronScheduler.client.tsx +230 -61
  152. package/src/tools/CronScheduler/components/CustomInput.tsx +21 -4
  153. package/src/tools/CronScheduler/components/DayChips.tsx +13 -11
  154. package/src/tools/CronScheduler/components/MonthDayGrid.tsx +4 -4
  155. package/src/tools/CronScheduler/components/SchedulePreview.tsx +7 -3
  156. package/src/tools/CronScheduler/components/TimeSelector.tsx +1 -1
  157. package/src/tools/CronScheduler/index.tsx +1 -1
  158. package/src/tools/CronScheduler/types/index.ts +8 -3
  159. package/src/tools/CronScheduler/utils/cron-humanize.ts +61 -16
  160. package/src/tools/CronScheduler/utils/cron-parser.ts +13 -4
  161. package/src/tools/FileIcon/FileIcon.tsx +24 -39
  162. package/src/tools/FileIcon/get-file-icon.ts +73 -0
  163. package/src/tools/FileIcon/icons/icon-data.ts +399 -0
  164. package/src/tools/FileIcon/index.ts +4 -0
  165. package/src/tools/FileIcon/loader.ts +17 -35
  166. package/src/tools/FileIcon/specialFolders.ts +18 -0
  167. package/src/tools/Gallery/components/lightbox/GalleryLightbox.tsx +112 -35
  168. package/src/tools/Gallery/components/media/GalleryVideo.tsx +21 -2
  169. package/src/tools/Gallery/components/preview/GalleryCarousel.tsx +11 -1
  170. package/src/tools/Gallery/hooks/usePreloadImages.ts +54 -7
  171. package/src/tools/ImageViewer/components/ImageInfo.tsx +12 -1
  172. package/src/tools/ImageViewer/components/ImageToolbar.tsx +51 -43
  173. package/src/tools/ImageViewer/components/ImageViewer.tsx +96 -24
  174. package/src/tools/ImageViewer/hooks/useImageLoading.ts +13 -0
  175. package/src/tools/ImageViewer/utils/constants.ts +3 -0
  176. package/src/tools/ImageViewer/utils/index.ts +1 -0
  177. package/src/tools/JsonForm/JsonSchemaForm.tsx +4 -1
  178. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +5 -3
  179. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +7 -4
  180. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +3 -1
  181. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +23 -3
  182. package/src/tools/JsonForm/widgets/ColorWidget.tsx +20 -12
  183. package/src/tools/JsonForm/widgets/NumberWidget.tsx +14 -9
  184. package/src/tools/JsonForm/widgets/RadioWidget.tsx +78 -0
  185. package/src/tools/JsonForm/widgets/SelectWidget.tsx +1 -0
  186. package/src/tools/JsonForm/widgets/SliderWidget.tsx +7 -4
  187. package/src/tools/JsonForm/widgets/TextWidget.tsx +41 -17
  188. package/src/tools/JsonForm/widgets/index.ts +1 -0
  189. package/src/tools/JsonTree/components/JsonContent.tsx +115 -40
  190. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +177 -72
  191. package/src/tools/LottiePlayer/index.tsx +14 -4
  192. package/src/tools/LottiePlayer/lazy.tsx +11 -3
  193. package/src/tools/LottiePlayer/types.ts +31 -1
  194. package/src/tools/LottiePlayer/useLottie.ts +32 -9
  195. package/src/tools/LottiePlayer/usePrefersReducedMotion.ts +46 -0
  196. package/src/tools/Map/components/LayerSwitcher.tsx +54 -21
  197. package/src/tools/Map/components/MapCluster.tsx +28 -21
  198. package/src/tools/Map/components/MapContainer.tsx +11 -4
  199. package/src/tools/Map/components/MapLegend.tsx +46 -15
  200. package/src/tools/Map/components/MapMarker.tsx +31 -2
  201. package/src/tools/Map/hooks/useMapEvents.ts +64 -105
  202. package/src/tools/MarkdownEditor/MarkdownEditor.tsx +61 -6
  203. package/src/tools/MarkdownEditor/MentionList.tsx +37 -4
  204. package/src/tools/MarkdownEditor/createMentionSuggestion.ts +11 -0
  205. package/src/tools/MarkdownEditor/lazy.tsx +32 -7
  206. package/src/tools/MarkdownEditor/styles.css +13 -0
  207. package/src/tools/MarkdownMessage/CodeBlock.tsx +40 -17
  208. package/src/tools/MarkdownMessage/MarkdownMessage.tsx +26 -6
  209. package/src/tools/MarkdownMessage/components.tsx +22 -9
  210. package/src/tools/MarkdownMessage/types.ts +24 -1
  211. package/src/tools/Mermaid/Mermaid.client.tsx +27 -5
  212. package/src/tools/Mermaid/components/MermaidErrorPanel.tsx +31 -0
  213. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +14 -17
  214. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +264 -168
  215. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +76 -10
  216. package/src/tools/Mermaid/index.tsx +6 -0
  217. package/src/tools/Mermaid/utils/mermaid-helpers.ts +141 -18
  218. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +11 -1
  219. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +49 -20
  220. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +7 -0
  221. package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +7 -4
  222. package/src/tools/OpenapiViewer/constants.ts +3 -0
  223. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +73 -11
  224. package/src/tools/OpenapiViewer/utils/schemaExport.ts +26 -6
  225. package/src/tools/PrettyCode/PrettyCode.client.tsx +23 -16
  226. package/src/tools/PrettyCode/lazy.tsx +1 -1
  227. package/src/tools/SpeechRecognition/README.md +1 -1
  228. package/src/tools/SpeechRecognition/__tests__/language.test.ts +9 -3
  229. package/src/tools/SpeechRecognition/components/RecordingPulse.tsx +59 -0
  230. package/src/tools/SpeechRecognition/components/index.ts +2 -0
  231. package/src/tools/SpeechRecognition/core/engine/external.ts +24 -7
  232. package/src/tools/SpeechRecognition/core/language.ts +23 -6
  233. package/src/tools/SpeechRecognition/hooks/usePushToTalk.ts +36 -5
  234. package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +18 -11
  235. package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +94 -26
  236. package/src/tools/SpeechRecognition/widgets/index.ts +1 -1
  237. package/src/tools/Tree/README.md +4 -8
  238. package/src/tools/Tree/TreeRoot.tsx +22 -10
  239. package/src/tools/Tree/components/TreeContent.tsx +24 -4
  240. package/src/tools/Tree/components/TreeLabel.tsx +8 -2
  241. package/src/tools/Tree/components/TreeRow.tsx +16 -6
  242. package/src/tools/Tree/data/flatten.ts +10 -4
  243. package/src/tools/Tree/types.ts +4 -0
  244. package/src/tools/Uploader/components/UploadAddButton.tsx +29 -6
  245. package/src/tools/Uploader/components/UploadDropzone.tsx +63 -7
  246. package/src/tools/Uploader/components/UploadPageDropOverlay.tsx +19 -5
  247. package/src/tools/Uploader/components/UploadPreviewItem.tsx +47 -17
  248. package/src/tools/Uploader/components/UploadPreviewList.tsx +24 -12
  249. package/src/tools/Uploader/utils/formatters.ts +8 -3
  250. package/src/tools/VideoPlayer/canvas/hls-canvas.tsx +1 -0
  251. package/src/tools/VideoPlayer/canvas/{jsx.d.ts → jsx-augmentation.ts} +12 -19
  252. package/src/tools/VideoPlayer/canvas/vimeo-canvas.tsx +1 -0
  253. package/src/tools/VideoPlayer/canvas/youtube-canvas.tsx +1 -0
  254. package/src/tools/VideoPlayer/parts/fullscreen.tsx +1 -1
  255. package/src/tools/VideoPlayer/parts/pip.tsx +1 -1
  256. package/src/tools/VideoPlayer/parts/playback-rate.tsx +1 -1
  257. package/src/tools/VideoPlayer/parts/seek-bar.tsx +2 -2
  258. package/src/tools/VideoPlayer/parts/volume.tsx +2 -2
  259. package/src/tools/index.ts +2 -1
  260. package/src/tools/Chat/components/AudioToggle.tsx +0 -78
  261. package/src/tools/Chat/components/ChatRoot.tsx +0 -305
  262. package/src/tools/Chat/components/Composer.tsx +0 -216
  263. package/src/tools/Chat/hooks/useChatScroll.ts +0 -145
  264. package/src/tools/Chat/types.ts +0 -9
  265. package/src/tools/JsonTree/components/JsonToolbar.tsx +0 -95
  266. package/src/tools/JsonTree/hooks/useElementCorner.ts +0 -84
  267. package/src/tools/JsonTree/hooks/useNavbarHeight.ts +0 -83
  268. package/src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts +0 -121
  269. package/src/tools/Tour/README.md +0 -373
  270. package/src/tools/Tour/components/Tour.tsx +0 -12
  271. package/src/tools/Tour/components/TourContent.tsx +0 -171
  272. package/src/tools/Tour/components/TourNavigation.tsx +0 -77
  273. package/src/tools/Tour/components/TourProgress.tsx +0 -88
  274. package/src/tools/Tour/components/TourSpotlight.tsx +0 -199
  275. package/src/tools/Tour/components/index.ts +0 -5
  276. package/src/tools/Tour/context/TourContext.ts +0 -19
  277. package/src/tools/Tour/context/TourProvider.tsx +0 -292
  278. package/src/tools/Tour/context/index.ts +0 -2
  279. package/src/tools/Tour/hooks/index.ts +0 -3
  280. package/src/tools/Tour/hooks/useKeyboardNavigation.ts +0 -59
  281. package/src/tools/Tour/hooks/useStepTarget.ts +0 -121
  282. package/src/tools/Tour/hooks/useTour.ts +0 -42
  283. package/src/tools/Tour/index.ts +0 -38
  284. package/src/tools/Tour/types/index.ts +0 -224
  285. package/src/tools/Tour/utils/dom.ts +0 -98
  286. package/src/tools/Tour/utils/index.ts +0 -3
  287. package/src/tools/Tour/utils/logger.ts +0 -3
  288. package/src/tools/Tour/utils/scrollIntoView.ts +0 -24
  289. /package/src/tools/Chat/{config.ts → constants.ts} +0 -0
  290. /package/src/tools/Chat/launcher/{ChatHeaderAudioToggle.tsx → header/ChatHeaderAudioToggle.tsx} +0 -0
  291. /package/src/tools/Chat/{components → messages}/Attachments.tsx +0 -0
  292. /package/src/tools/Chat/{components → messages}/JumpToLatest.tsx +0 -0
  293. /package/src/tools/Chat/{components → messages}/MessageActions.tsx +0 -0
  294. /package/src/tools/Chat/{components → messages}/Sources.tsx +0 -0
  295. /package/src/tools/Chat/{components → messages}/StreamingIndicator.tsx +0 -0
  296. /package/src/tools/Chat/{components → messages}/ToolCalls.tsx +0 -0
  297. /package/src/tools/Chat/{components → shell}/ErrorBanner.tsx +0 -0
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useState, useCallback, useEffect } from 'react';
3
+ import { useState, useCallback, useEffect, useRef } from 'react';
4
4
  import {
5
5
  useBatchAddListener,
6
6
  useItemProgressListener,
@@ -28,6 +28,11 @@ export function UploadPreviewList({
28
28
  const abortItem = useAbortItem();
29
29
  const { upload } = useUploady();
30
30
 
31
+ // Live ref to items so unmount cleanup sees the latest object URLs
32
+ // (a [] effect would only ever capture the initial empty Map).
33
+ const itemsRef = useRef(items);
34
+ itemsRef.current = items;
35
+
31
36
  // Batch added - create initial items
32
37
  useBatchAddListener((batch) => {
33
38
  setItems(prev => {
@@ -36,8 +41,8 @@ export function UploadPreviewList({
36
41
  const file = item.file as File;
37
42
  let previewUrl: string | undefined;
38
43
 
39
- // Create object URL for image preview
40
- if (file.type.startsWith('image/')) {
44
+ // Create object URL for image/video preview
45
+ if (file.type.startsWith('image/') || file.type.startsWith('video/')) {
41
46
  previewUrl = URL.createObjectURL(file);
42
47
  }
43
48
 
@@ -80,11 +85,18 @@ export function UploadPreviewList({
80
85
  ? buildAssetFromResponse(response, existing.file)
81
86
  : undefined;
82
87
 
88
+ // The server now hosts the file — revoke the local object URL so it
89
+ // can be garbage-collected, and keep only a server-hosted preview URL.
90
+ if (existing.previewUrl) {
91
+ URL.revokeObjectURL(existing.previewUrl);
92
+ }
93
+
83
94
  next.set(item.id, {
84
95
  ...existing,
85
96
  status: 'complete',
86
97
  progress: 100,
87
98
  asset,
99
+ previewUrl: asset?.thumbnailUrl || asset?.url || undefined,
88
100
  });
89
101
  }
90
102
  return next;
@@ -153,15 +165,15 @@ export function UploadPreviewList({
153
165
  const handleRetry = useCallback((id: string) => {
154
166
  const item = items.get(id);
155
167
  if (item && (item.status === 'error' || item.status === 'aborted')) {
156
- // Reset status to pending
168
+ // rpldy.upload() creates a NEW batch item with a fresh id, so the old
169
+ // entry must be dropped (and its object URL revoked) to avoid a stale
170
+ // ghost item and a leaked URL. useBatchAddListener adds the new one.
171
+ if (item.previewUrl) {
172
+ URL.revokeObjectURL(item.previewUrl);
173
+ }
157
174
  setItems(prev => {
158
175
  const next = new Map(prev);
159
- next.set(id, {
160
- ...item,
161
- status: 'pending',
162
- progress: 0,
163
- error: undefined,
164
- });
176
+ next.delete(id);
165
177
  return next;
166
178
  });
167
179
 
@@ -171,10 +183,10 @@ export function UploadPreviewList({
171
183
  }
172
184
  }, [items, upload, onRetry]);
173
185
 
174
- // Cleanup object URLs on unmount
186
+ // Cleanup object URLs on unmount (reads live ref, not a stale closure)
175
187
  useEffect(() => {
176
188
  return () => {
177
- items.forEach(item => {
189
+ itemsRef.current.forEach(item => {
178
190
  if (item.previewUrl) {
179
191
  URL.revokeObjectURL(item.previewUrl);
180
192
  }
@@ -27,17 +27,22 @@ export function truncateFilename(filename: string, maxLength = 32): string {
27
27
  }
28
28
 
29
29
  export function formatFileSize(bytes: number): string {
30
- if (bytes === 0) return '0 B';
30
+ if (!Number.isFinite(bytes) || bytes <= 0) return '0 B';
31
31
 
32
- const units = ['B', 'KB', 'MB', 'GB', 'TB'];
32
+ const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
33
33
  const k = 1024;
34
- const i = Math.floor(Math.log(bytes) / Math.log(k));
34
+ // Clamp the unit index so absurdly large values never index out of bounds.
35
+ const i = Math.min(
36
+ Math.floor(Math.log(bytes) / Math.log(k)),
37
+ units.length - 1,
38
+ );
35
39
  const size = bytes / Math.pow(k, i);
36
40
 
37
41
  return `${size.toFixed(i > 0 ? 1 : 0)} ${units[i]}`;
38
42
  }
39
43
 
40
44
  export function formatDuration(seconds: number): string {
45
+ if (!Number.isFinite(seconds) || seconds < 0) return '0:00';
41
46
  const mins = Math.floor(seconds / 60);
42
47
  const secs = Math.floor(seconds % 60);
43
48
  return `${mins}:${secs.toString().padStart(2, '0')}`;
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import 'hls-video-element';
10
+ import './jsx-augmentation';
10
11
  import { MediaPosterImage } from 'media-chrome/react';
11
12
  import type { HlsSource, VideoPlayerSettings } from '../types';
12
13
 
@@ -3,14 +3,18 @@
3
3
  * shipped by `youtube-video-element`, `vimeo-video-element`,
4
4
  * `hls-video-element`.
5
5
  *
6
- * media-chrome relies on `slot="media"` to attach a media element to
7
- * its `<MediaController>`. We declare the bare minimum prop surface
8
- * we use `src`, `slot`, `autoplay`, `muted`, `loop`, `playsinline`,
9
- * `crossorigin`, `preload`, `poster`.
6
+ * This is a real module (not an ambient `.d.ts`) so that it is reliably
7
+ * pulled into the type graph when imported as a side effect. Ambient
8
+ * `.d.ts` files are only loaded when they fall inside a project's
9
+ * `include` — consumers (e.g. the Storybook tsconfig) only `include`
10
+ * their own `stories/`, so a bare `.d.ts` here was silently skipped.
10
11
  *
11
- * All custom elements are HTMLElement subclasses with `HTMLVideoElement`
12
- * -shaped APIs, so we type their JSX props as a partial of
13
- * `HTMLVideoElementAttributes` + `slot`.
12
+ * media-chrome relies on `slot="media"` to attach a media element to
13
+ * its `<MediaController>`. We declare the bare prop surface we use:
14
+ * `src`, `slot`, `autoplay`, `muted`, `loop`, `playsinline`,
15
+ * `crossorigin`, `preload`, `poster`. All three elements are
16
+ * `HTMLElement` subclasses with `HTMLVideoElement`-shaped APIs, so
17
+ * their JSX props are typed as a partial of `VideoHTMLAttributes`.
14
18
  */
15
19
 
16
20
  import type { DetailedHTMLProps, HTMLAttributes, VideoHTMLAttributes } from 'react';
@@ -22,7 +26,7 @@ type VideoLikeElement = DetailedHTMLProps<
22
26
 
23
27
  /**
24
28
  * `youtube-video-element` accepts a `config` property — an object merged
25
- * into the YouTube IFrame `playerVars` (it is JSON-serialized into a
29
+ * into the YouTube IFrame `playerVars` (JSON-serialized into a
26
30
  * `data-config` attribute internally). Used to suppress native chrome.
27
31
  */
28
32
  type YouTubeVideoElement = VideoLikeElement & {
@@ -40,15 +44,4 @@ declare module 'react' {
40
44
  }
41
45
  }
42
46
 
43
- declare global {
44
- // eslint-disable-next-line @typescript-eslint/no-namespace
45
- namespace JSX {
46
- interface IntrinsicElements {
47
- 'youtube-video': YouTubeVideoElement;
48
- 'vimeo-video': VideoLikeElement;
49
- 'hls-video': VideoLikeElement;
50
- }
51
- }
52
- }
53
-
54
47
  export {};
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import 'vimeo-video-element';
10
+ import './jsx-augmentation';
10
11
  import { MediaPosterImage } from 'media-chrome/react';
11
12
  import type { VimeoSource, VideoPlayerSettings } from '../types';
12
13
 
@@ -19,6 +19,7 @@
19
19
  */
20
20
 
21
21
  import 'youtube-video-element';
22
+ import './jsx-augmentation';
22
23
  import { MediaPosterImage } from 'media-chrome/react';
23
24
  import type { YouTubeSource, VideoPlayerSettings } from '../types';
24
25
 
@@ -11,7 +11,7 @@ export function Fullscreen({ className, ...props }: FullscreenProps) {
11
11
  <MediaFullscreenButton
12
12
  {...props}
13
13
  className={cn(
14
- 'media-control-square h-8 w-8 rounded-md text-foreground hover:bg-foreground/10',
14
+ 'media-control-square video-player__control h-8 w-8',
15
15
  className,
16
16
  )}
17
17
  />
@@ -11,7 +11,7 @@ export function Pip({ className, ...props }: PipProps) {
11
11
  <MediaPipButton
12
12
  {...props}
13
13
  className={cn(
14
- 'media-control-square h-8 w-8 rounded-md text-foreground hover:bg-foreground/10',
14
+ 'media-control-square video-player__control h-8 w-8',
15
15
  className,
16
16
  )}
17
17
  />
@@ -23,7 +23,7 @@ export function PlaybackRate({ className, rates, ...props }: PlaybackRateProps)
23
23
  // React typing pulls from the getter only, which is narrower.
24
24
  rates={value as unknown as ArrayLike<number>}
25
25
  className={cn(
26
- 'h-8 rounded-md px-2 text-xs tabular-nums text-foreground hover:bg-foreground/10',
26
+ 'video-player__control h-8 px-2 text-xs tabular-nums',
27
27
  className,
28
28
  )}
29
29
  />
@@ -13,12 +13,12 @@ export function SeekBar({ className, showTime = true, ...props }: SeekBarProps)
13
13
  <>
14
14
  <MediaTimeRange
15
15
  {...props}
16
- className={cn('h-8 flex-1 text-foreground', className)}
16
+ className={cn('h-8 flex-1', className)}
17
17
  />
18
18
  {showTime && (
19
19
  <MediaTimeDisplay
20
20
  showDuration
21
- className="px-2 text-xs tabular-nums text-muted-foreground"
21
+ className="video-player__time px-2 text-xs tabular-nums"
22
22
  />
23
23
  )}
24
24
  </>
@@ -17,14 +17,14 @@ export function Volume({ className, iconOnly, muteProps, rangeProps }: VolumePro
17
17
  <MediaMuteButton
18
18
  {...muteProps}
19
19
  className={cn(
20
- 'media-control-square h-8 w-8 rounded-md text-foreground hover:bg-foreground/10',
20
+ 'media-control-square video-player__control h-8 w-8',
21
21
  muteProps?.className,
22
22
  )}
23
23
  />
24
24
  {!iconOnly && (
25
25
  <MediaVolumeRange
26
26
  {...rangeProps}
27
- className={cn('h-8 w-20 text-foreground', rangeProps?.className)}
27
+ className={cn('h-8 w-20', rangeProps?.className)}
28
28
  />
29
29
  )}
30
30
  </div>
@@ -22,12 +22,13 @@ export type { JsonTreeConfig } from './JsonTree';
22
22
  export { default as Mermaid } from './Mermaid';
23
23
  export { default as PrettyCode } from './PrettyCode';
24
24
  export type { Language } from './PrettyCode';
25
- export { LottiePlayer, useLottie } from './LottiePlayer';
25
+ export { LottiePlayer, useLottie, usePrefersReducedMotion } from './LottiePlayer';
26
26
  export type {
27
27
  LottiePlayerProps,
28
28
  LottieSize,
29
29
  LottieSpeed,
30
30
  LottieDirection,
31
+ LottieSegment,
31
32
  UseLottieOptions,
32
33
  UseLottieReturn,
33
34
  } from './LottiePlayer';
@@ -1,78 +0,0 @@
1
- 'use client';
2
-
3
- /**
4
- * Mute / unmute toggle for chat audio events.
5
- *
6
- * Reads the current ``muted`` state from the active chat context and
7
- * persists changes through ``useChatAudioPrefs`` (cross-tab safe). The
8
- * button auto-hides when no ``audio.sounds`` config is provided —
9
- * showing a mute toggle for a chat with no sounds is just clutter.
10
- *
11
- * Drop into a ChatRoot ``header`` slot or anywhere inside a chat
12
- * provider:
13
- *
14
- * ```tsx
15
- * <ChatRoot
16
- * header={<AudioToggle />}
17
- * audio={{ sounds: { messageReceived: '/ping.mp3' } }}
18
- * ...
19
- * />
20
- * ```
21
- */
22
-
23
- import { Volume2, VolumeX } from 'lucide-react';
24
-
25
- import { Button } from '@djangocfg/ui-core/components';
26
- import { cn } from '@djangocfg/ui-core/lib';
27
-
28
- import { useChatContextOptional } from '../context';
29
- import { useChatAudioPrefs } from '../core/audio/preferences';
30
-
31
- export interface AudioToggleProps {
32
- /** Visual size — matches Button sizes. Default: ``icon``. */
33
- size?: 'sm' | 'icon';
34
- /** Variant passed to the underlying Button. Default: ``ghost``. */
35
- variant?: 'ghost' | 'outline' | 'secondary';
36
- /** Force-show even when no audio config is wired (e.g. for stories). */
37
- alwaysShow?: boolean;
38
- className?: string;
39
- }
40
-
41
- export function AudioToggle({
42
- size = 'icon',
43
- variant = 'ghost',
44
- alwaysShow = false,
45
- className,
46
- }: AudioToggleProps) {
47
- // Read straight from the persist store so the toggle works even
48
- // when rendered OUTSIDE the ChatRoot (e.g. in a parent header). The
49
- // chat audio bus reads the same store, so a click here flips the
50
- // "muted" state for any sibling ChatRoot in the same tab and
51
- // mirrors across tabs via the storage event.
52
- const muted = useChatAudioPrefs((s) => s.muted);
53
- const setMuted = useChatAudioPrefs((s) => s.setMuted);
54
-
55
- // If a ChatRoot is in scope, hide unless it actually wired sounds —
56
- // otherwise the button is a no-op for that surface. When rendered
57
- // standalone (no context), default to visible.
58
- const ctx = useChatContextOptional();
59
- if (ctx && !ctx.hasAudio && !alwaysShow) return null;
60
-
61
- const Icon = muted ? VolumeX : Volume2;
62
- const label = muted ? 'Unmute chat sounds' : 'Mute chat sounds';
63
-
64
- return (
65
- <Button
66
- type="button"
67
- variant={variant}
68
- size={size}
69
- onClick={() => setMuted(!muted)}
70
- aria-label={label}
71
- aria-pressed={muted}
72
- title={label}
73
- className={cn(size === 'icon' ? 'h-9 w-9' : '', className)}
74
- >
75
- <Icon aria-hidden className="size-4" />
76
- </Button>
77
- );
78
- }
@@ -1,305 +0,0 @@
1
- 'use client';
2
-
3
- import { type ReactNode, useMemo, useRef, useState } from 'react';
4
-
5
- import { cn } from '@djangocfg/ui-core/lib';
6
-
7
- import type { ChatAttachment, ChatConfig, ChatMessage, ChatTransport } from '../types';
8
- import type { ChatAudioConfig } from '../core/audio/types';
9
- import {
10
- ChatProvider,
11
- useChatContext,
12
- useChatContextOptional,
13
- type ChatContextValue,
14
- } from '../context';
15
- import { useAutoFocusOnStreamEnd } from '../hooks/useAutoFocusOnStreamEnd';
16
- import { useChatComposer, type UseChatComposerReturn } from '../hooks/useChatComposer';
17
- import { useFocusOnEmptyClick } from '../hooks/useFocusOnEmptyClick';
18
- import { Composer, type ComposerSize } from './Composer';
19
- import { EmptyState } from './EmptyState';
20
- import { ErrorBanner } from './ErrorBanner';
21
- import { JumpToLatest } from './JumpToLatest';
22
- import { MessageBubble } from './MessageBubble';
23
- import { MessageList, type MessageListHandle } from './MessageList';
24
- import type { AttachmentRendererMap } from './Attachments';
25
- import type { ToolCallsProps } from './ToolCalls';
26
-
27
- export interface ChatRootProps {
28
- // ---- core wiring -------------------------------------------------------
29
- /**
30
- * Transport. Required UNLESS `<ChatRoot>` is rendered inside an
31
- * existing `<ChatProvider>` (e.g. mounted by `<ChatLauncher>`), in
32
- * which case the ambient provider is reused and `transport` is
33
- * ignored.
34
- */
35
- transport?: ChatTransport;
36
- config?: ChatConfig;
37
- initialSessionId?: string;
38
- autoCreateSession?: boolean;
39
- streaming?: boolean;
40
- /** Audio-trigger configuration. Off by default (no `sounds` map). */
41
- audio?: ChatAudioConfig;
42
- /**
43
- * Verbose dev-mode logging via `consola` (namespace `chat:*`).
44
- * Defaults to `isDev` from `@djangocfg/ui-core/lib`. Pass `false` to silence
45
- * even in development, or `true` to force on in production for debugging.
46
- */
47
- debug?: boolean;
48
- className?: string;
49
-
50
- // ---- named ReactNode slots --------------------------------------------
51
- /** Sticky banner above the message list (e.g. quota warning). */
52
- banner?: ReactNode;
53
- /** Header row below the banner — title / actions / session switcher. */
54
- header?: ReactNode;
55
- /** Footer slot below the composer (disclaimers, model picker). */
56
- footer?: ReactNode;
57
- /** Replaces the default `<EmptyState>` rendered when the conversation is empty. */
58
- empty?: ReactNode;
59
- /** Slot left of the textarea inside `<Composer>`. */
60
- composerToolbarStart?: ReactNode;
61
- /** Slot right of the textarea inside `<Composer>`. */
62
- composerToolbarEnd?: ReactNode;
63
- /** Replaces the default attachment tray inside `<Composer>`. */
64
- composerAttachmentTray?: ReactNode;
65
- /** Replaces the default `<JumpToLatest>` floating pill. */
66
- jumpToLatest?: ReactNode;
67
-
68
- // ---- render-prop slots (need access to data) --------------------------
69
- /** Replace `<MessageBubble>` per message. */
70
- renderMessage?: (m: ChatMessage, i: number) => ReactNode;
71
- /**
72
- * Render arbitrary content beneath every default `<MessageBubble>`
73
- * (not invoked when `renderMessage` is set — the host owns layout
74
- * in that case). Useful for product widgets driven by a side channel
75
- * (e.g. `ui_payload` SSE frames) where the widget is per-message but
76
- * not tied to the bubble's tool-calls array.
77
- */
78
- renderAfterMessage?: (m: ChatMessage) => ReactNode;
79
- /** Render the header lazily — receives the chat context. */
80
- renderHeader?: (ctx: ChatContextValue) => ReactNode;
81
- /** Render the empty-state lazily — receives a `setValue` to seed the composer. */
82
- renderEmpty?: (api: { setValue: (v: string) => void; focus: () => void }) => ReactNode;
83
- /**
84
- * Replace the default `<Composer>` entirely. Receives the live
85
- * `useChatComposer` return and the composer-related slot props
86
- * (placeholder, size, attach button, toolbar slots) — host can wire
87
- * them into a custom widget (e.g. a MarkdownEditor with mentions).
88
- *
89
- * When set, `composerToolbarStart/End`, `composerAttachmentTray`,
90
- * `composerSize`, `showAttachmentButton`, `onPickFiles` are passed
91
- * to the render-prop but are no longer auto-rendered.
92
- */
93
- renderComposer?: (api: {
94
- composer: UseChatComposerReturn;
95
- placeholder?: string;
96
- size?: ComposerSize;
97
- showAttachmentButton?: boolean;
98
- onPickFiles?: () => void;
99
- toolbarStart?: ReactNode;
100
- toolbarEnd?: ReactNode;
101
- attachmentTray?: ReactNode;
102
- }) => ReactNode;
103
- /** Forwarded into `<MessageBubble toolCallsProps>` so hosts can swap payload renderers. */
104
- toolCallsProps?: Omit<ToolCallsProps, 'calls'>;
105
- /** Per-type attachment renderers — `{ image, audio, video, file, default }`. */
106
- attachmentRenderers?: AttachmentRendererMap;
107
- /** Called when an attachment tile is clicked (e.g. open lightbox). */
108
- onAttachmentOpen?: (attachment: ChatAttachment) => void;
109
-
110
- // ---- composer customization -------------------------------------------
111
- /** Show the paperclip "attach" button in the composer. */
112
- showAttachmentButton?: boolean;
113
- /** Called when the user clicks the attach button (host opens its file picker). */
114
- onPickFiles?: () => void;
115
- /** Hide the composer input area entirely (e.g. while waiting for human approval). */
116
- hideComposer?: boolean;
117
- /** Composer size variant. Default ``md`` (36px slot). Use ``lg`` for primary
118
- * surfaces (onboarding, full-page chat), ``sm`` for dense sidebars. */
119
- composerSize?: ComposerSize;
120
- /** Extra className forwarded to the `<MessageList>` scroll container.
121
- * Use to add padding inside the scroll area so the scrollbar stays flush
122
- * with the edge (e.g. `"px-6 pt-6 sm:px-8"`). */
123
- listClassName?: string;
124
- /** Extra className forwarded to the `<Composer>` wrapper div. */
125
- composerClassName?: string;
126
- /**
127
- * Click in the message area → focus the composer (Slack / ChatGPT / Linear).
128
- * Honours selection drag, interactive elements, and touch input.
129
- * @default true
130
- */
131
- focusOnEmptyClick?: boolean;
132
- }
133
-
134
- export function ChatRoot(props: ChatRootProps) {
135
- const { transport, config, initialSessionId, autoCreateSession, streaming, audio, debug, className, listClassName, ...slots } = props;
136
- // When mounted under a launcher-owned `<ChatProvider>`, reuse that
137
- // provider instead of wrapping in a second one. This lets host code
138
- // freely nest `<ChatRoot>` inside `<ChatLauncher>` without losing
139
- // `headerSlots` access to the same session / clearMessages / audio.
140
- const ambient = useChatContextOptional();
141
- if (ambient) {
142
- return <ChatRootShell className={className} listClassName={listClassName} slots={slots} />;
143
- }
144
- if (!transport) {
145
- throw new Error(
146
- '<ChatRoot> requires `transport` when mounted outside a <ChatProvider>.',
147
- );
148
- }
149
- return (
150
- <ChatProvider
151
- transport={transport}
152
- config={config}
153
- initialSessionId={initialSessionId}
154
- autoCreateSession={autoCreateSession}
155
- streaming={streaming}
156
- audio={audio}
157
- debug={debug}
158
- >
159
- <ChatRootShell className={className} listClassName={listClassName} slots={slots} />
160
- </ChatProvider>
161
- );
162
- }
163
-
164
- interface ChatRootShellProps {
165
- className?: string;
166
- listClassName?: string;
167
- slots: Omit<ChatRootProps, 'transport' | 'config' | 'initialSessionId' | 'autoCreateSession' | 'streaming' | 'audio' | 'debug' | 'className' | 'listClassName'>;
168
- }
169
-
170
- function ChatRootShell({ className, listClassName, slots }: ChatRootShellProps) {
171
- const chat = useChatContext();
172
- const composer = useChatComposer({
173
- onSubmit: (content, attachments) => chat.sendMessage(content, attachments),
174
- disabled: chat.isStreaming,
175
- });
176
- const onMessagesMouseUp = useFocusOnEmptyClick({
177
- enabled: slots.focusOnEmptyClick !== false,
178
- });
179
- // Re-focus the composer on the streaming → idle edge. Was dropped in
180
- // a previous refactor — restored here so the standard chat UX (type
181
- // → send → read → keep typing without clicking) works again.
182
- useAutoFocusOnStreamEnd();
183
- // MessageList (virtuoso) owns the scroll viewport. We talk to it
184
- // via the imperative handle (scrollToBottom on JumpToLatest click)
185
- // and via the `onAtBottomChange` callback (drives the pill).
186
- const listRef = useRef<MessageListHandle | null>(null);
187
- const [isAtBottom, setIsAtBottom] = useState(true);
188
- // The id of the most-recent user-sent message. We pass it as
189
- // `scrollAnchorId` to MessageList so every send re-anchors the
190
- // viewport — fixes "I sent a message but the chat didn't scroll
191
- // down". Recomputed lazily; the resulting id only flips when a new
192
- // user message lands.
193
- const lastUserMessageId = useMemo(() => {
194
- const msgs = chat.messages;
195
- for (let i = msgs.length - 1; i >= 0; i -= 1) {
196
- if (msgs[i].role === 'user') return msgs[i].id;
197
- }
198
- return null;
199
- }, [chat.messages]);
200
- const handleStartReached = chat.hasMore && !chat.isLoadingMore
201
- ? () => void chat.loadMore()
202
- : undefined;
203
-
204
- const greeting = chat.config.greeting ?? 'How can I help?';
205
- const description = chat.config.description;
206
- const suggestions = chat.config.suggestions;
207
-
208
- const headerNode = slots.renderHeader ? slots.renderHeader(chat) : slots.header;
209
-
210
- const emptyNode = slots.empty
211
- ?? (slots.renderEmpty
212
- ? slots.renderEmpty({ setValue: composer.setValue, focus: composer.focus })
213
- : (
214
- <EmptyState
215
- greeting={greeting}
216
- description={description}
217
- suggestions={suggestions}
218
- onPickSuggestion={(prompt) => {
219
- composer.setValue(prompt);
220
- composer.focus();
221
- }}
222
- />
223
- ));
224
-
225
- const renderItem = slots.renderMessage
226
- ?? ((m: ChatMessage) => (
227
- <MessageBubble
228
- key={m.id}
229
- message={m}
230
- toolCallsProps={slots.toolCallsProps}
231
- attachmentRenderers={slots.attachmentRenderers}
232
- onAttachmentOpen={slots.onAttachmentOpen}
233
- renderAfterMessage={slots.renderAfterMessage}
234
- onCopy={() => copy(m.content)}
235
- onRegenerate={() => void chat.regenerate(m.id)}
236
- onDelete={() => chat.deleteMessage(m.id)}
237
- />
238
- ));
239
-
240
- return (
241
- <div className={cn('relative flex h-full min-h-0 flex-col overflow-hidden', className)}>
242
- {slots.banner ?? null}
243
- {headerNode ?? null}
244
- <div className="relative flex min-h-0 flex-1 flex-col" onMouseUp={onMessagesMouseUp}>
245
- <ErrorBanner
246
- error={chat.error}
247
- onDismiss={chat.error ? () => chat.clearMessages() : undefined}
248
- onRetry={chat.error ? () => void chat.regenerate() : undefined}
249
- />
250
- <MessageList
251
- ref={listRef}
252
- renderItem={renderItem}
253
- renderEmpty={() => <>{emptyNode}</>}
254
- className={listClassName}
255
- onStartReached={handleStartReached}
256
- onAtBottomChange={setIsAtBottom}
257
- scrollAnchorId={lastUserMessageId}
258
- />
259
- <div className="pointer-events-none absolute inset-x-0 bottom-2 flex justify-center">
260
- {slots.jumpToLatest ?? (
261
- <JumpToLatest
262
- visible={!isAtBottom}
263
- onClick={() => listRef.current?.scrollToBottom(true)}
264
- />
265
- )}
266
- </div>
267
- </div>
268
- {!slots.hideComposer && (
269
- slots.renderComposer
270
- ? slots.renderComposer({
271
- composer,
272
- placeholder: chat.config.placeholder,
273
- size: slots.composerSize,
274
- showAttachmentButton: slots.showAttachmentButton,
275
- onPickFiles: slots.onPickFiles,
276
- toolbarStart: slots.composerToolbarStart,
277
- toolbarEnd: slots.composerToolbarEnd,
278
- attachmentTray: slots.composerAttachmentTray,
279
- })
280
- : (
281
- <Composer
282
- composer={composer}
283
- placeholder={chat.config.placeholder}
284
- showAttachmentButton={slots.showAttachmentButton}
285
- onPickFiles={slots.onPickFiles}
286
- toolbarStart={slots.composerToolbarStart}
287
- toolbarEnd={slots.composerToolbarEnd}
288
- attachmentTray={slots.composerAttachmentTray}
289
- size={slots.composerSize}
290
- />
291
- )
292
- )}
293
- {slots.footer ?? null}
294
- </div>
295
- );
296
- }
297
-
298
- function copy(text: string) {
299
- if (typeof navigator !== 'undefined' && navigator.clipboard) {
300
- void navigator.clipboard.writeText(text);
301
- }
302
- }
303
-
304
- // re-export for convenience: composer hook return is a common slot dependency
305
- export type { UseChatComposerReturn };