@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,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope detection — find the root element(s) the DOM walk starts from.
|
|
3
|
+
*
|
|
4
|
+
* Implements the 6-tier fallback ladder: try each tier
|
|
5
|
+
* top-to-bottom, first non-empty result wins. `[data-ai-context]` is an
|
|
6
|
+
* optional refinement (Tier 1), NOT a requirement — auto-detection
|
|
7
|
+
* works on un-annotated pages.
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { isChrome } from './chrome-filter';
|
|
12
|
+
import { elementArea, isVisible, linkDensity } from './dom-utils';
|
|
13
|
+
|
|
14
|
+
/** Strategy for choosing the capture root. */
|
|
15
|
+
export type ScopeStrategy = 'container' | 'viewport' | 'full';
|
|
16
|
+
|
|
17
|
+
/** Options influencing scope resolution. */
|
|
18
|
+
export interface ScopeOptions {
|
|
19
|
+
strategy: ScopeStrategy;
|
|
20
|
+
/** Optional explicit selector; overrides auto-detection entirely. */
|
|
21
|
+
targetSelector?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Which tier produced the resolved scope (for telemetry / debugging). */
|
|
25
|
+
export type ScopeTier =
|
|
26
|
+
| 'explicit-selector'
|
|
27
|
+
| 'tier0-dialog'
|
|
28
|
+
| 'tier1-annotation'
|
|
29
|
+
| 'tier2-main'
|
|
30
|
+
| 'tier3-heuristic'
|
|
31
|
+
| 'tier4-body'
|
|
32
|
+
| 'tier5-viewport';
|
|
33
|
+
|
|
34
|
+
/** Resolved capture scope. */
|
|
35
|
+
export interface ResolvedScope {
|
|
36
|
+
/** Root element(s) to walk. Usually one; dialogs/multi-panel give more. */
|
|
37
|
+
roots: HTMLElement[];
|
|
38
|
+
/** The tier that produced this result. */
|
|
39
|
+
tier: ScopeTier;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Block-level tags considered as heuristic content candidates (Tier 3). */
|
|
43
|
+
const CANDIDATE_SELECTOR =
|
|
44
|
+
'div, section, article, form, table, ul, ol, [role="region"]';
|
|
45
|
+
|
|
46
|
+
/** Tier 0 — a visible open modal / dialog overrides everything. */
|
|
47
|
+
function tierDialog(): HTMLElement | null {
|
|
48
|
+
const selectors = [
|
|
49
|
+
'[role="dialog"][aria-modal="true"]',
|
|
50
|
+
'[role="alertdialog"][aria-modal="true"]',
|
|
51
|
+
'dialog[open]',
|
|
52
|
+
];
|
|
53
|
+
for (const sel of selectors) {
|
|
54
|
+
const nodes = Array.from(document.querySelectorAll(sel));
|
|
55
|
+
const visible = nodes.filter((n) => isVisible(n)) as HTMLElement[];
|
|
56
|
+
if (visible.length > 0) {
|
|
57
|
+
// Topmost / largest open dialog.
|
|
58
|
+
return visible.sort((a, b) => elementArea(b) - elementArea(a))[0];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Tier 1 — explicit `[data-ai-context]` annotation (optional refinement). */
|
|
65
|
+
function tierAnnotation(): HTMLElement[] {
|
|
66
|
+
const nodes = Array.from(
|
|
67
|
+
document.querySelectorAll('[data-ai-context]'),
|
|
68
|
+
) as HTMLElement[];
|
|
69
|
+
return nodes.filter(
|
|
70
|
+
(n) => isVisible(n) && n.dataset.aiContext !== 'exclude',
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Tier 2 — ARIA `<main>` / `role="main"`. */
|
|
75
|
+
function tierMain(): HTMLElement | null {
|
|
76
|
+
const nodes = Array.from(
|
|
77
|
+
document.querySelectorAll('main, [role="main"]'),
|
|
78
|
+
) as HTMLElement[];
|
|
79
|
+
const visible = nodes.filter((n) => isVisible(n));
|
|
80
|
+
if (visible.length === 0) return null;
|
|
81
|
+
if (visible.length === 1) return visible[0];
|
|
82
|
+
// Multiple — take the largest by rendered area.
|
|
83
|
+
return visible.sort((a, b) => elementArea(b) - elementArea(a))[0];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Tier 3 — heuristic largest non-chrome content block. */
|
|
87
|
+
function tierHeuristic(): HTMLElement | null {
|
|
88
|
+
const docArea = Math.max(
|
|
89
|
+
1,
|
|
90
|
+
document.documentElement.clientWidth *
|
|
91
|
+
document.documentElement.clientHeight,
|
|
92
|
+
);
|
|
93
|
+
const candidates = Array.from(
|
|
94
|
+
document.body.querySelectorAll(CANDIDATE_SELECTOR),
|
|
95
|
+
) as HTMLElement[];
|
|
96
|
+
|
|
97
|
+
let best: HTMLElement | null = null;
|
|
98
|
+
let bestScore = 0;
|
|
99
|
+
|
|
100
|
+
for (const el of candidates) {
|
|
101
|
+
if (!isVisible(el) || isChrome(el)) continue;
|
|
102
|
+
const normalizedArea = Math.min(1, elementArea(el) / docArea);
|
|
103
|
+
if (normalizedArea < 0.05) continue; // too small to be main content
|
|
104
|
+
const density = linkDensity(el);
|
|
105
|
+
// Favor large, low-link-density blocks (boilerplate is link-dense).
|
|
106
|
+
const score = normalizedArea * (1 - density);
|
|
107
|
+
if (score > bestScore) {
|
|
108
|
+
bestScore = score;
|
|
109
|
+
best = el;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return best;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Tier 5 — intersect a root with the viewport. Last resort for huge
|
|
117
|
+
* pages: returns the root itself (the walk's own viewport filtering
|
|
118
|
+
* trims off-screen nodes).
|
|
119
|
+
*/
|
|
120
|
+
function tierViewport(root: HTMLElement): HTMLElement {
|
|
121
|
+
return root;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Resolve the scope to walk.
|
|
126
|
+
*
|
|
127
|
+
* Honors an explicit `targetSelector` first; otherwise runs the ladder.
|
|
128
|
+
* The `strategy` option biases the ladder: `'viewport'` jumps to the
|
|
129
|
+
* viewport tier, `'full'` forces body-minus-chrome.
|
|
130
|
+
*/
|
|
131
|
+
export function resolveScope(options: ScopeOptions): ResolvedScope {
|
|
132
|
+
if (typeof document === 'undefined') {
|
|
133
|
+
throw new Error('resolveScope: no document (non-browser runtime).');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Explicit selector — caller knows best.
|
|
137
|
+
if (options.targetSelector) {
|
|
138
|
+
const el = document.querySelector(options.targetSelector);
|
|
139
|
+
if (el instanceof HTMLElement) {
|
|
140
|
+
return { roots: [el], tier: 'explicit-selector' };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Strategy shortcuts.
|
|
145
|
+
if (options.strategy === 'full') {
|
|
146
|
+
return { roots: [document.body], tier: 'tier4-body' };
|
|
147
|
+
}
|
|
148
|
+
if (options.strategy === 'viewport') {
|
|
149
|
+
return { roots: [tierViewport(document.body)], tier: 'tier5-viewport' };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// The ladder (strategy === 'container').
|
|
153
|
+
const dialog = tierDialog();
|
|
154
|
+
if (dialog) return { roots: [dialog], tier: 'tier0-dialog' };
|
|
155
|
+
|
|
156
|
+
const annotated = tierAnnotation();
|
|
157
|
+
if (annotated.length > 0) {
|
|
158
|
+
return { roots: annotated, tier: 'tier1-annotation' };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const main = tierMain();
|
|
162
|
+
if (main) return { roots: [main], tier: 'tier2-main' };
|
|
163
|
+
|
|
164
|
+
const heuristic = tierHeuristic();
|
|
165
|
+
if (heuristic) return { roots: [heuristic], tier: 'tier3-heuristic' };
|
|
166
|
+
|
|
167
|
+
// Tier 4 — body minus chrome (chrome is filtered during the walk).
|
|
168
|
+
return { roots: [document.body], tier: 'tier4-body' };
|
|
169
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM → CST walker.
|
|
3
|
+
*
|
|
4
|
+
* Recursively walks a scope root into CST nodes: visibility filtering,
|
|
5
|
+
* chrome exclusion, role classification, ref assignment, and a
|
|
6
|
+
* redaction hook for every captured value/text.
|
|
7
|
+
*
|
|
8
|
+
* Layout reads are confined to dom-utils helpers; the walk itself does
|
|
9
|
+
* no interleaved geometry queries beyond the up-front visibility check.
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
CSTContainerNode,
|
|
15
|
+
CSTInteractiveNode,
|
|
16
|
+
CSTNode,
|
|
17
|
+
CSTRefId,
|
|
18
|
+
CSTTextNode,
|
|
19
|
+
} from '../cst/types';
|
|
20
|
+
import { accessibleName } from './accessible-name';
|
|
21
|
+
import {
|
|
22
|
+
classify,
|
|
23
|
+
containerRole,
|
|
24
|
+
interactiveRole,
|
|
25
|
+
} from './classify';
|
|
26
|
+
import { isChrome } from './chrome-filter';
|
|
27
|
+
import {
|
|
28
|
+
PLACEHOLDER_TAGS,
|
|
29
|
+
SKIP_TAGS,
|
|
30
|
+
isVisible,
|
|
31
|
+
tagName,
|
|
32
|
+
} from './dom-utils';
|
|
33
|
+
|
|
34
|
+
/** A value passed through redaction before entering the tree. */
|
|
35
|
+
export type RedactValue = (value: string, context: RedactContext) => string;
|
|
36
|
+
|
|
37
|
+
/** Context for a redaction decision. */
|
|
38
|
+
export interface RedactContext {
|
|
39
|
+
/** Element the value came from. */
|
|
40
|
+
element: HTMLElement;
|
|
41
|
+
/** Where the value sits — an input value or static text. */
|
|
42
|
+
kind: 'value' | 'text';
|
|
43
|
+
/** Ref id assigned to the element, if any. */
|
|
44
|
+
ref?: CSTRefId;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** No-op redaction — passes values through unchanged. */
|
|
48
|
+
export const noopRedact: RedactValue = (value) => value;
|
|
49
|
+
|
|
50
|
+
/** Result of walking the DOM under a scope root. */
|
|
51
|
+
export interface WalkResult {
|
|
52
|
+
/** Captured child nodes (the root node is assembled by the engine). */
|
|
53
|
+
children: CSTNode[];
|
|
54
|
+
/** Map of assigned ref id → element — kept so directives can later
|
|
55
|
+
* resolve a CST ref back to the live DOM node. */
|
|
56
|
+
refMap: Map<CSTRefId, HTMLElement>;
|
|
57
|
+
/** Number of elements visited. */
|
|
58
|
+
nodesVisited: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Options for a walk. */
|
|
62
|
+
export interface WalkOptions {
|
|
63
|
+
/** Redaction hook; defaults to no-op. */
|
|
64
|
+
redactValue?: RedactValue;
|
|
65
|
+
/** Hard cap on elements visited (safety valve); default 50_000. */
|
|
66
|
+
maxNodes?: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Mutable state threaded through one walk. */
|
|
70
|
+
interface WalkState {
|
|
71
|
+
redact: RedactValue;
|
|
72
|
+
refMap: Map<CSTRefId, HTMLElement>;
|
|
73
|
+
refCounter: number;
|
|
74
|
+
visited: number;
|
|
75
|
+
maxNodes: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Minimum trimmed length for a text node to be worth keeping. */
|
|
79
|
+
const MIN_TEXT_LEN = 2;
|
|
80
|
+
|
|
81
|
+
/** Read the current value of a form control as a string. */
|
|
82
|
+
function controlValue(el: Element): string | undefined {
|
|
83
|
+
if (el instanceof HTMLInputElement) {
|
|
84
|
+
if (el.type === 'checkbox' || el.type === 'radio') return undefined;
|
|
85
|
+
return el.value || undefined;
|
|
86
|
+
}
|
|
87
|
+
if (el instanceof HTMLTextAreaElement) return el.value || undefined;
|
|
88
|
+
if (el instanceof HTMLSelectElement) {
|
|
89
|
+
return el.selectedOptions[0]?.textContent?.trim() || undefined;
|
|
90
|
+
}
|
|
91
|
+
if (el.getAttribute('contenteditable') === 'true') {
|
|
92
|
+
return el.textContent?.trim() || undefined;
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Build an interactive CST node for an element. */
|
|
98
|
+
function buildInteractive(el: HTMLElement, state: WalkState): CSTInteractiveNode {
|
|
99
|
+
const role = interactiveRole(el)!;
|
|
100
|
+
const ref = `@e${state.refCounter++}` as CSTRefId;
|
|
101
|
+
state.refMap.set(ref, el);
|
|
102
|
+
|
|
103
|
+
const rawValue = controlValue(el);
|
|
104
|
+
const value =
|
|
105
|
+
rawValue === undefined
|
|
106
|
+
? undefined
|
|
107
|
+
: state.redact(rawValue, { element: el, kind: 'value', ref });
|
|
108
|
+
|
|
109
|
+
const node: CSTInteractiveNode = {
|
|
110
|
+
type: 'interactive',
|
|
111
|
+
role,
|
|
112
|
+
ref,
|
|
113
|
+
name: accessibleName(el),
|
|
114
|
+
};
|
|
115
|
+
if (value !== undefined) node.value = value;
|
|
116
|
+
|
|
117
|
+
const placeholder = el.getAttribute('placeholder');
|
|
118
|
+
if (placeholder) node.placeholder = placeholder;
|
|
119
|
+
|
|
120
|
+
if (el instanceof HTMLInputElement) {
|
|
121
|
+
if (el.type === 'checkbox' || el.type === 'radio') {
|
|
122
|
+
node.checked = el.checked;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const ariaChecked = el.getAttribute('aria-checked');
|
|
126
|
+
if (ariaChecked === 'true' || ariaChecked === 'false') {
|
|
127
|
+
node.checked = ariaChecked === 'true';
|
|
128
|
+
}
|
|
129
|
+
const ariaExpanded = el.getAttribute('aria-expanded');
|
|
130
|
+
if (ariaExpanded === 'true' || ariaExpanded === 'false') {
|
|
131
|
+
node.expanded = ariaExpanded === 'true';
|
|
132
|
+
}
|
|
133
|
+
if (
|
|
134
|
+
'disabled' in el &&
|
|
135
|
+
(el as HTMLInputElement).disabled === true
|
|
136
|
+
) {
|
|
137
|
+
node.disabled = true;
|
|
138
|
+
}
|
|
139
|
+
if (el.getAttribute('aria-disabled') === 'true') node.disabled = true;
|
|
140
|
+
if (
|
|
141
|
+
el.getAttribute('required') !== null ||
|
|
142
|
+
el.getAttribute('aria-required') === 'true'
|
|
143
|
+
) {
|
|
144
|
+
node.required = true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return node;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Collect direct-child text not owned by a child element. */
|
|
151
|
+
function ownText(el: Element, state: WalkState): string {
|
|
152
|
+
let text = '';
|
|
153
|
+
el.childNodes.forEach((n) => {
|
|
154
|
+
if (n.nodeType === 3 /* TEXT_NODE */) {
|
|
155
|
+
text += n.textContent ?? '';
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
const trimmed = text.replace(/\s+/g, ' ').trim();
|
|
159
|
+
if (trimmed.length < MIN_TEXT_LEN) return '';
|
|
160
|
+
return state.redact(trimmed, {
|
|
161
|
+
element: el as HTMLElement,
|
|
162
|
+
kind: 'text',
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Walk one element into zero or more CST nodes. */
|
|
167
|
+
function walkElement(el: Element, state: WalkState): CSTNode[] {
|
|
168
|
+
if (state.visited >= state.maxNodes) return [];
|
|
169
|
+
state.visited++;
|
|
170
|
+
|
|
171
|
+
const tag = tagName(el);
|
|
172
|
+
if (SKIP_TAGS.has(tag)) return [];
|
|
173
|
+
if (!(el instanceof HTMLElement)) return [];
|
|
174
|
+
if (isChrome(el)) return [];
|
|
175
|
+
if (!isVisible(el)) return [];
|
|
176
|
+
|
|
177
|
+
// data-ai-redact — drop the whole subtree, replace with a placeholder.
|
|
178
|
+
// Checked on the element itself; the walk descends top-down so an
|
|
179
|
+
// annotated ancestor is caught before any descendant is reached.
|
|
180
|
+
if (el.hasAttribute('data-ai-redact')) {
|
|
181
|
+
return [{ type: 'text', content: '[redacted]' } satisfies CSTTextNode];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Canvas / SVG / iframe — placeholder, no descent (also redaction-safe).
|
|
185
|
+
if (PLACEHOLDER_TAGS.has(tag)) {
|
|
186
|
+
const label = accessibleName(el) || tag;
|
|
187
|
+
return [{ type: 'text', content: `[${label}]` } satisfies CSTTextNode];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const kind = classify(el);
|
|
191
|
+
|
|
192
|
+
if (kind === 'interactive') {
|
|
193
|
+
return [buildInteractive(el, state)];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Container or text-host: gather children, plus own text.
|
|
197
|
+
const childNodes: CSTNode[] = [];
|
|
198
|
+
const text = ownText(el, state);
|
|
199
|
+
if (text) childNodes.push({ type: 'text', content: text });
|
|
200
|
+
for (const child of Array.from(el.children)) {
|
|
201
|
+
childNodes.push(...walkElement(child, state));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (childNodes.length === 0) return [];
|
|
205
|
+
|
|
206
|
+
if (kind === 'container') {
|
|
207
|
+
const node: CSTContainerNode = {
|
|
208
|
+
type: 'container',
|
|
209
|
+
role: containerRole(el)!,
|
|
210
|
+
children: childNodes,
|
|
211
|
+
};
|
|
212
|
+
const name = accessibleName(el);
|
|
213
|
+
if (name && name.length <= 80) node.name = name;
|
|
214
|
+
return [node];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// text-host: a plain wrapper — flatten, don't add a tree level.
|
|
218
|
+
return childNodes;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Walk the DOM subtree under `root` into CST child nodes.
|
|
223
|
+
*/
|
|
224
|
+
export function walkDOM(
|
|
225
|
+
root: HTMLElement,
|
|
226
|
+
options: WalkOptions = {},
|
|
227
|
+
): WalkResult {
|
|
228
|
+
const state: WalkState = {
|
|
229
|
+
redact: options.redactValue ?? noopRedact,
|
|
230
|
+
refMap: new Map(),
|
|
231
|
+
refCounter: 1,
|
|
232
|
+
visited: 0,
|
|
233
|
+
maxNodes: options.maxNodes ?? 50_000,
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Walk the root's children — the root element itself becomes the CST
|
|
237
|
+
// root node, assembled by the engine.
|
|
238
|
+
const children: CSTNode[] = [];
|
|
239
|
+
const text = ownText(root, state);
|
|
240
|
+
if (text) children.push({ type: 'text', content: text });
|
|
241
|
+
for (const child of Array.from(root.children)) {
|
|
242
|
+
children.push(...walkElement(child, state));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
children,
|
|
247
|
+
refMap: state.refMap,
|
|
248
|
+
nodesVisited: state.visited,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { serializeCST } from '../serialize';
|
|
4
|
+
import type { CSTRootNode } from '../types';
|
|
5
|
+
|
|
6
|
+
const sampleRoot: CSTRootNode = {
|
|
7
|
+
type: 'root',
|
|
8
|
+
title: 'Billing Settings',
|
|
9
|
+
url: 'https://app.example.com/settings/billing',
|
|
10
|
+
children: [
|
|
11
|
+
{
|
|
12
|
+
type: 'container',
|
|
13
|
+
role: 'form',
|
|
14
|
+
name: 'Billing',
|
|
15
|
+
children: [
|
|
16
|
+
{
|
|
17
|
+
type: 'interactive',
|
|
18
|
+
role: 'textbox',
|
|
19
|
+
ref: '@e1',
|
|
20
|
+
name: 'Company',
|
|
21
|
+
value: 'Acme',
|
|
22
|
+
},
|
|
23
|
+
{ type: 'text', content: 'Configure your subscription.' },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
describe('serializeCST', () => {
|
|
30
|
+
it('produces a deterministic string for the same tree', () => {
|
|
31
|
+
expect(serializeCST(sampleRoot)).toBe(serializeCST(sampleRoot));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('drops undefined optional fields', () => {
|
|
35
|
+
const json = serializeCST(sampleRoot);
|
|
36
|
+
// The textbox has no `placeholder` — it must not appear.
|
|
37
|
+
expect(json).not.toContain('placeholder');
|
|
38
|
+
expect(json).toContain('"ref":"@e1"');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('emits keys in a fixed order regardless of input key order', () => {
|
|
42
|
+
const reordered: CSTRootNode = {
|
|
43
|
+
children: sampleRoot.children,
|
|
44
|
+
url: sampleRoot.url,
|
|
45
|
+
type: 'root',
|
|
46
|
+
title: sampleRoot.title,
|
|
47
|
+
};
|
|
48
|
+
expect(serializeCST(reordered)).toBe(serializeCST(sampleRoot));
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI directives — how the AI points at the user's screen.
|
|
3
|
+
*
|
|
4
|
+
* The chat response can carry directives; for now the only one is
|
|
5
|
+
* `point` (highlight / focus an element). The union is a discriminated
|
|
6
|
+
* type so a future write-style directive (e.g. `fill`) can be added
|
|
7
|
+
* without breaking older clients — unknown `type`s are simply ignored.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { CSTRefId } from './types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Point at an element: highlight and/or focus it. Read-only — neither
|
|
14
|
+
* changes the user's data.
|
|
15
|
+
*/
|
|
16
|
+
export interface PointDirective {
|
|
17
|
+
type: 'point';
|
|
18
|
+
/** CST ref of the target element. */
|
|
19
|
+
ref: CSTRefId;
|
|
20
|
+
/** Draw the highlight overlay (default: true). */
|
|
21
|
+
highlight?: boolean;
|
|
22
|
+
/** Call element.focus() once visible (default: false). */
|
|
23
|
+
focus?: boolean;
|
|
24
|
+
/** Optional short caption shown beside the highlight. */
|
|
25
|
+
label?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The directive union. MVP ships `point` only.
|
|
30
|
+
*
|
|
31
|
+
* Level 3 (future, NOT built) would add:
|
|
32
|
+
* | { type: 'fill'; ref: CSTRefId; value: string }
|
|
33
|
+
*
|
|
34
|
+
* Any consumer must `switch` on `type` and ignore unknown variants —
|
|
35
|
+
* never throw — so an older client meeting a newer directive is safe.
|
|
36
|
+
*/
|
|
37
|
+
export type UIDirective = PointDirective;
|
|
38
|
+
|
|
39
|
+
/** Carried in the structured side-channel of the SSE response. */
|
|
40
|
+
export interface ChatResponseMeta {
|
|
41
|
+
directives?: UIDirective[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Type guard: is this a `point` directive. */
|
|
45
|
+
export function isPointDirective(d: UIDirective): d is PointDirective {
|
|
46
|
+
return d.type === 'point';
|
|
47
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The request-side payload: a snapshot plus its route context and
|
|
3
|
+
* metadata, sent to the backend as a separate field of the chat request.
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { CSTRootNode } from './types';
|
|
8
|
+
|
|
9
|
+
/** Current CST schema version. Client and server check this. */
|
|
10
|
+
export const CST_SCHEMA_VERSION = '1.0.0';
|
|
11
|
+
|
|
12
|
+
/** Snapshot representation tag. Only CST for now. */
|
|
13
|
+
export type SnapshotRepresentation = 'CST';
|
|
14
|
+
|
|
15
|
+
/** Metadata describing how a snapshot was produced. */
|
|
16
|
+
export interface SnapshotMetadata {
|
|
17
|
+
/** Representation format. */
|
|
18
|
+
representation: SnapshotRepresentation;
|
|
19
|
+
/** Approximate token count of the serialized snapshot. */
|
|
20
|
+
tokenEstimate: number;
|
|
21
|
+
/** Epoch ms when the snapshot was captured. */
|
|
22
|
+
captureTimestamp: number;
|
|
23
|
+
/** CST schema version. */
|
|
24
|
+
schemaVersion: string;
|
|
25
|
+
/** Stable content hash — used for staleness detection. */
|
|
26
|
+
contentHash: string;
|
|
27
|
+
/** How many nodes/values were redacted during capture. */
|
|
28
|
+
redactedCount: number;
|
|
29
|
+
/** How many repetitive sibling chains were folded. */
|
|
30
|
+
foldedCount: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The full page-context payload. Travels as its own request field —
|
|
35
|
+
* never concatenated into the user's message.
|
|
36
|
+
*/
|
|
37
|
+
export interface PageContextPayload {
|
|
38
|
+
/** Full URL at capture time. */
|
|
39
|
+
url: string;
|
|
40
|
+
/** Route pathname. */
|
|
41
|
+
route: string;
|
|
42
|
+
/** Route params, if resolvable. */
|
|
43
|
+
params: Record<string, string>;
|
|
44
|
+
/** document.title. */
|
|
45
|
+
title: string;
|
|
46
|
+
/** The captured tree. */
|
|
47
|
+
snapshot: CSTRootNode;
|
|
48
|
+
/** Capture metadata. */
|
|
49
|
+
metadata: SnapshotMetadata;
|
|
50
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CST serialization — tree → deterministic JSON string.
|
|
3
|
+
*
|
|
4
|
+
* Key order is fixed so the same tree always produces the same string,
|
|
5
|
+
* which makes the content hash (staleness detection) stable.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
CSTContainerNode,
|
|
10
|
+
CSTInteractiveNode,
|
|
11
|
+
CSTNode,
|
|
12
|
+
CSTRootNode,
|
|
13
|
+
CSTTextNode,
|
|
14
|
+
} from './types';
|
|
15
|
+
|
|
16
|
+
/** Build a plain object with keys in a fixed order (undefined dropped). */
|
|
17
|
+
function ordered(entries: Array<[string, unknown]>): Record<string, unknown> {
|
|
18
|
+
const out: Record<string, unknown> = {};
|
|
19
|
+
for (const [key, value] of entries) {
|
|
20
|
+
if (value !== undefined) out[key] = value;
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Normalize one node into a deterministic plain object. */
|
|
26
|
+
function normalizeNode(node: CSTNode): Record<string, unknown> {
|
|
27
|
+
switch (node.type) {
|
|
28
|
+
case 'root':
|
|
29
|
+
return normalizeRoot(node);
|
|
30
|
+
case 'interactive':
|
|
31
|
+
return normalizeInteractive(node);
|
|
32
|
+
case 'container':
|
|
33
|
+
return normalizeContainer(node);
|
|
34
|
+
case 'text':
|
|
35
|
+
return normalizeText(node);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeRoot(node: CSTRootNode): Record<string, unknown> {
|
|
40
|
+
return ordered([
|
|
41
|
+
['type', node.type],
|
|
42
|
+
['title', node.title],
|
|
43
|
+
['url', node.url],
|
|
44
|
+
['children', node.children.map(normalizeNode)],
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeInteractive(
|
|
49
|
+
node: CSTInteractiveNode,
|
|
50
|
+
): Record<string, unknown> {
|
|
51
|
+
return ordered([
|
|
52
|
+
['type', node.type],
|
|
53
|
+
['role', node.role],
|
|
54
|
+
['ref', node.ref],
|
|
55
|
+
['name', node.name],
|
|
56
|
+
['value', node.value],
|
|
57
|
+
['placeholder', node.placeholder],
|
|
58
|
+
['disabled', node.disabled],
|
|
59
|
+
['checked', node.checked],
|
|
60
|
+
['expanded', node.expanded],
|
|
61
|
+
['required', node.required],
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeContainer(node: CSTContainerNode): Record<string, unknown> {
|
|
66
|
+
return ordered([
|
|
67
|
+
['type', node.type],
|
|
68
|
+
['role', node.role],
|
|
69
|
+
['name', node.name],
|
|
70
|
+
['children', node.children.map(normalizeNode)],
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function normalizeText(node: CSTTextNode): Record<string, unknown> {
|
|
75
|
+
return ordered([
|
|
76
|
+
['type', node.type],
|
|
77
|
+
['content', node.content],
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Serialize a CST tree to a deterministic JSON string. */
|
|
82
|
+
export function serializeCST(node: CSTNode): string {
|
|
83
|
+
return JSON.stringify(normalizeNode(node));
|
|
84
|
+
}
|