@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.
- package/README.md +9 -10
- package/dist/file-icon/index.cjs +449 -61
- package/dist/file-icon/index.cjs.map +1 -1
- package/dist/file-icon/index.d.cts +56 -18
- package/dist/file-icon/index.d.ts +56 -18
- package/dist/file-icon/index.mjs +448 -62
- package/dist/file-icon/index.mjs.map +1 -1
- package/dist/tree/index.cjs +49 -22
- package/dist/tree/index.cjs.map +1 -1
- package/dist/tree/index.d.cts +9 -3
- package/dist/tree/index.d.ts +9 -3
- package/dist/tree/index.mjs +49 -22
- package/dist/tree/index.mjs.map +1 -1
- package/dist/{types-B_zhyAqR.d.cts → types-eEu8SeiQ.d.cts} +4 -0
- package/dist/{types-B_zhyAqR.d.ts → types-eEu8SeiQ.d.ts} +4 -0
- package/package.json +8 -13
- package/src/components/FloatingToolbar/index.tsx +37 -3
- package/src/lib/page-snapshot/__tests__/capture-integration.test.ts +85 -0
- package/src/lib/page-snapshot/__tests__/engine.test.ts +36 -0
- package/src/lib/page-snapshot/__tests__/redaction-integration.test.ts +99 -0
- package/src/lib/page-snapshot/__tests__/tokens.test.ts +17 -0
- package/src/lib/page-snapshot/capture/__tests__/budget.test.ts +49 -0
- package/src/lib/page-snapshot/capture/__tests__/chrome-filter.test.ts +47 -0
- package/src/lib/page-snapshot/capture/__tests__/fold.test.ts +66 -0
- package/src/lib/page-snapshot/capture/__tests__/scope.test.ts +74 -0
- package/src/lib/page-snapshot/capture/__tests__/walk.test.ts +129 -0
- package/src/lib/page-snapshot/capture/accessible-name.ts +73 -0
- package/src/lib/page-snapshot/capture/budget.ts +95 -0
- package/src/lib/page-snapshot/capture/chrome-filter.ts +81 -0
- package/src/lib/page-snapshot/capture/classify.ts +111 -0
- package/src/lib/page-snapshot/capture/dom-utils.ts +111 -0
- package/src/lib/page-snapshot/capture/fold.ts +96 -0
- package/src/lib/page-snapshot/capture/scope.ts +169 -0
- package/src/lib/page-snapshot/capture/walk.ts +250 -0
- package/src/lib/page-snapshot/cst/__tests__/serialize.test.ts +50 -0
- package/src/lib/page-snapshot/cst/directives.ts +47 -0
- package/src/lib/page-snapshot/cst/payload.ts +50 -0
- package/src/lib/page-snapshot/cst/serialize.ts +84 -0
- package/src/lib/page-snapshot/cst/types.ts +115 -0
- package/src/lib/page-snapshot/engine.ts +176 -0
- package/src/lib/page-snapshot/index.ts +93 -0
- package/src/lib/page-snapshot/react/PageSnapshotChip.tsx +72 -0
- package/src/lib/page-snapshot/react/PageSnapshotPreview.tsx +78 -0
- package/src/lib/page-snapshot/react/__tests__/PageSnapshotChip.test.tsx +54 -0
- package/src/lib/page-snapshot/react/__tests__/provider.test.tsx +103 -0
- package/src/lib/page-snapshot/react/__tests__/use-page-snapshot-toggle.test.tsx +62 -0
- package/src/lib/page-snapshot/react/provider.tsx +162 -0
- package/src/lib/page-snapshot/react/use-page-snapshot-toggle.ts +47 -0
- package/src/lib/page-snapshot/react/use-page-snapshot.ts +67 -0
- package/src/lib/page-snapshot/redaction/__tests__/audit.test.ts +25 -0
- package/src/lib/page-snapshot/redaction/__tests__/heuristics.test.ts +73 -0
- package/src/lib/page-snapshot/redaction/__tests__/luhn.test.ts +26 -0
- package/src/lib/page-snapshot/redaction/__tests__/patterns.test.ts +60 -0
- package/src/lib/page-snapshot/redaction/audit.ts +58 -0
- package/src/lib/page-snapshot/redaction/heuristics.ts +75 -0
- package/src/lib/page-snapshot/redaction/index.ts +75 -0
- package/src/lib/page-snapshot/redaction/luhn.ts +25 -0
- package/src/lib/page-snapshot/redaction/patterns.ts +111 -0
- package/src/lib/page-snapshot/refs/__tests__/registry.test.ts +24 -0
- package/src/lib/page-snapshot/refs/registry.ts +46 -0
- package/src/lib/page-snapshot/staleness/__tests__/hash.test.ts +34 -0
- package/src/lib/page-snapshot/staleness/hash.ts +20 -0
- package/src/lib/page-snapshot/tokens.ts +15 -0
- package/src/tools/AudioPlayer/context/PlayerProvider.tsx +13 -14
- package/src/tools/AudioPlayer/hooks/useAudioElementEvents.ts +55 -6
- package/src/tools/AudioPlayer/parts/Meta/TimeDisplay.tsx +2 -5
- package/src/tools/Chat/README.md +277 -39
- package/src/tools/Chat/composer/Composer.tsx +471 -0
- package/src/tools/Chat/composer/ComposerActionBar.tsx +65 -0
- package/src/tools/Chat/composer/ComposerBanner.tsx +128 -0
- package/src/tools/Chat/composer/ComposerButton.tsx +64 -0
- package/src/tools/Chat/composer/ComposerFooter.tsx +90 -0
- package/src/tools/Chat/composer/ComposerMenuButton.tsx +62 -0
- package/src/tools/Chat/composer/ComposerModelPicker.tsx +104 -0
- package/src/tools/Chat/composer/ComposerRichTextarea.tsx +88 -0
- package/src/tools/Chat/composer/ComposerToolPill.tsx +95 -0
- package/src/tools/Chat/composer/index.ts +45 -0
- package/src/tools/Chat/composer/size-context.tsx +26 -0
- package/src/tools/Chat/composer/types.ts +143 -0
- package/src/tools/Chat/composer/useComposerActions.tsx +164 -0
- package/src/tools/Chat/context/ChatProvider.tsx +54 -3
- package/src/tools/Chat/core/__tests__/metadata.test.ts +69 -0
- package/src/tools/Chat/core/index.ts +23 -1
- package/src/tools/Chat/core/markdown.ts +1 -1
- package/src/tools/Chat/core/metadata.ts +47 -0
- package/src/tools/Chat/core/payload-dispatch.ts +1 -1
- package/src/tools/Chat/core/transport/http.ts +71 -32
- package/src/tools/Chat/core/transport/sse.ts +18 -10
- package/src/tools/Chat/highlight/HighlightOverlay.tsx +101 -0
- package/src/tools/Chat/highlight/README.md +103 -0
- package/src/tools/Chat/highlight/SpotlightCanvas.tsx +153 -0
- package/src/tools/Chat/highlight/__tests__/HighlightOverlay.test.tsx +112 -0
- package/src/tools/Chat/highlight/__tests__/resolveRef.test.ts +55 -0
- package/src/tools/Chat/highlight/index.ts +21 -0
- package/src/tools/Chat/highlight/resolveRef.ts +42 -0
- package/src/tools/Chat/highlight/types.ts +49 -0
- package/src/tools/Chat/highlight/useHighlightTargets.ts +128 -0
- package/src/tools/Chat/hooks/index.ts +0 -5
- package/src/tools/Chat/hooks/useAutoFocusOnStreamEnd.ts +28 -47
- package/src/tools/Chat/hooks/useChat.ts +47 -14
- package/src/tools/Chat/hooks/useChatComposer.ts +2 -2
- package/src/tools/Chat/hooks/useChatLayout.ts +1 -1
- package/src/tools/Chat/hooks/useStreamEndFocus.ts +54 -0
- package/src/tools/Chat/index.ts +25 -219
- package/src/tools/Chat/launcher/ChatDock.tsx +1 -1
- package/src/tools/Chat/launcher/ChatLauncher.tsx +1 -1
- package/src/tools/Chat/launcher/{ChatHeader.tsx → header/ChatHeader.tsx} +24 -11
- package/src/tools/Chat/launcher/{ChatHeaderActionButton.tsx → header/ChatHeaderActionButton.tsx} +34 -3
- package/src/tools/Chat/launcher/{ChatHeaderLanguageButton.tsx → header/ChatHeaderLanguageButton.tsx} +2 -2
- package/src/tools/Chat/launcher/{ChatHeaderModeToggle.tsx → header/ChatHeaderModeToggle.tsx} +1 -1
- package/src/tools/Chat/launcher/{ChatHeaderResetButton.tsx → header/ChatHeaderResetButton.tsx} +2 -1
- package/src/tools/Chat/launcher/{HeaderSlots.tsx → header/HeaderSlots.tsx} +3 -3
- package/src/tools/Chat/launcher/header/index.ts +26 -0
- package/src/tools/Chat/launcher/index.ts +3 -10
- package/src/tools/Chat/lazy.tsx +38 -284
- package/src/tools/Chat/{components → messages}/MessageBubble.tsx +58 -5
- package/src/tools/Chat/{components → messages}/MessageList.tsx +8 -25
- package/src/tools/Chat/messages/blocks/MessageBlocks.tsx +131 -0
- package/src/tools/Chat/messages/blocks/builtin.tsx +91 -0
- package/src/tools/Chat/messages/blocks/index.ts +12 -0
- package/src/tools/Chat/messages/blocks/registry.tsx +42 -0
- package/src/tools/Chat/messages/blocks/renderers/AudioBlock.tsx +20 -0
- package/src/tools/Chat/messages/blocks/renderers/CodeBlock.tsx +19 -0
- package/src/tools/Chat/messages/blocks/renderers/GalleryBlock.tsx +26 -0
- package/src/tools/Chat/messages/blocks/renderers/ImageBlock.tsx +27 -0
- package/src/tools/Chat/messages/blocks/renderers/JsonBlock.tsx +12 -0
- package/src/tools/Chat/messages/blocks/renderers/LottieBlock.tsx +11 -0
- package/src/tools/Chat/messages/blocks/renderers/MapBlock.tsx +36 -0
- package/src/tools/Chat/messages/blocks/renderers/MermaidBlock.tsx +11 -0
- package/src/tools/Chat/messages/blocks/renderers/VideoBlock.tsx +24 -0
- package/src/tools/Chat/messages/blocks/renderers/types.ts +8 -0
- package/src/tools/Chat/{components → messages}/index.ts +11 -5
- package/src/tools/Chat/public.ts +212 -0
- package/src/tools/Chat/shell/ChatRoot.tsx +345 -0
- package/src/tools/Chat/{components → shell}/EmptyState.tsx +4 -2
- package/src/tools/Chat/shell/index.ts +15 -0
- package/src/tools/Chat/types/block.ts +120 -0
- package/src/tools/Chat/types/config.ts +0 -5
- package/src/tools/Chat/types/index.ts +17 -0
- package/src/tools/Chat/types/message.ts +3 -0
- package/src/tools/Chat/utils/index.ts +4 -0
- package/src/tools/CodeEditor/README.md +4 -6
- package/src/tools/CodeEditor/components/DiffEditor.tsx +48 -13
- package/src/tools/CodeEditor/components/Editor.tsx +96 -44
- package/src/tools/CodeEditor/context/EditorProvider.tsx +34 -17
- package/src/tools/CodeEditor/hooks/useEditorTheme.ts +92 -99
- package/src/tools/CodeEditor/hooks/useMonaco.ts +37 -22
- package/src/tools/CodeEditor/lazy.tsx +6 -0
- package/src/tools/CodeEditor/lib/index.ts +1 -1
- package/src/tools/CodeEditor/lib/themes.ts +3 -39
- package/src/tools/CronScheduler/CronScheduler.client.tsx +230 -61
- package/src/tools/CronScheduler/components/CustomInput.tsx +21 -4
- package/src/tools/CronScheduler/components/DayChips.tsx +13 -11
- package/src/tools/CronScheduler/components/MonthDayGrid.tsx +4 -4
- package/src/tools/CronScheduler/components/SchedulePreview.tsx +7 -3
- package/src/tools/CronScheduler/components/TimeSelector.tsx +1 -1
- package/src/tools/CronScheduler/index.tsx +1 -1
- package/src/tools/CronScheduler/types/index.ts +8 -3
- package/src/tools/CronScheduler/utils/cron-humanize.ts +61 -16
- package/src/tools/CronScheduler/utils/cron-parser.ts +13 -4
- package/src/tools/FileIcon/FileIcon.tsx +24 -39
- package/src/tools/FileIcon/get-file-icon.ts +73 -0
- package/src/tools/FileIcon/icons/icon-data.ts +399 -0
- package/src/tools/FileIcon/index.ts +4 -0
- package/src/tools/FileIcon/loader.ts +17 -35
- package/src/tools/FileIcon/specialFolders.ts +18 -0
- package/src/tools/Gallery/components/lightbox/GalleryLightbox.tsx +112 -35
- package/src/tools/Gallery/components/media/GalleryVideo.tsx +21 -2
- package/src/tools/Gallery/components/preview/GalleryCarousel.tsx +11 -1
- package/src/tools/Gallery/hooks/usePreloadImages.ts +54 -7
- package/src/tools/ImageViewer/components/ImageInfo.tsx +12 -1
- package/src/tools/ImageViewer/components/ImageToolbar.tsx +51 -43
- package/src/tools/ImageViewer/components/ImageViewer.tsx +96 -24
- package/src/tools/ImageViewer/hooks/useImageLoading.ts +13 -0
- package/src/tools/ImageViewer/utils/constants.ts +3 -0
- package/src/tools/ImageViewer/utils/index.ts +1 -0
- package/src/tools/JsonForm/JsonSchemaForm.tsx +4 -1
- package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +5 -3
- package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +7 -4
- package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +3 -1
- package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +23 -3
- package/src/tools/JsonForm/widgets/ColorWidget.tsx +20 -12
- package/src/tools/JsonForm/widgets/NumberWidget.tsx +14 -9
- package/src/tools/JsonForm/widgets/RadioWidget.tsx +78 -0
- package/src/tools/JsonForm/widgets/SelectWidget.tsx +1 -0
- package/src/tools/JsonForm/widgets/SliderWidget.tsx +7 -4
- package/src/tools/JsonForm/widgets/TextWidget.tsx +41 -17
- package/src/tools/JsonForm/widgets/index.ts +1 -0
- package/src/tools/JsonTree/components/JsonContent.tsx +115 -40
- package/src/tools/LottiePlayer/LottiePlayer.client.tsx +177 -72
- package/src/tools/LottiePlayer/index.tsx +14 -4
- package/src/tools/LottiePlayer/lazy.tsx +11 -3
- package/src/tools/LottiePlayer/types.ts +31 -1
- package/src/tools/LottiePlayer/useLottie.ts +32 -9
- package/src/tools/LottiePlayer/usePrefersReducedMotion.ts +46 -0
- package/src/tools/Map/components/LayerSwitcher.tsx +54 -21
- package/src/tools/Map/components/MapCluster.tsx +28 -21
- package/src/tools/Map/components/MapContainer.tsx +11 -4
- package/src/tools/Map/components/MapLegend.tsx +46 -15
- package/src/tools/Map/components/MapMarker.tsx +31 -2
- package/src/tools/Map/hooks/useMapEvents.ts +64 -105
- package/src/tools/MarkdownEditor/MarkdownEditor.tsx +61 -6
- package/src/tools/MarkdownEditor/MentionList.tsx +37 -4
- package/src/tools/MarkdownEditor/createMentionSuggestion.ts +11 -0
- package/src/tools/MarkdownEditor/lazy.tsx +32 -7
- package/src/tools/MarkdownEditor/styles.css +13 -0
- package/src/tools/MarkdownMessage/CodeBlock.tsx +40 -17
- package/src/tools/MarkdownMessage/MarkdownMessage.tsx +26 -6
- package/src/tools/MarkdownMessage/components.tsx +22 -9
- package/src/tools/MarkdownMessage/types.ts +24 -1
- package/src/tools/Mermaid/Mermaid.client.tsx +27 -5
- package/src/tools/Mermaid/components/MermaidErrorPanel.tsx +31 -0
- package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +14 -17
- package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +264 -168
- package/src/tools/Mermaid/hooks/useMermaidValidation.ts +76 -10
- package/src/tools/Mermaid/index.tsx +6 -0
- package/src/tools/Mermaid/utils/mermaid-helpers.ts +141 -18
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +11 -1
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +49 -20
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +7 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +7 -4
- package/src/tools/OpenapiViewer/constants.ts +3 -0
- package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +73 -11
- package/src/tools/OpenapiViewer/utils/schemaExport.ts +26 -6
- package/src/tools/PrettyCode/PrettyCode.client.tsx +23 -16
- package/src/tools/PrettyCode/lazy.tsx +1 -1
- package/src/tools/SpeechRecognition/README.md +1 -1
- package/src/tools/SpeechRecognition/__tests__/language.test.ts +9 -3
- package/src/tools/SpeechRecognition/components/RecordingPulse.tsx +59 -0
- package/src/tools/SpeechRecognition/components/index.ts +2 -0
- package/src/tools/SpeechRecognition/core/engine/external.ts +24 -7
- package/src/tools/SpeechRecognition/core/language.ts +23 -6
- package/src/tools/SpeechRecognition/hooks/usePushToTalk.ts +36 -5
- package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +18 -11
- package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +94 -26
- package/src/tools/SpeechRecognition/widgets/index.ts +1 -1
- package/src/tools/Tree/README.md +4 -8
- package/src/tools/Tree/TreeRoot.tsx +22 -10
- package/src/tools/Tree/components/TreeContent.tsx +24 -4
- package/src/tools/Tree/components/TreeLabel.tsx +8 -2
- package/src/tools/Tree/components/TreeRow.tsx +16 -6
- package/src/tools/Tree/data/flatten.ts +10 -4
- package/src/tools/Tree/types.ts +4 -0
- package/src/tools/Uploader/components/UploadAddButton.tsx +29 -6
- package/src/tools/Uploader/components/UploadDropzone.tsx +63 -7
- package/src/tools/Uploader/components/UploadPageDropOverlay.tsx +19 -5
- package/src/tools/Uploader/components/UploadPreviewItem.tsx +47 -17
- package/src/tools/Uploader/components/UploadPreviewList.tsx +24 -12
- package/src/tools/Uploader/utils/formatters.ts +8 -3
- package/src/tools/VideoPlayer/canvas/hls-canvas.tsx +1 -0
- package/src/tools/VideoPlayer/canvas/{jsx.d.ts → jsx-augmentation.ts} +12 -19
- package/src/tools/VideoPlayer/canvas/vimeo-canvas.tsx +1 -0
- package/src/tools/VideoPlayer/canvas/youtube-canvas.tsx +1 -0
- package/src/tools/VideoPlayer/parts/fullscreen.tsx +1 -1
- package/src/tools/VideoPlayer/parts/pip.tsx +1 -1
- package/src/tools/VideoPlayer/parts/playback-rate.tsx +1 -1
- package/src/tools/VideoPlayer/parts/seek-bar.tsx +2 -2
- package/src/tools/VideoPlayer/parts/volume.tsx +2 -2
- package/src/tools/index.ts +2 -1
- package/src/tools/Chat/components/AudioToggle.tsx +0 -78
- package/src/tools/Chat/components/ChatRoot.tsx +0 -305
- package/src/tools/Chat/components/Composer.tsx +0 -216
- package/src/tools/Chat/hooks/useChatScroll.ts +0 -145
- package/src/tools/Chat/types.ts +0 -9
- package/src/tools/JsonTree/components/JsonToolbar.tsx +0 -95
- package/src/tools/JsonTree/hooks/useElementCorner.ts +0 -84
- package/src/tools/JsonTree/hooks/useNavbarHeight.ts +0 -83
- package/src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts +0 -121
- package/src/tools/Tour/README.md +0 -373
- package/src/tools/Tour/components/Tour.tsx +0 -12
- package/src/tools/Tour/components/TourContent.tsx +0 -171
- package/src/tools/Tour/components/TourNavigation.tsx +0 -77
- package/src/tools/Tour/components/TourProgress.tsx +0 -88
- package/src/tools/Tour/components/TourSpotlight.tsx +0 -199
- package/src/tools/Tour/components/index.ts +0 -5
- package/src/tools/Tour/context/TourContext.ts +0 -19
- package/src/tools/Tour/context/TourProvider.tsx +0 -292
- package/src/tools/Tour/context/index.ts +0 -2
- package/src/tools/Tour/hooks/index.ts +0 -3
- package/src/tools/Tour/hooks/useKeyboardNavigation.ts +0 -59
- package/src/tools/Tour/hooks/useStepTarget.ts +0 -121
- package/src/tools/Tour/hooks/useTour.ts +0 -42
- package/src/tools/Tour/index.ts +0 -38
- package/src/tools/Tour/types/index.ts +0 -224
- package/src/tools/Tour/utils/dom.ts +0 -98
- package/src/tools/Tour/utils/index.ts +0 -3
- package/src/tools/Tour/utils/logger.ts +0 -3
- package/src/tools/Tour/utils/scrollIntoView.ts +0 -24
- /package/src/tools/Chat/{config.ts → constants.ts} +0 -0
- /package/src/tools/Chat/launcher/{ChatHeaderAudioToggle.tsx → header/ChatHeaderAudioToggle.tsx} +0 -0
- /package/src/tools/Chat/{components → messages}/Attachments.tsx +0 -0
- /package/src/tools/Chat/{components → messages}/JumpToLatest.tsx +0 -0
- /package/src/tools/Chat/{components → messages}/MessageActions.tsx +0 -0
- /package/src/tools/Chat/{components → messages}/Sources.tsx +0 -0
- /package/src/tools/Chat/{components → messages}/StreamingIndicator.tsx +0 -0
- /package/src/tools/Chat/{components → messages}/ToolCalls.tsx +0 -0
- /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
|
-
//
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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')}`;
|
|
@@ -3,14 +3,18 @@
|
|
|
3
3
|
* shipped by `youtube-video-element`, `vimeo-video-element`,
|
|
4
4
|
* `hls-video-element`.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
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` (
|
|
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 {};
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
27
|
+
className={cn('h-8 w-20', rangeProps?.className)}
|
|
28
28
|
/>
|
|
29
29
|
)}
|
|
30
30
|
</div>
|
package/src/tools/index.ts
CHANGED
|
@@ -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 };
|