@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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
import type { UseChatComposerReturn } from '../hooks/useChatComposer';
|
|
6
|
+
|
|
7
|
+
/** Composer visual size — shared across the `<Composer>` API. */
|
|
8
|
+
export type ComposerSize = 'sm' | 'md' | 'lg';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Spaciousness of the input surface — orthogonal to `size`:
|
|
12
|
+
* - `compact` — default; embedded composer (FAB panel, dock).
|
|
13
|
+
* - `full` — full-page chat; roomier surface, bigger textarea + padding.
|
|
14
|
+
*/
|
|
15
|
+
export type ComposerAppearance = 'compact' | 'full';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Geometry of the input surface:
|
|
19
|
+
* - `stacked` — textarea on top, action bar pinned below, one bordered surface.
|
|
20
|
+
* - `inline` — single horizontal row (compact; `size="sm"` defaults to this).
|
|
21
|
+
*/
|
|
22
|
+
export type ComposerLayout = 'stacked' | 'inline';
|
|
23
|
+
|
|
24
|
+
/** Conditional visibility of an action, evaluated against composer state. */
|
|
25
|
+
export type ComposerActionVisibility =
|
|
26
|
+
| 'streaming'
|
|
27
|
+
| 'empty'
|
|
28
|
+
| 'hasText'
|
|
29
|
+
| 'disabled';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Declarative descriptor for one action-bar button (Tier A). The Composer
|
|
33
|
+
* renders these into consistent `<ComposerButton>`s — size, aria and tooltip
|
|
34
|
+
* are handled centrally so hosts cannot misalign anything.
|
|
35
|
+
*/
|
|
36
|
+
export interface ComposerAction {
|
|
37
|
+
/** Stable key. */
|
|
38
|
+
id: string;
|
|
39
|
+
/** Lucide icon element. */
|
|
40
|
+
icon: ReactNode;
|
|
41
|
+
/** Used as `aria-label` and tooltip — required for a11y. */
|
|
42
|
+
label: string;
|
|
43
|
+
onClick: () => void;
|
|
44
|
+
disabled?: boolean;
|
|
45
|
+
/** Toggle state → emits `aria-pressed`. */
|
|
46
|
+
pressed?: boolean;
|
|
47
|
+
/** `send` — theme-inverting filled circle (ChatGPT send button). */
|
|
48
|
+
variant?: 'ghost' | 'secondary' | 'primary' | 'send';
|
|
49
|
+
/** Hide the action when the composer is in this state. */
|
|
50
|
+
hideWhen?: ComposerActionVisibility;
|
|
51
|
+
/** Sort weight within its cluster (ascending). Built-ins reserve high values. */
|
|
52
|
+
order?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Tier A — declarative slot arrays + raw-node escape hatches. Hosts supply
|
|
57
|
+
* action descriptors; the Composer owns the rendering.
|
|
58
|
+
*/
|
|
59
|
+
export interface ComposerSlots {
|
|
60
|
+
/** Bottom-left cluster. Built-in attach prepends here when enabled. */
|
|
61
|
+
actionsStart?: ComposerAction[];
|
|
62
|
+
/** Bottom-right cluster. Built-in mic/send append here. */
|
|
63
|
+
actionsEnd?: ComposerAction[];
|
|
64
|
+
/** Full-width row above the textarea (reply banner, custom tray). */
|
|
65
|
+
blockStart?: ReactNode;
|
|
66
|
+
/** Raw nodes placed left of the textarea in the `inline` layout. */
|
|
67
|
+
inlineStart?: ReactNode;
|
|
68
|
+
/** Raw nodes placed right of the textarea in the `inline` layout. */
|
|
69
|
+
inlineEnd?: ReactNode;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── Tier B — full slot replacement ─────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
export interface SendButtonProps {
|
|
75
|
+
/** True while a reply is streaming — render the stop affordance. */
|
|
76
|
+
streaming: boolean;
|
|
77
|
+
disabled: boolean;
|
|
78
|
+
size: ComposerSize;
|
|
79
|
+
onSend: () => void;
|
|
80
|
+
onCancel?: () => void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface AttachButtonProps {
|
|
84
|
+
disabled: boolean;
|
|
85
|
+
size: ComposerSize;
|
|
86
|
+
onClick: () => void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface ComposerTextareaProps {
|
|
90
|
+
composer: UseChatComposerReturn;
|
|
91
|
+
placeholder: string;
|
|
92
|
+
disabled: boolean;
|
|
93
|
+
size: ComposerSize;
|
|
94
|
+
className?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ActionBarProps {
|
|
98
|
+
/** Already filtered + sorted by `useComposerActions`. */
|
|
99
|
+
actionsStart: ComposerAction[];
|
|
100
|
+
actionsEnd: ComposerAction[];
|
|
101
|
+
size: ComposerSize;
|
|
102
|
+
layout: ComposerLayout;
|
|
103
|
+
inlineStart?: ReactNode;
|
|
104
|
+
inlineEnd?: ReactNode;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Tier B — swap a primitive entirely. Each is optional. */
|
|
108
|
+
export interface ComposerSlotComponents {
|
|
109
|
+
SendButton?: ComponentType<SendButtonProps>;
|
|
110
|
+
AttachButton?: ComponentType<AttachButtonProps>;
|
|
111
|
+
Textarea?: ComponentType<ComposerTextareaProps>;
|
|
112
|
+
/** Replace the whole `blockEnd` action-bar row. */
|
|
113
|
+
ActionBar?: ComponentType<ActionBarProps>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Per-slot prop overrides forwarded to the built-in primitives. */
|
|
117
|
+
export interface ComposerSlotProps {
|
|
118
|
+
send?: Partial<SendButtonProps>;
|
|
119
|
+
attach?: Partial<AttachButtonProps>;
|
|
120
|
+
textarea?: { className?: string };
|
|
121
|
+
actionBar?: { className?: string };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── Footer ─────────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
/** Props for the strip below the input surface. */
|
|
127
|
+
export interface ComposerFooterProps {
|
|
128
|
+
start?: ReactNode;
|
|
129
|
+
center?: ReactNode;
|
|
130
|
+
end?: ReactNode;
|
|
131
|
+
/** Auto char counter — reads `composer.value` + `maxLength`. */
|
|
132
|
+
showCounter?: boolean;
|
|
133
|
+
/** Auto keyboard-shortcut hint — reads `submitOn`. @default false */
|
|
134
|
+
showHint?: boolean;
|
|
135
|
+
/** Live value, for the counter. */
|
|
136
|
+
value?: string;
|
|
137
|
+
/** Character cap, for the counter. */
|
|
138
|
+
maxLength?: number;
|
|
139
|
+
/** Submit binding, for the hint. */
|
|
140
|
+
submitOn?: 'enter' | 'cmd+enter';
|
|
141
|
+
size?: ComposerSize;
|
|
142
|
+
className?: string;
|
|
143
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { ArrowUp, Paperclip, Square } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
import type { UseChatComposerReturn } from '../hooks/useChatComposer';
|
|
7
|
+
import type { ComposerAction } from './types';
|
|
8
|
+
|
|
9
|
+
/** `order` values reserved for built-in actions so host extras land between. */
|
|
10
|
+
const ORDER = {
|
|
11
|
+
attach: 100,
|
|
12
|
+
hostStart: 200,
|
|
13
|
+
hostEnd: 200,
|
|
14
|
+
mic: 800,
|
|
15
|
+
send: 900,
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export interface UseComposerActionsParams {
|
|
19
|
+
composer: UseChatComposerReturn;
|
|
20
|
+
isStreaming: boolean;
|
|
21
|
+
isDisabled: boolean;
|
|
22
|
+
/** Built-in attach action wiring. */
|
|
23
|
+
showAttachmentButton?: boolean;
|
|
24
|
+
onPickFiles?: () => void;
|
|
25
|
+
attachLabel?: string;
|
|
26
|
+
/** Host-supplied declarative clusters. */
|
|
27
|
+
actionsStart?: ComposerAction[];
|
|
28
|
+
actionsEnd?: ComposerAction[];
|
|
29
|
+
/** Send / stop wiring. */
|
|
30
|
+
onSend: () => void;
|
|
31
|
+
onCancel?: () => void;
|
|
32
|
+
sendLabel?: string;
|
|
33
|
+
stopLabel?: string;
|
|
34
|
+
/** Telegram-style mic↔send swap. When true (default), mic and send never
|
|
35
|
+
* show together — mic when the draft is empty, send once there is text. */
|
|
36
|
+
micSendSwap?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ComposerActionClusters {
|
|
40
|
+
actionsStart: ComposerAction[];
|
|
41
|
+
actionsEnd: ComposerAction[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Merges built-in send/stop/attach descriptors with host-provided arrays,
|
|
46
|
+
* applies `hideWhen` against live composer state, resolves the mic↔send
|
|
47
|
+
* swap, and sorts each cluster by `order`.
|
|
48
|
+
*/
|
|
49
|
+
export function useComposerActions(params: UseComposerActionsParams): ComposerActionClusters {
|
|
50
|
+
const {
|
|
51
|
+
composer,
|
|
52
|
+
isStreaming,
|
|
53
|
+
isDisabled,
|
|
54
|
+
showAttachmentButton,
|
|
55
|
+
onPickFiles,
|
|
56
|
+
attachLabel = 'Attach files',
|
|
57
|
+
actionsStart,
|
|
58
|
+
actionsEnd,
|
|
59
|
+
onSend,
|
|
60
|
+
onCancel,
|
|
61
|
+
sendLabel = 'Send',
|
|
62
|
+
stopLabel = 'Stop',
|
|
63
|
+
micSendSwap = true,
|
|
64
|
+
} = params;
|
|
65
|
+
|
|
66
|
+
const hasText = composer.canSubmit;
|
|
67
|
+
const canSubmit = composer.canSubmit;
|
|
68
|
+
|
|
69
|
+
return useMemo(() => {
|
|
70
|
+
const start: ComposerAction[] = [];
|
|
71
|
+
const end: ComposerAction[] = [];
|
|
72
|
+
|
|
73
|
+
if (showAttachmentButton) {
|
|
74
|
+
start.push({
|
|
75
|
+
id: 'attach',
|
|
76
|
+
icon: <Paperclip aria-hidden />,
|
|
77
|
+
label: attachLabel,
|
|
78
|
+
onClick: () => onPickFiles?.(),
|
|
79
|
+
disabled: isDisabled,
|
|
80
|
+
variant: 'ghost',
|
|
81
|
+
order: ORDER.attach,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const a of actionsStart ?? []) {
|
|
86
|
+
start.push({ order: ORDER.hostStart, ...a });
|
|
87
|
+
}
|
|
88
|
+
for (const a of actionsEnd ?? []) {
|
|
89
|
+
end.push({ order: ORDER.hostEnd, ...a });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Send / stop. While streaming → stop; otherwise → send. Both are
|
|
93
|
+
// round, theme-inverting buttons (ChatGPT/Gemini style).
|
|
94
|
+
if (isStreaming) {
|
|
95
|
+
end.push({
|
|
96
|
+
id: 'stop',
|
|
97
|
+
icon: <Square aria-hidden />,
|
|
98
|
+
label: stopLabel,
|
|
99
|
+
onClick: () => onCancel?.(),
|
|
100
|
+
variant: 'send',
|
|
101
|
+
order: ORDER.send,
|
|
102
|
+
});
|
|
103
|
+
} else {
|
|
104
|
+
end.push({
|
|
105
|
+
id: 'send',
|
|
106
|
+
icon: <ArrowUp aria-hidden />,
|
|
107
|
+
label: sendLabel,
|
|
108
|
+
onClick: onSend,
|
|
109
|
+
disabled: !canSubmit,
|
|
110
|
+
variant: 'send',
|
|
111
|
+
order: ORDER.send,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Apply `hideWhen` against the live composer state.
|
|
116
|
+
const visible = (a: ComposerAction): boolean => {
|
|
117
|
+
switch (a.hideWhen) {
|
|
118
|
+
case 'streaming':
|
|
119
|
+
return !isStreaming;
|
|
120
|
+
case 'empty':
|
|
121
|
+
return hasText;
|
|
122
|
+
case 'hasText':
|
|
123
|
+
return !hasText;
|
|
124
|
+
case 'disabled':
|
|
125
|
+
return !isDisabled;
|
|
126
|
+
default:
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
let filteredEnd = end.filter(visible);
|
|
132
|
+
const filteredStart = start.filter(visible);
|
|
133
|
+
|
|
134
|
+
// Mic↔send swap: when there is text and a mic action exists, drop the
|
|
135
|
+
// mic so only send shows. The host marks its mic with `id: 'mic'`
|
|
136
|
+
// (or `hideWhen: 'hasText'`); this is a safety net for the common id.
|
|
137
|
+
if (micSendSwap && hasText) {
|
|
138
|
+
filteredEnd = filteredEnd.filter((a) => a.id !== 'mic');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const byOrder = (a: ComposerAction, b: ComposerAction) =>
|
|
142
|
+
(a.order ?? 0) - (b.order ?? 0);
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
actionsStart: filteredStart.sort(byOrder),
|
|
146
|
+
actionsEnd: filteredEnd.sort(byOrder),
|
|
147
|
+
};
|
|
148
|
+
}, [
|
|
149
|
+
showAttachmentButton,
|
|
150
|
+
attachLabel,
|
|
151
|
+
onPickFiles,
|
|
152
|
+
isDisabled,
|
|
153
|
+
actionsStart,
|
|
154
|
+
actionsEnd,
|
|
155
|
+
isStreaming,
|
|
156
|
+
stopLabel,
|
|
157
|
+
onCancel,
|
|
158
|
+
sendLabel,
|
|
159
|
+
onSend,
|
|
160
|
+
canSubmit,
|
|
161
|
+
hasText,
|
|
162
|
+
micSendSwap,
|
|
163
|
+
]);
|
|
164
|
+
}
|
|
@@ -13,10 +13,12 @@ import {
|
|
|
13
13
|
|
|
14
14
|
import type { ChatConfig, ChatLabels, ChatTransport } from '../types';
|
|
15
15
|
import { DEFAULT_LABELS } from '../types';
|
|
16
|
+
import type { BlockRegistry } from '../messages/blocks';
|
|
16
17
|
|
|
17
18
|
import { useChat, type UseChatReturn } from '../hooks/useChat';
|
|
18
19
|
import { useChatLayout, type UseChatLayoutReturn } from '../hooks/useChatLayout';
|
|
19
20
|
import { useChatAudio } from '../hooks/useChatAudio';
|
|
21
|
+
import { useStreamEndFocus } from '../hooks/useStreamEndFocus';
|
|
20
22
|
import type { ChatAudioConfig, UseChatAudioReturn } from '../core/audio/types';
|
|
21
23
|
|
|
22
24
|
/** Imperative handle a composer (built-in or custom) registers so
|
|
@@ -60,6 +62,9 @@ export interface ChatContextValue extends UseChatReturn {
|
|
|
60
62
|
* null until any composer has mounted. Plan64 follow-up. */
|
|
61
63
|
composer: ComposerHandle | null;
|
|
62
64
|
registerComposer: (handle: ComposerHandle | null) => void;
|
|
65
|
+
/** Registry of `kind` → renderer for `message.blocks`. `null` when the
|
|
66
|
+
* host wired none — `<MessageBubble>` then uses `BUILTIN_BLOCK_REGISTRY`. */
|
|
67
|
+
blockRegistry: BlockRegistry | null;
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
const Ctx = createContext<ChatContextValue | null>(null);
|
|
@@ -116,12 +121,31 @@ export interface ChatProviderProps {
|
|
|
116
121
|
audio?: ChatAudioConfig;
|
|
117
122
|
/** Enable verbose dev logging via consola. Defaults to `isDev`. */
|
|
118
123
|
debug?: boolean;
|
|
124
|
+
/** Registry of `kind` → renderer for `message.blocks`. Stored in
|
|
125
|
+
* context so nested `<ChatRoot>` / `<MessageBubble>` pick it up. */
|
|
126
|
+
blockRegistry?: BlockRegistry;
|
|
119
127
|
/**
|
|
120
128
|
* Rewrite outgoing message content before it hits the transport.
|
|
121
129
|
* History bubble keeps the original; useful for stripping rich-
|
|
122
130
|
* display chips so the LLM sees plain text. See `useChat`.
|
|
123
131
|
*/
|
|
124
132
|
onBeforeSend?: (content: string) => string | Promise<string>;
|
|
133
|
+
/**
|
|
134
|
+
* Contribute extra transport metadata, computed fresh per send.
|
|
135
|
+
* Merged over the static metadata right before `transport.stream/send`.
|
|
136
|
+
* Keeps `Chat` decoupled from any specific metadata source — the host
|
|
137
|
+
* supplies the function (e.g. the page-context snapshot from
|
|
138
|
+
* `usePageSnapshot().getChatMetadata`).
|
|
139
|
+
*/
|
|
140
|
+
getDynamicMetadata?: () => Record<string, unknown> | undefined;
|
|
141
|
+
/**
|
|
142
|
+
* Re-focus the registered composer on the streaming → idle edge —
|
|
143
|
+
* the standard chat UX (type → send → read → keep typing without
|
|
144
|
+
* reaching for the mouse). Default `true`. Works for every usage
|
|
145
|
+
* pattern (`ChatRoot`, hand-rolled `Composer` layout, headless) as
|
|
146
|
+
* long as a composer is registered. Set `false` to opt out.
|
|
147
|
+
*/
|
|
148
|
+
autoFocusOnStreamEnd?: boolean;
|
|
125
149
|
children?: ReactNode;
|
|
126
150
|
}
|
|
127
151
|
|
|
@@ -133,7 +157,10 @@ export function ChatProvider({
|
|
|
133
157
|
streaming,
|
|
134
158
|
audio,
|
|
135
159
|
debug,
|
|
160
|
+
blockRegistry,
|
|
136
161
|
onBeforeSend,
|
|
162
|
+
getDynamicMetadata,
|
|
163
|
+
autoFocusOnStreamEnd = true,
|
|
137
164
|
children,
|
|
138
165
|
}: ChatProviderProps) {
|
|
139
166
|
const audioApi = useChatAudio(audio ?? {});
|
|
@@ -155,7 +182,7 @@ export function ChatProvider({
|
|
|
155
182
|
streaming,
|
|
156
183
|
debug,
|
|
157
184
|
metadata: {
|
|
158
|
-
locale: config.
|
|
185
|
+
locale: config.prefs?.locale,
|
|
159
186
|
slug: config.slug,
|
|
160
187
|
},
|
|
161
188
|
userPersona: config.user,
|
|
@@ -164,6 +191,7 @@ export function ChatProvider({
|
|
|
164
191
|
onStreamStart,
|
|
165
192
|
onError,
|
|
166
193
|
onBeforeSend,
|
|
194
|
+
getDynamicMetadata,
|
|
167
195
|
});
|
|
168
196
|
const layout = useChatLayout({ defaultMode: 'embedded' });
|
|
169
197
|
|
|
@@ -206,9 +234,32 @@ export function ChatProvider({
|
|
|
206
234
|
setComposer(handle);
|
|
207
235
|
}, []);
|
|
208
236
|
|
|
237
|
+
// Re-focus the composer on the streaming → idle edge. Lives here (not
|
|
238
|
+
// in `ChatRoot`) so it works for *every* usage pattern — `ChatRoot`,
|
|
239
|
+
// a hand-rolled `ChatProvider` + `Composer` layout, or headless — as
|
|
240
|
+
// long as a composer registered its handle.
|
|
241
|
+
const composerRef = useRef<ComposerHandle | null>(composer);
|
|
242
|
+
composerRef.current = composer;
|
|
243
|
+
useStreamEndFocus({
|
|
244
|
+
isStreaming: chat.isStreaming,
|
|
245
|
+
enabled: autoFocusOnStreamEnd,
|
|
246
|
+
delayMs: 0,
|
|
247
|
+
resolveTarget: () => composerRef.current,
|
|
248
|
+
});
|
|
249
|
+
|
|
209
250
|
const value = useMemo<ChatContextValue>(
|
|
210
|
-
() => ({
|
|
211
|
-
|
|
251
|
+
() => ({
|
|
252
|
+
...chat,
|
|
253
|
+
layout,
|
|
254
|
+
config,
|
|
255
|
+
labels,
|
|
256
|
+
audio: audioApi,
|
|
257
|
+
hasAudio,
|
|
258
|
+
composer,
|
|
259
|
+
registerComposer,
|
|
260
|
+
blockRegistry: blockRegistry ?? null,
|
|
261
|
+
}),
|
|
262
|
+
[chat, layout, config, labels, audioApi, hasAudio, composer, registerComposer, blockRegistry],
|
|
212
263
|
);
|
|
213
264
|
|
|
214
265
|
return (
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { resolveSendMetadata, safeDynamicMetadata } from '../metadata';
|
|
4
|
+
|
|
5
|
+
describe('resolveSendMetadata', () => {
|
|
6
|
+
it('passes static metadata through when there is no contributor', () => {
|
|
7
|
+
const result = resolveSendMetadata({ locale: 'en' }, undefined);
|
|
8
|
+
expect(result).toEqual({ locale: 'en' });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('returns undefined when both static and dynamic are absent', () => {
|
|
12
|
+
expect(resolveSendMetadata(undefined, undefined)).toBeUndefined();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('merges dynamic metadata over static metadata', () => {
|
|
16
|
+
const result = resolveSendMetadata(
|
|
17
|
+
{ locale: 'en', slug: 'proj' },
|
|
18
|
+
() => ({ pageContext: { url: '/x' } }),
|
|
19
|
+
);
|
|
20
|
+
expect(result).toEqual({
|
|
21
|
+
locale: 'en',
|
|
22
|
+
slug: 'proj',
|
|
23
|
+
pageContext: { url: '/x' },
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('lets dynamic keys win on collision', () => {
|
|
28
|
+
const result = resolveSendMetadata(
|
|
29
|
+
{ locale: 'en' },
|
|
30
|
+
() => ({ locale: 'fr' }),
|
|
31
|
+
);
|
|
32
|
+
expect(result).toEqual({ locale: 'fr' });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('passes static through when the contributor returns undefined', () => {
|
|
36
|
+
const result = resolveSendMetadata({ locale: 'en' }, () => undefined);
|
|
37
|
+
expect(result).toEqual({ locale: 'en' });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('does not break the send when the contributor throws', () => {
|
|
41
|
+
const result = resolveSendMetadata({ locale: 'en' }, () => {
|
|
42
|
+
throw new Error('capture failed');
|
|
43
|
+
});
|
|
44
|
+
// Falls back to static metadata — the send must still proceed.
|
|
45
|
+
expect(result).toEqual({ locale: 'en' });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('produces a fresh object each call (no shared mutation)', () => {
|
|
49
|
+
const getDynamic = () => ({ n: 1 });
|
|
50
|
+
const a = resolveSendMetadata({ locale: 'en' }, getDynamic);
|
|
51
|
+
const b = resolveSendMetadata({ locale: 'en' }, getDynamic);
|
|
52
|
+
expect(a).not.toBe(b);
|
|
53
|
+
expect(a).toEqual(b);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('safeDynamicMetadata', () => {
|
|
58
|
+
it('returns the contributor result', () => {
|
|
59
|
+
expect(safeDynamicMetadata(() => ({ a: 1 }))).toEqual({ a: 1 });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('swallows a throw and returns undefined', () => {
|
|
63
|
+
expect(
|
|
64
|
+
safeDynamicMetadata(() => {
|
|
65
|
+
throw new Error('boom');
|
|
66
|
+
}),
|
|
67
|
+
).toBeUndefined();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -2,4 +2,26 @@ export { reducer, initialState, type ChatState, type ChatAction } from './reduce
|
|
|
2
2
|
export { createId } from './ids';
|
|
3
3
|
export { createTokenBuffer, type TokenBuffer } from './markdown';
|
|
4
4
|
export { resolvePersona, deriveInitials } from './persona';
|
|
5
|
-
export
|
|
5
|
+
export {
|
|
6
|
+
createHttpTransport,
|
|
7
|
+
createMockTransport,
|
|
8
|
+
parseSSE,
|
|
9
|
+
TransportError,
|
|
10
|
+
createPydanticAIChatTransport,
|
|
11
|
+
createToolIdQueue,
|
|
12
|
+
mapPydanticAIEvent,
|
|
13
|
+
createPydanticAISSEMap,
|
|
14
|
+
type HttpTransportConfig,
|
|
15
|
+
type MockTransportOptions,
|
|
16
|
+
type ParseSSEOptions,
|
|
17
|
+
type ChatTransport,
|
|
18
|
+
type ChatStreamEvent,
|
|
19
|
+
type CreateSessionOptions,
|
|
20
|
+
type SessionInfo,
|
|
21
|
+
type HistoryPage,
|
|
22
|
+
type StreamOptions,
|
|
23
|
+
type SendOptions,
|
|
24
|
+
type PydanticAIChatTransportOpts,
|
|
25
|
+
type PydanticAIEvent,
|
|
26
|
+
type ToolIdQueue,
|
|
27
|
+
} from './transport';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-metadata assembly.
|
|
3
|
+
*
|
|
4
|
+
* A chat send carries two kinds of metadata:
|
|
5
|
+
* - static — fixed per session (locale, slug), set once in config.
|
|
6
|
+
* - dynamic — recomputed for every send by an optional contributor.
|
|
7
|
+
*
|
|
8
|
+
* The dynamic contributor is what enables "capture-on-submit" features
|
|
9
|
+
* (e.g. a fresh page-context snapshot per message): it runs synchronously
|
|
10
|
+
* at send time, and its result is merged over the static metadata.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** A function that produces extra metadata, freshly, at send time. */
|
|
14
|
+
export type DynamicMetadataFn = () => Record<string, unknown> | undefined;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Invoke a dynamic-metadata contributor, swallowing any throw.
|
|
18
|
+
*
|
|
19
|
+
* A contributor that fails (e.g. a snapshot capture errors) must never
|
|
20
|
+
* break a chat send — the send proceeds with the static metadata only.
|
|
21
|
+
*/
|
|
22
|
+
export function safeDynamicMetadata(
|
|
23
|
+
fn: DynamicMetadataFn,
|
|
24
|
+
): Record<string, unknown> | undefined {
|
|
25
|
+
try {
|
|
26
|
+
return fn();
|
|
27
|
+
} catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Merge the static metadata with a per-send dynamic contributor.
|
|
34
|
+
*
|
|
35
|
+
* Called at each `transport.stream/send` so the contributor runs fresh.
|
|
36
|
+
* Dynamic keys win over static keys on collision. When there is no
|
|
37
|
+
* contributor (or it returns nothing) the static metadata passes
|
|
38
|
+
* through unchanged.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveSendMetadata(
|
|
41
|
+
staticMetadata: Record<string, unknown> | undefined,
|
|
42
|
+
getDynamic: DynamicMetadataFn | undefined,
|
|
43
|
+
): Record<string, unknown> | undefined {
|
|
44
|
+
const dynamic = getDynamic ? safeDynamicMetadata(getDynamic) : undefined;
|
|
45
|
+
if (!dynamic) return staticMetadata;
|
|
46
|
+
return { ...staticMetadata, ...dynamic };
|
|
47
|
+
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import type { ReactNode } from 'react';
|
|
9
9
|
|
|
10
10
|
import type { ChatToolCall } from '../types';
|
|
11
|
-
import type { ToolPayloadKind } from '../
|
|
11
|
+
import type { ToolPayloadKind } from '../messages/ToolCalls';
|
|
12
12
|
|
|
13
13
|
export interface ToolPayloadMatcher {
|
|
14
14
|
/** Cheap predicate. First match wins. */
|