@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.
- package/README.md +9 -11
- 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 +13 -16
- 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/lazy.tsx +13 -27
- package/src/tools/AudioPlayer/parts/Meta/TimeDisplay.tsx +2 -5
- package/src/tools/Chat/README.md +267 -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 +326 -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 +106 -26
- 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/README.md +87 -230
- package/src/tools/VideoPlayer/VideoPlayer.tsx +82 -0
- package/src/tools/VideoPlayer/canvas/canvas-dispatcher.tsx +34 -0
- package/src/tools/VideoPlayer/canvas/hls-canvas.tsx +39 -0
- package/src/tools/VideoPlayer/canvas/iframe-canvas.tsx +33 -0
- package/src/tools/VideoPlayer/canvas/index.ts +12 -0
- package/src/tools/VideoPlayer/canvas/jsx-augmentation.ts +47 -0
- package/src/tools/VideoPlayer/canvas/native-canvas.tsx +38 -0
- package/src/tools/VideoPlayer/canvas/vimeo-canvas.tsx +40 -0
- package/src/tools/VideoPlayer/canvas/youtube-canvas.tsx +78 -0
- package/src/tools/VideoPlayer/index.ts +51 -65
- package/src/tools/VideoPlayer/lazy.tsx +11 -54
- package/src/tools/VideoPlayer/parts/controls-bar.tsx +35 -0
- package/src/tools/VideoPlayer/parts/fullscreen.tsx +19 -0
- package/src/tools/VideoPlayer/parts/index.ts +15 -0
- package/src/tools/VideoPlayer/parts/pip.tsx +19 -0
- package/src/tools/VideoPlayer/parts/play-button.tsx +19 -0
- package/src/tools/VideoPlayer/parts/playback-rate.tsx +31 -0
- package/src/tools/VideoPlayer/parts/poster.tsx +3 -0
- package/src/tools/VideoPlayer/parts/seek-bar.tsx +26 -0
- package/src/tools/VideoPlayer/parts/volume.tsx +32 -0
- package/src/tools/VideoPlayer/styles/video-player.css +141 -0
- package/src/tools/VideoPlayer/types.ts +82 -0
- package/src/tools/VideoPlayer/utils/parse-embed-url.ts +70 -0
- package/src/tools/VideoPlayer/utils/vimeo-id.ts +24 -0
- package/src/tools/VideoPlayer/utils/youtube-id.ts +64 -0
- package/src/tools/index.ts +37 -29
- 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/VideoPlayer/components/VideoControls.tsx +0 -138
- package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +0 -172
- package/src/tools/VideoPlayer/components/VideoPlayer.tsx +0 -201
- package/src/tools/VideoPlayer/components/index.ts +0 -14
- package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +0 -52
- package/src/tools/VideoPlayer/context/index.ts +0 -8
- package/src/tools/VideoPlayer/hooks/index.ts +0 -12
- package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +0 -71
- package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +0 -117
- package/src/tools/VideoPlayer/providers/NativeProvider.tsx +0 -284
- package/src/tools/VideoPlayer/providers/StreamProvider.tsx +0 -505
- package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +0 -397
- package/src/tools/VideoPlayer/providers/index.ts +0 -8
- package/src/tools/VideoPlayer/types/index.ts +0 -38
- package/src/tools/VideoPlayer/types/player.ts +0 -116
- package/src/tools/VideoPlayer/types/provider.ts +0 -93
- package/src/tools/VideoPlayer/types/sources.ts +0 -97
- package/src/tools/VideoPlayer/utils/debug.ts +0 -14
- package/src/tools/VideoPlayer/utils/fileSource.ts +0 -78
- package/src/tools/VideoPlayer/utils/index.ts +0 -12
- package/src/tools/VideoPlayer/utils/resolvers.ts +0 -75
- /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,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 };
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { type ReactNode, forwardRef, useEffect, useRef } from 'react';
|
|
4
|
-
import { Paperclip, Send, Square } from 'lucide-react';
|
|
5
|
-
|
|
6
|
-
import { Button, Textarea } from '@djangocfg/ui-core/components';
|
|
7
|
-
import { cn } from '@djangocfg/ui-core/lib';
|
|
8
|
-
|
|
9
|
-
import { useChatContextOptional } from '../context';
|
|
10
|
-
import type { UseChatComposerReturn } from '../hooks/useChatComposer';
|
|
11
|
-
import { Attachments } from './Attachments';
|
|
12
|
-
|
|
13
|
-
export type ComposerSize = 'sm' | 'md' | 'lg';
|
|
14
|
-
|
|
15
|
-
export interface ComposerProps {
|
|
16
|
-
composer: UseChatComposerReturn;
|
|
17
|
-
placeholder?: string;
|
|
18
|
-
disabled?: boolean;
|
|
19
|
-
showAttachmentButton?: boolean;
|
|
20
|
-
onPickFiles?: () => void;
|
|
21
|
-
toolbarStart?: ReactNode;
|
|
22
|
-
toolbarEnd?: ReactNode;
|
|
23
|
-
attachmentTray?: ReactNode;
|
|
24
|
-
className?: string;
|
|
25
|
-
textareaClassName?: string;
|
|
26
|
-
/** Visual size — controls textarea height + button slot size.
|
|
27
|
-
*
|
|
28
|
-
* - ``sm`` — 32px slot, dense compact composer (admin sidebars, etc).
|
|
29
|
-
* - ``md`` — 36px slot, default. Same as the legacy fixed size.
|
|
30
|
-
* - ``lg`` — 48px slot, generous textarea. Use when the chat is
|
|
31
|
-
* the page's primary surface (onboarding, support).
|
|
32
|
-
*/
|
|
33
|
-
size?: ComposerSize;
|
|
34
|
-
/** Show "Stop" button instead of "Send" while streaming. */
|
|
35
|
-
isStreaming?: boolean;
|
|
36
|
-
onCancel?: () => void;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const SIZE_CLASSES: Record<ComposerSize, {
|
|
40
|
-
slot: string;
|
|
41
|
-
button: string;
|
|
42
|
-
iconButton: string;
|
|
43
|
-
textarea: string;
|
|
44
|
-
text: string;
|
|
45
|
-
padding: string;
|
|
46
|
-
containerPadding: string;
|
|
47
|
-
}> = {
|
|
48
|
-
sm: {
|
|
49
|
-
slot: '[&>:not(textarea)]:h-8',
|
|
50
|
-
button: 'h-8 w-8',
|
|
51
|
-
iconButton: 'size-3.5',
|
|
52
|
-
textarea: 'min-h-8 max-h-48 px-3 py-1.5',
|
|
53
|
-
text: 'text-sm',
|
|
54
|
-
padding: 'gap-1.5',
|
|
55
|
-
containerPadding: 'px-2 pt-1.5 pb-[max(0.375rem,env(safe-area-inset-bottom))]',
|
|
56
|
-
},
|
|
57
|
-
md: {
|
|
58
|
-
slot: '[&>:not(textarea)]:h-9',
|
|
59
|
-
button: 'h-9 w-9',
|
|
60
|
-
iconButton: 'size-4',
|
|
61
|
-
textarea: 'min-h-9 max-h-60 px-3.5 py-2',
|
|
62
|
-
text: 'text-base sm:text-sm',
|
|
63
|
-
padding: 'gap-1.5',
|
|
64
|
-
containerPadding: 'px-2.5 pt-2 pb-[max(0.5rem,env(safe-area-inset-bottom))]',
|
|
65
|
-
},
|
|
66
|
-
lg: {
|
|
67
|
-
slot: '[&>:not(textarea)]:h-12',
|
|
68
|
-
button: 'h-12 w-12',
|
|
69
|
-
iconButton: 'size-5',
|
|
70
|
-
textarea: 'min-h-12 max-h-72 px-4 py-3',
|
|
71
|
-
text: 'text-base',
|
|
72
|
-
padding: 'gap-2',
|
|
73
|
-
containerPadding: 'px-3.5 pt-3 pb-[max(0.875rem,env(safe-area-inset-bottom))]',
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
export const Composer = forwardRef<HTMLDivElement, ComposerProps>(function Composer(
|
|
78
|
-
{
|
|
79
|
-
composer,
|
|
80
|
-
placeholder = 'Type a message...',
|
|
81
|
-
disabled,
|
|
82
|
-
showAttachmentButton = false,
|
|
83
|
-
onPickFiles,
|
|
84
|
-
toolbarStart,
|
|
85
|
-
toolbarEnd,
|
|
86
|
-
attachmentTray,
|
|
87
|
-
className,
|
|
88
|
-
textareaClassName,
|
|
89
|
-
size = 'md',
|
|
90
|
-
isStreaming: isStreamingProp,
|
|
91
|
-
onCancel: onCancelProp,
|
|
92
|
-
},
|
|
93
|
-
ref,
|
|
94
|
-
) {
|
|
95
|
-
const ctx = useChatContextOptional();
|
|
96
|
-
const isStreaming = isStreamingProp ?? ctx?.isStreaming ?? false;
|
|
97
|
-
const onCancel = onCancelProp ?? ctx?.cancelStream;
|
|
98
|
-
const isDisabled = disabled ?? isStreaming;
|
|
99
|
-
const sz = SIZE_CLASSES[size];
|
|
100
|
-
|
|
101
|
-
// Register the composer's focus() with the chat context so other
|
|
102
|
-
// parts of the tree (e.g. useAutoFocusOnStreamEnd) can drive it
|
|
103
|
-
// imperatively without prop-drilling a ref. No-op when used outside
|
|
104
|
-
// a ChatProvider.
|
|
105
|
-
const register = ctx?.registerComposer;
|
|
106
|
-
const composerFocus = composer.focus;
|
|
107
|
-
const composerSetValue = composer.setValue;
|
|
108
|
-
const textareaRef = composer.textareaRef;
|
|
109
|
-
// `getValue` reads the live ref instead of closing over `composer.value`
|
|
110
|
-
// so we don't need to re-register on every keystroke. Same trick for
|
|
111
|
-
// `setValue` — handler identity stays stable across renders.
|
|
112
|
-
const getValueRef = useRef<() => string>(() => composer.value);
|
|
113
|
-
getValueRef.current = () => composer.value;
|
|
114
|
-
useEffect(() => {
|
|
115
|
-
if (!register) return;
|
|
116
|
-
register({
|
|
117
|
-
focus: composerFocus,
|
|
118
|
-
moveCursorToEnd: () => {
|
|
119
|
-
const el = textareaRef.current;
|
|
120
|
-
if (!el) return;
|
|
121
|
-
const end = el.value.length;
|
|
122
|
-
el.setSelectionRange(end, end);
|
|
123
|
-
},
|
|
124
|
-
getValue: () => getValueRef.current(),
|
|
125
|
-
setValue: composerSetValue,
|
|
126
|
-
});
|
|
127
|
-
return () => register(null);
|
|
128
|
-
}, [register, composerFocus, composerSetValue, textareaRef]);
|
|
129
|
-
|
|
130
|
-
return (
|
|
131
|
-
<div
|
|
132
|
-
ref={ref}
|
|
133
|
-
className={cn(
|
|
134
|
-
'border-t border-border bg-background/95',
|
|
135
|
-
sz.containerPadding,
|
|
136
|
-
className,
|
|
137
|
-
)}
|
|
138
|
-
>
|
|
139
|
-
{composer.attachments.length > 0 ? (
|
|
140
|
-
<div className="mb-1.5">
|
|
141
|
-
{attachmentTray ?? (
|
|
142
|
-
<Attachments
|
|
143
|
-
attachments={composer.attachments}
|
|
144
|
-
onRemove={(a) => composer.removeAttachment(a.id)}
|
|
145
|
-
/>
|
|
146
|
-
)}
|
|
147
|
-
</div>
|
|
148
|
-
) : null}
|
|
149
|
-
|
|
150
|
-
{/* Size-aware slot row. ``[&>:not(textarea)]:h-{N}`` enforces a
|
|
151
|
-
* consistent slot height so toolbar buttons line up with the
|
|
152
|
-
* textarea baseline. Toolbar slots that want to opt out can
|
|
153
|
-
* pass an explicit class like `!h-auto`. */}
|
|
154
|
-
<div className={cn('flex items-end [&>:not(textarea)]:shrink-0', sz.padding, sz.slot)}>
|
|
155
|
-
{showAttachmentButton ? (
|
|
156
|
-
<Button
|
|
157
|
-
type="button"
|
|
158
|
-
variant="ghost"
|
|
159
|
-
size="icon"
|
|
160
|
-
onClick={onPickFiles}
|
|
161
|
-
aria-label="Attach files"
|
|
162
|
-
disabled={isDisabled}
|
|
163
|
-
className={sz.button}
|
|
164
|
-
>
|
|
165
|
-
<Paperclip aria-hidden className={sz.iconButton} />
|
|
166
|
-
</Button>
|
|
167
|
-
) : null}
|
|
168
|
-
|
|
169
|
-
{toolbarStart}
|
|
170
|
-
|
|
171
|
-
<Textarea
|
|
172
|
-
{...composer.textareaProps}
|
|
173
|
-
rows={1}
|
|
174
|
-
placeholder={placeholder}
|
|
175
|
-
aria-label={placeholder}
|
|
176
|
-
aria-multiline="true"
|
|
177
|
-
disabled={isDisabled}
|
|
178
|
-
className={cn(
|
|
179
|
-
'flex-1 resize-none rounded-2xl',
|
|
180
|
-
sz.textarea,
|
|
181
|
-
sz.text,
|
|
182
|
-
textareaClassName,
|
|
183
|
-
)}
|
|
184
|
-
/>
|
|
185
|
-
|
|
186
|
-
{toolbarEnd}
|
|
187
|
-
|
|
188
|
-
{isStreaming ? (
|
|
189
|
-
<Button
|
|
190
|
-
type="button"
|
|
191
|
-
variant="secondary"
|
|
192
|
-
size="icon"
|
|
193
|
-
onClick={onCancel}
|
|
194
|
-
aria-label="Stop"
|
|
195
|
-
aria-keyshortcuts="Escape"
|
|
196
|
-
className={sz.button}
|
|
197
|
-
>
|
|
198
|
-
<Square aria-hidden className={sz.iconButton} />
|
|
199
|
-
</Button>
|
|
200
|
-
) : (
|
|
201
|
-
<Button
|
|
202
|
-
type="button"
|
|
203
|
-
size="icon"
|
|
204
|
-
onClick={() => void composer.submit()}
|
|
205
|
-
disabled={!composer.canSubmit}
|
|
206
|
-
aria-label="Send"
|
|
207
|
-
aria-keyshortcuts="Enter"
|
|
208
|
-
className={sz.button}
|
|
209
|
-
>
|
|
210
|
-
<Send aria-hidden className={sz.iconButton} />
|
|
211
|
-
</Button>
|
|
212
|
-
)}
|
|
213
|
-
</div>
|
|
214
|
-
</div>
|
|
215
|
-
);
|
|
216
|
-
});
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { type RefObject, useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @deprecated Plan64. As of ui-tools 2.1.369, `<MessageList>` is
|
|
7
|
-
* virtualized via react-virtuoso and owns its own scroll viewport.
|
|
8
|
-
* Sticky-bottom + auto-follow on streaming live inside the component;
|
|
9
|
-
* use the new `MessageListProps.onAtBottomChange` prop and the
|
|
10
|
-
* `MessageListHandle.scrollToBottom()` imperative method instead.
|
|
11
|
-
*
|
|
12
|
-
* This hook is kept for hosts that render messages outside
|
|
13
|
-
* `<MessageList>` (e.g. headless story shells, custom non-virtualized
|
|
14
|
-
* scroll containers). It still works against any HTMLElement scroll
|
|
15
|
-
* container, but it does NOT integrate with Virtuoso — passing a
|
|
16
|
-
* Virtuoso-managed element here will read garbage scroll metrics.
|
|
17
|
-
*/
|
|
18
|
-
export interface UseChatScrollOptions {
|
|
19
|
-
containerRef: RefObject<HTMLElement | null>;
|
|
20
|
-
bottomRef: RefObject<HTMLElement | null>;
|
|
21
|
-
isStreaming?: boolean;
|
|
22
|
-
/** Distance from bottom (px) considered "at bottom". */
|
|
23
|
-
bottomThresholdPx?: number;
|
|
24
|
-
/** Bump key — increment when a new message arrives so the hook re-evaluates auto-scroll. */
|
|
25
|
-
messagesCount?: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface UseChatScrollReturn {
|
|
29
|
-
isAtBottom: boolean;
|
|
30
|
-
unreadCount: number;
|
|
31
|
-
scrollToBottom: (smooth?: boolean) => void;
|
|
32
|
-
resetUnread: () => void;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function useChatScroll(options: UseChatScrollOptions): UseChatScrollReturn {
|
|
36
|
-
const {
|
|
37
|
-
containerRef,
|
|
38
|
-
bottomRef,
|
|
39
|
-
isStreaming = false,
|
|
40
|
-
bottomThresholdPx = 80,
|
|
41
|
-
messagesCount = 0,
|
|
42
|
-
} = options;
|
|
43
|
-
|
|
44
|
-
const [isAtBottom, setIsAtBottom] = useState(true);
|
|
45
|
-
const [unreadCount, setUnreadCount] = useState(0);
|
|
46
|
-
const lastCountRef = useRef(messagesCount);
|
|
47
|
-
const stickyRef = useRef(true);
|
|
48
|
-
const wasStreamingRef = useRef(isStreaming);
|
|
49
|
-
|
|
50
|
-
const scrollToBottom = useCallback(
|
|
51
|
-
(smooth = false) => {
|
|
52
|
-
const el = containerRef.current;
|
|
53
|
-
if (!el) return;
|
|
54
|
-
el.scrollTo({
|
|
55
|
-
top: el.scrollHeight,
|
|
56
|
-
behavior: smooth ? 'smooth' : 'auto',
|
|
57
|
-
});
|
|
58
|
-
stickyRef.current = true;
|
|
59
|
-
setIsAtBottom(true);
|
|
60
|
-
setUnreadCount(0);
|
|
61
|
-
},
|
|
62
|
-
[containerRef],
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
const resetUnread = useCallback(() => setUnreadCount(0), []);
|
|
66
|
-
|
|
67
|
-
// Track scroll position relative to bottom.
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
const el = containerRef.current;
|
|
70
|
-
if (!el) return;
|
|
71
|
-
const onScroll = () => {
|
|
72
|
-
const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
|
|
73
|
-
const atBottom = distance <= bottomThresholdPx;
|
|
74
|
-
stickyRef.current = atBottom;
|
|
75
|
-
setIsAtBottom(atBottom);
|
|
76
|
-
if (atBottom) setUnreadCount(0);
|
|
77
|
-
};
|
|
78
|
-
onScroll();
|
|
79
|
-
el.addEventListener('scroll', onScroll, { passive: true });
|
|
80
|
-
return () => {
|
|
81
|
-
el.removeEventListener('scroll', onScroll);
|
|
82
|
-
};
|
|
83
|
-
}, [containerRef, bottomThresholdPx]);
|
|
84
|
-
|
|
85
|
-
// Stick to bottom while streaming, and one extra rAF after stream ends so
|
|
86
|
-
// the final layout (markdown re-render, sources/tool-call panels) doesn't
|
|
87
|
-
// push the latest content out of view.
|
|
88
|
-
useEffect(() => {
|
|
89
|
-
const el = containerRef.current;
|
|
90
|
-
if (!el) return;
|
|
91
|
-
|
|
92
|
-
if (isStreaming) {
|
|
93
|
-
wasStreamingRef.current = true;
|
|
94
|
-
if (!stickyRef.current) return;
|
|
95
|
-
let raf = 0;
|
|
96
|
-
const tick = () => {
|
|
97
|
-
if (!stickyRef.current) return;
|
|
98
|
-
el.scrollTop = el.scrollHeight;
|
|
99
|
-
raf = requestAnimationFrame(tick);
|
|
100
|
-
};
|
|
101
|
-
raf = requestAnimationFrame(tick);
|
|
102
|
-
return () => cancelAnimationFrame(raf);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Stream just ended — flush one more scroll on the next two frames so
|
|
106
|
-
// both markdown swap and any post-stream panel insertions are reflected.
|
|
107
|
-
if (wasStreamingRef.current && stickyRef.current) {
|
|
108
|
-
wasStreamingRef.current = false;
|
|
109
|
-
let raf1 = 0;
|
|
110
|
-
let raf2 = 0;
|
|
111
|
-
raf1 = requestAnimationFrame(() => {
|
|
112
|
-
el.scrollTop = el.scrollHeight;
|
|
113
|
-
raf2 = requestAnimationFrame(() => {
|
|
114
|
-
el.scrollTop = el.scrollHeight;
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
return () => {
|
|
118
|
-
cancelAnimationFrame(raf1);
|
|
119
|
-
cancelAnimationFrame(raf2);
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
wasStreamingRef.current = false;
|
|
123
|
-
return;
|
|
124
|
-
}, [containerRef, isStreaming]);
|
|
125
|
-
|
|
126
|
-
// On message count increase, decide whether to scroll or bump unread.
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
if (messagesCount > lastCountRef.current) {
|
|
129
|
-
if (stickyRef.current) {
|
|
130
|
-
const el = containerRef.current;
|
|
131
|
-
if (el) el.scrollTop = el.scrollHeight;
|
|
132
|
-
} else {
|
|
133
|
-
setUnreadCount((n) => n + (messagesCount - lastCountRef.current));
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
lastCountRef.current = messagesCount;
|
|
137
|
-
}, [containerRef, messagesCount]);
|
|
138
|
-
|
|
139
|
-
// Watch bottom sentinel just for symmetry/future hooks.
|
|
140
|
-
useEffect(() => {
|
|
141
|
-
void bottomRef;
|
|
142
|
-
}, [bottomRef]);
|
|
143
|
-
|
|
144
|
-
return { isAtBottom, unreadCount, scrollToBottom, resetUnread };
|
|
145
|
-
}
|
package/src/tools/Chat/types.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Backwards-compatibility re-export.
|
|
3
|
-
*
|
|
4
|
-
* Types live in `./types/` (one file per domain). Import directly from
|
|
5
|
-
* `@djangocfg/ui-tools/Chat` in new code; this barrel exists so old
|
|
6
|
-
* `import … from '.../types'` paths keep working.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export * from './types/index';
|