@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,10 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hook for rendering Mermaid diagrams with
|
|
2
|
+
* Hook for rendering Mermaid diagrams with debounced, race-safe rendering.
|
|
3
|
+
*
|
|
4
|
+
* Mermaid (~800KB) is imported eagerly here because the whole component
|
|
5
|
+
* tree is already lazy-loaded behind `Mermaid.client` — splitting again
|
|
6
|
+
* would just add a second waterfall for no win.
|
|
3
7
|
*/
|
|
4
8
|
|
|
5
9
|
import mermaid from 'mermaid';
|
|
6
10
|
import { useEffect, useRef, useState } from 'react';
|
|
7
11
|
|
|
12
|
+
import {
|
|
13
|
+
applyMermaidErRowColors,
|
|
14
|
+
applyMermaidTextColors,
|
|
15
|
+
getThemeColor,
|
|
16
|
+
getTextColor,
|
|
17
|
+
isVerticalDiagram,
|
|
18
|
+
} from '../utils/mermaid-helpers';
|
|
8
19
|
import { useMermaidCleanup } from './useMermaidCleanup';
|
|
9
20
|
import { useMermaidValidation } from './useMermaidValidation';
|
|
10
21
|
|
|
@@ -12,6 +23,8 @@ interface UseMermaidRendererProps {
|
|
|
12
23
|
chart: string;
|
|
13
24
|
theme: string;
|
|
14
25
|
isCompact?: boolean;
|
|
26
|
+
/** Debounce window in ms before (re)rendering. Default 300. */
|
|
27
|
+
debounceMs?: number;
|
|
15
28
|
}
|
|
16
29
|
|
|
17
30
|
interface MermaidRenderResult {
|
|
@@ -19,215 +32,298 @@ interface MermaidRenderResult {
|
|
|
19
32
|
svgContent: string;
|
|
20
33
|
isVertical: boolean;
|
|
21
34
|
isRendering: boolean;
|
|
35
|
+
/** Set when the last render failed with a syntax / parse error. */
|
|
36
|
+
error: string | null;
|
|
22
37
|
}
|
|
23
38
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Section color scales for timeline / journey / mindmap / pie diagrams.
|
|
41
|
+
*
|
|
42
|
+
* These diagram types do NOT use `mainBkg` for their boxes — they cycle
|
|
43
|
+
* through `cScale0..N` (and `pie1..N`) instead. Mermaid's `base` theme
|
|
44
|
+
* derives those scales from `primaryColor`, which lands far too dark in
|
|
45
|
+
* light mode (dark box) while the section label still inherits the
|
|
46
|
+
* default dark `textColor` — i.e. dark text on a dark box.
|
|
47
|
+
*
|
|
48
|
+
* We pin an explicit, theme-aware palette: mid-saturation backgrounds
|
|
49
|
+
* that read on either page background, each paired with an explicit
|
|
50
|
+
* contrasting label color via `cScaleLabel*`. `cScalePeer*` colors the
|
|
51
|
+
* sub-task boxes that sit under a section.
|
|
52
|
+
*/
|
|
53
|
+
const SECTION_SCALES = {
|
|
54
|
+
light: [
|
|
55
|
+
{ bg: '#1f6f8b', label: '#ffffff', peer: '#3a8ba6' },
|
|
56
|
+
{ bg: '#7b5ea7', label: '#ffffff', peer: '#977dc0' },
|
|
57
|
+
{ bg: '#2e8b57', label: '#ffffff', peer: '#4caf7d' },
|
|
58
|
+
{ bg: '#c25a3a', label: '#ffffff', peer: '#d6805f' },
|
|
59
|
+
{ bg: '#3a6ea5', label: '#ffffff', peer: '#5d8cc0' },
|
|
60
|
+
{ bg: '#a8456b', label: '#ffffff', peer: '#c06b8b' },
|
|
61
|
+
{ bg: '#5f8f3a', label: '#ffffff', peer: '#80aa5d' },
|
|
62
|
+
{ bg: '#9a7d2e', label: '#ffffff', peer: '#b89c50' },
|
|
63
|
+
],
|
|
64
|
+
dark: [
|
|
65
|
+
{ bg: '#3aa6c9', label: '#0b1620', peer: '#2b7d99' },
|
|
66
|
+
{ bg: '#b39ddb', label: '#1a142b', peer: '#8a72b5' },
|
|
67
|
+
{ bg: '#66c990', label: '#0c1f15', peer: '#479a6b' },
|
|
68
|
+
{ bg: '#e8956f', label: '#2a1409', peer: '#bd6f4c' },
|
|
69
|
+
{ bg: '#79a8d9', label: '#0d1726', peer: '#577fad' },
|
|
70
|
+
{ bg: '#d987a8', label: '#2a0f1b', peer: '#ad6082' },
|
|
71
|
+
{ bg: '#a4cf7a', label: '#142008', peer: '#7da352' },
|
|
72
|
+
{ bg: '#d4bb6e', label: '#241c08', peer: '#a8924c' },
|
|
73
|
+
],
|
|
74
|
+
} as const;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build Mermaid `themeVariables` from our semantic tokens.
|
|
78
|
+
*
|
|
79
|
+
* Tokens are read live from the DOM so the diagram tracks light/dark
|
|
80
|
+
* without a hard-coded palette. Fallbacks only fire during SSR or before
|
|
81
|
+
* stylesheets load.
|
|
82
|
+
*/
|
|
83
|
+
function buildThemeVariables(theme: string, fontSize: string) {
|
|
84
|
+
const isDark = theme === 'dark';
|
|
85
|
+
const fg = getThemeColor('--foreground', isDark ? 'hsl(0 0% 98%)' : 'hsl(0 0% 9%)');
|
|
86
|
+
const card = getThemeColor('--card', isDark ? 'hsl(0 0% 8%)' : 'hsl(0 0% 100%)');
|
|
87
|
+
const muted = getThemeColor('--muted', isDark ? 'hsl(0 0% 15%)' : 'hsl(0 0% 96%)');
|
|
88
|
+
const border = getThemeColor('--border', isDark ? 'hsl(0 0% 15%)' : 'hsl(0 0% 90%)');
|
|
89
|
+
const primary = getThemeColor('--primary', isDark ? 'hsl(189 100% 50%)' : 'hsl(192 90% 35%)');
|
|
90
|
+
const accent = getThemeColor('--accent', muted);
|
|
91
|
+
const secondary = getThemeColor('--secondary', muted);
|
|
92
|
+
const background = getThemeColor('--background', isDark ? 'hsl(0 0% 4%)' : 'hsl(0 0% 94%)');
|
|
93
|
+
const destructive = getThemeColor('--destructive', 'hsl(0 84% 60%)');
|
|
94
|
+
const destructiveFg = getThemeColor('--destructive-foreground', 'hsl(0 0% 98%)');
|
|
95
|
+
|
|
96
|
+
const scales = SECTION_SCALES[isDark ? 'dark' : 'light'];
|
|
97
|
+
// `cScale*` / `pie*` / `fillType*` are flat keys (cScale0, pie1, ...).
|
|
98
|
+
//
|
|
99
|
+
// `fillType*` is what the **journey** diagram actually paints its
|
|
100
|
+
// section / task rects with. Left unset, Mermaid derives it by
|
|
101
|
+
// rotating the hue of `cScale*` and forcing odd indexes to
|
|
102
|
+
// `hsl(H, 0%, 9%)` — a near-black box. Pinning `fillType*` to our
|
|
103
|
+
// explicit palette kills the dark-on-dark sections.
|
|
104
|
+
const sectionVars: Record<string, string> = {};
|
|
105
|
+
scales.forEach((s, i) => {
|
|
106
|
+
sectionVars[`cScale${i}`] = s.bg;
|
|
107
|
+
sectionVars[`cScaleLabel${i}`] = s.label;
|
|
108
|
+
sectionVars[`cScaleInv${i}`] = s.label;
|
|
109
|
+
sectionVars[`cScalePeer${i}`] = s.peer;
|
|
110
|
+
sectionVars[`fillType${i}`] = s.bg;
|
|
111
|
+
sectionVars[`surface${i}`] = s.bg;
|
|
112
|
+
// Pie slices are 1-indexed and have no separate label var — the
|
|
113
|
+
// slice text color is global (`pieSectionTextColor`).
|
|
114
|
+
sectionVars[`pie${i + 1}`] = s.bg;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
primaryColor: primary,
|
|
119
|
+
primaryTextColor: fg,
|
|
120
|
+
primaryBorderColor: primary,
|
|
121
|
+
secondaryColor: secondary,
|
|
122
|
+
secondaryTextColor: fg,
|
|
123
|
+
secondaryBorderColor: border,
|
|
124
|
+
tertiaryColor: accent,
|
|
125
|
+
tertiaryTextColor: fg,
|
|
126
|
+
tertiaryBorderColor: border,
|
|
127
|
+
mainBkg: card,
|
|
128
|
+
textColor: fg,
|
|
129
|
+
nodeBorder: border,
|
|
130
|
+
nodeTextColor: fg,
|
|
131
|
+
secondBkg: muted,
|
|
132
|
+
lineColor: primary,
|
|
133
|
+
edgeLabelBackground: card,
|
|
134
|
+
clusterBkg: muted,
|
|
135
|
+
clusterBorder: primary,
|
|
136
|
+
background,
|
|
137
|
+
labelBackground: card,
|
|
138
|
+
labelTextColor: fg,
|
|
139
|
+
errorBkgColor: destructive,
|
|
140
|
+
errorTextColor: destructiveFg,
|
|
141
|
+
|
|
142
|
+
// --- ER diagram ---
|
|
143
|
+
// ER attribute-row zebra fills are derived from `mainBkg` by
|
|
144
|
+
// Mermaid and ignore `themeVariables` — they are re-asserted
|
|
145
|
+
// post-render via `applyMermaidErRowColors`.
|
|
146
|
+
|
|
147
|
+
// --- Section-colored diagrams (timeline / journey / mindmap) ---
|
|
148
|
+
...sectionVars,
|
|
149
|
+
// Timeline / journey section title bars + the global label fallback.
|
|
150
|
+
cScaleLabel0: scales[0]?.label ?? fg,
|
|
151
|
+
// Journey actor faces / labels in the legend.
|
|
152
|
+
actorBkg: card,
|
|
153
|
+
actorBorder: border,
|
|
154
|
+
actorTextColor: fg,
|
|
155
|
+
actorLineColor: border,
|
|
156
|
+
// Mindmap nodes inherit `cScale*`; their text uses `nodeTextColor`
|
|
157
|
+
// on the outer ring — keep it readable on the page background.
|
|
158
|
+
|
|
159
|
+
// --- Pie chart ---
|
|
160
|
+
pieTitleTextColor: fg,
|
|
161
|
+
pieSectionTextColor: isDark ? '#0b1620' : '#ffffff',
|
|
162
|
+
pieSectionTextSize: '13px',
|
|
163
|
+
pieLegendTextColor: fg,
|
|
164
|
+
pieLegendTextSize: '13px',
|
|
165
|
+
pieStrokeColor: background,
|
|
166
|
+
pieStrokeWidth: '2px',
|
|
167
|
+
pieOuterStrokeColor: border,
|
|
168
|
+
pieOuterStrokeWidth: '1px',
|
|
169
|
+
pieOpacity: '1',
|
|
170
|
+
|
|
171
|
+
// --- Git graph ---
|
|
172
|
+
git0: scales[0]?.bg ?? primary,
|
|
173
|
+
git1: scales[1]?.bg ?? secondary,
|
|
174
|
+
git2: scales[2]?.bg ?? accent,
|
|
175
|
+
git3: scales[3]?.bg ?? primary,
|
|
176
|
+
git4: scales[4]?.bg ?? secondary,
|
|
177
|
+
git5: scales[5]?.bg ?? accent,
|
|
178
|
+
git6: scales[6]?.bg ?? primary,
|
|
179
|
+
git7: scales[7]?.bg ?? secondary,
|
|
180
|
+
gitBranchLabel0: scales[0]?.label ?? fg,
|
|
181
|
+
gitBranchLabel1: scales[1]?.label ?? fg,
|
|
182
|
+
gitBranchLabel2: scales[2]?.label ?? fg,
|
|
183
|
+
gitBranchLabel3: scales[3]?.label ?? fg,
|
|
184
|
+
gitBranchLabel4: scales[4]?.label ?? fg,
|
|
185
|
+
gitBranchLabel5: scales[5]?.label ?? fg,
|
|
186
|
+
gitBranchLabel6: scales[6]?.label ?? fg,
|
|
187
|
+
gitBranchLabel7: scales[7]?.label ?? fg,
|
|
188
|
+
gitInv0: scales[0]?.label ?? fg,
|
|
189
|
+
commitLabelColor: fg,
|
|
190
|
+
commitLabelBackground: card,
|
|
191
|
+
tagLabelColor: fg,
|
|
192
|
+
tagLabelBackground: muted,
|
|
193
|
+
tagLabelBorder: border,
|
|
194
|
+
|
|
195
|
+
fontSize,
|
|
196
|
+
fontFamily: 'Inter, system-ui, sans-serif',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function useMermaidRenderer({
|
|
201
|
+
chart,
|
|
202
|
+
theme,
|
|
203
|
+
isCompact = false,
|
|
204
|
+
debounceMs = 300,
|
|
205
|
+
}: UseMermaidRendererProps): MermaidRenderResult {
|
|
56
206
|
const mermaidRef = useRef<HTMLDivElement>(null);
|
|
57
|
-
const renderTimerRef = useRef<
|
|
207
|
+
const renderTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
208
|
+
// Monotonic token: every effect run bumps it, and an in-flight async
|
|
209
|
+
// render checks it before touching the DOM. Guards against a stale
|
|
210
|
+
// chart's `mermaid.render` resolving after a newer one started.
|
|
211
|
+
const renderSeqRef = useRef(0);
|
|
212
|
+
|
|
58
213
|
const [svgContent, setSvgContent] = useState<string>('');
|
|
59
214
|
const [isVertical, setIsVertical] = useState(false);
|
|
60
215
|
const [isRendering, setIsRendering] = useState(false);
|
|
216
|
+
const [error, setError] = useState<string | null>(null);
|
|
61
217
|
|
|
62
218
|
const { isMermaidCodeComplete } = useMermaidValidation();
|
|
63
219
|
const { cleanupMermaidErrors } = useMermaidCleanup();
|
|
64
220
|
|
|
65
221
|
useEffect(() => {
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
if (typeof document === 'undefined') return '';
|
|
69
|
-
const value = getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
|
|
70
|
-
if (!value) return '';
|
|
71
|
-
// If value is already a complete color (hex, rgb, hsl with parentheses), return as-is
|
|
72
|
-
if (value.startsWith('#') || value.startsWith('rgb') || value.startsWith('hsl(')) {
|
|
73
|
-
return value;
|
|
74
|
-
}
|
|
75
|
-
// Otherwise assume it's HSL components and wrap in hsl()
|
|
76
|
-
return `hsl(${value})`;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const diagramFontSize = isCompact ? '12px' : '14px';
|
|
80
|
-
|
|
81
|
-
const themeVariables = theme === 'dark' ? {
|
|
82
|
-
primaryColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
83
|
-
primaryTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
|
|
84
|
-
primaryBorderColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
85
|
-
secondaryColor: getCSSVariable('--muted') || 'hsl(217.2 32.6% 17.5%)',
|
|
86
|
-
secondaryTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
|
|
87
|
-
secondaryBorderColor: getCSSVariable('--border') || 'hsl(217.2 32.6% 27.5%)',
|
|
88
|
-
tertiaryColor: getCSSVariable('--accent') || 'hsl(217.2 32.6% 20%)',
|
|
89
|
-
tertiaryTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
|
|
90
|
-
tertiaryBorderColor: getCSSVariable('--border') || 'hsl(217.2 32.6% 27.5%)',
|
|
91
|
-
mainBkg: getCSSVariable('--card') || 'hsl(222.2 84% 8%)',
|
|
92
|
-
textColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
|
|
93
|
-
nodeBorder: getCSSVariable('--border') || 'hsl(217.2 32.6% 27.5%)',
|
|
94
|
-
nodeTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
|
|
95
|
-
secondBkg: getCSSVariable('--muted') || 'hsl(217.2 32.6% 17.5%)',
|
|
96
|
-
lineColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
97
|
-
edgeLabelBackground: getCSSVariable('--card') || 'hsl(222.2 84% 8%)',
|
|
98
|
-
clusterBkg: getCSSVariable('--muted') || 'hsl(217.2 32.6% 12%)',
|
|
99
|
-
clusterBorder: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
100
|
-
background: getCSSVariable('--background') || 'hsl(222.2 84% 4.9%)',
|
|
101
|
-
labelBackground: getCSSVariable('--card') || 'hsl(222.2 84% 8%)',
|
|
102
|
-
labelTextColor: getCSSVariable('--foreground') || 'hsl(210 40% 98%)',
|
|
103
|
-
errorBkgColor: getCSSVariable('--destructive') || 'hsl(0 62.8% 30.6%)',
|
|
104
|
-
errorTextColor: 'hsl(210 40% 98%)',
|
|
105
|
-
fontSize: diagramFontSize,
|
|
106
|
-
fontFamily: 'Inter, system-ui, sans-serif',
|
|
107
|
-
} : {
|
|
108
|
-
primaryColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
109
|
-
primaryTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
|
|
110
|
-
primaryBorderColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
111
|
-
secondaryColor: getCSSVariable('--secondary') || 'hsl(210 40% 96.1%)',
|
|
112
|
-
secondaryTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
|
|
113
|
-
secondaryBorderColor: getCSSVariable('--border') || 'hsl(214.3 31.8% 91.4%)',
|
|
114
|
-
tertiaryColor: getCSSVariable('--muted') || 'hsl(210 40% 96.1%)',
|
|
115
|
-
tertiaryTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
|
|
116
|
-
tertiaryBorderColor: getCSSVariable('--border') || 'hsl(214.3 31.8% 91.4%)',
|
|
117
|
-
mainBkg: getCSSVariable('--card') || 'hsl(0 0% 100%)',
|
|
118
|
-
textColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
|
|
119
|
-
nodeBorder: getCSSVariable('--border') || 'hsl(214.3 31.8% 91.4%)',
|
|
120
|
-
nodeTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
|
|
121
|
-
secondBkg: getCSSVariable('--muted') || 'hsl(210 40% 96.1%)',
|
|
122
|
-
lineColor: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
123
|
-
edgeLabelBackground: getCSSVariable('--card') || 'hsl(0 0% 100%)',
|
|
124
|
-
clusterBkg: getCSSVariable('--accent') || 'hsl(210 40% 98%)',
|
|
125
|
-
clusterBorder: getCSSVariable('--primary') || 'hsl(221.2 83.2% 53.3%)',
|
|
126
|
-
background: getCSSVariable('--background') || 'hsl(0 0% 100%)',
|
|
127
|
-
labelBackground: getCSSVariable('--card') || 'hsl(0 0% 100%)',
|
|
128
|
-
labelTextColor: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)',
|
|
129
|
-
errorBkgColor: getCSSVariable('--destructive') || 'hsl(0 84.2% 60.2%)',
|
|
130
|
-
errorTextColor: 'hsl(210 40% 98%)',
|
|
131
|
-
fontSize: diagramFontSize,
|
|
132
|
-
fontFamily: 'Inter, system-ui, sans-serif',
|
|
133
|
-
};
|
|
222
|
+
const seq = ++renderSeqRef.current;
|
|
223
|
+
const isStale = () => seq !== renderSeqRef.current;
|
|
134
224
|
|
|
135
|
-
|
|
136
|
-
startOnLoad: false,
|
|
137
|
-
theme: 'base',
|
|
138
|
-
securityLevel: 'loose',
|
|
139
|
-
suppressErrorRendering: true, // Prevent mermaid from appending errors to body
|
|
140
|
-
fontFamily: 'Inter, system-ui, sans-serif',
|
|
141
|
-
flowchart: {
|
|
142
|
-
useMaxWidth: true,
|
|
143
|
-
htmlLabels: true,
|
|
144
|
-
curve: 'basis',
|
|
145
|
-
},
|
|
146
|
-
themeVariables,
|
|
147
|
-
});
|
|
225
|
+
const fontSize = isCompact ? '12px' : '14px';
|
|
148
226
|
|
|
149
227
|
const renderChart = async () => {
|
|
150
|
-
|
|
228
|
+
const host = mermaidRef.current;
|
|
229
|
+
if (!host || !chart) {
|
|
230
|
+
setIsRendering(false);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
151
233
|
|
|
152
|
-
//
|
|
234
|
+
// Streaming guard: an incomplete diagram (still being typed /
|
|
235
|
+
// streamed) would throw a parse error. Keep the previous SVG
|
|
236
|
+
// visible and just show the spinner — don't flash an error.
|
|
153
237
|
if (!isMermaidCodeComplete(chart)) {
|
|
154
238
|
setIsRendering(true);
|
|
155
239
|
return;
|
|
156
240
|
}
|
|
157
241
|
|
|
158
|
-
|
|
159
|
-
|
|
242
|
+
setIsRendering(true);
|
|
243
|
+
setError(null);
|
|
160
244
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
245
|
+
// `mermaid.initialize` is global + idempotent; re-running it
|
|
246
|
+
// per render keeps `themeVariables` in sync with the live
|
|
247
|
+
// light/dark tokens (cheap, no library reload).
|
|
248
|
+
mermaid.initialize({
|
|
249
|
+
startOnLoad: false,
|
|
250
|
+
theme: 'base',
|
|
251
|
+
securityLevel: 'loose',
|
|
252
|
+
suppressErrorRendering: true,
|
|
253
|
+
fontFamily: 'Inter, system-ui, sans-serif',
|
|
254
|
+
flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'basis' },
|
|
255
|
+
themeVariables: buildThemeVariables(theme, fontSize),
|
|
256
|
+
});
|
|
165
257
|
|
|
166
|
-
|
|
258
|
+
const id = `mermaid-${Math.random().toString(36).slice(2, 9)}`;
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
// `mermaid.parse` validates without mutating the DOM —
|
|
262
|
+
// catches syntax errors before `render` appends anything.
|
|
263
|
+
await mermaid.parse(chart);
|
|
167
264
|
const { svg } = await mermaid.render(id, chart);
|
|
168
265
|
|
|
169
|
-
if (mermaidRef.current)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
266
|
+
if (isStale() || !mermaidRef.current) return;
|
|
267
|
+
|
|
268
|
+
const textColor = getTextColor(theme);
|
|
269
|
+
const processedSvg = svg.replace(
|
|
270
|
+
/<svg /,
|
|
271
|
+
`<svg style="--mermaid-text-color: ${textColor};" `,
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
mermaidRef.current.innerHTML = processedSvg;
|
|
275
|
+
applyMermaidTextColors(mermaidRef.current, textColor);
|
|
276
|
+
// ER attribute rows zebra-stripe off `mainBkg` and ignore
|
|
277
|
+
// `themeVariables` — re-assert themed, contrasting fills.
|
|
278
|
+
applyMermaidErRowColors(
|
|
279
|
+
mermaidRef.current,
|
|
280
|
+
getThemeColor('--card', theme === 'dark' ? 'hsl(0 0% 8%)' : 'hsl(0 0% 100%)'),
|
|
281
|
+
getThemeColor('--muted', theme === 'dark' ? 'hsl(0 0% 15%)' : 'hsl(0 0% 96%)'),
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
const svgElement = mermaidRef.current.querySelector('svg');
|
|
285
|
+
if (svgElement) {
|
|
286
|
+
svgElement.style.maxWidth = '100%';
|
|
287
|
+
svgElement.style.height = 'auto';
|
|
288
|
+
svgElement.style.display = 'block';
|
|
289
|
+
setIsVertical(isVerticalDiagram(svgElement));
|
|
191
290
|
}
|
|
192
291
|
|
|
292
|
+
setSvgContent(processedSvg);
|
|
293
|
+
setError(null);
|
|
193
294
|
setIsRendering(false);
|
|
194
|
-
} catch (
|
|
195
|
-
|
|
196
|
-
|
|
295
|
+
} catch (err) {
|
|
296
|
+
// `render` may still have appended an orphan node to
|
|
297
|
+
// <body> despite `suppressErrorRendering` — sweep it.
|
|
197
298
|
cleanupMermaidErrors();
|
|
299
|
+
if (isStale()) return;
|
|
198
300
|
|
|
301
|
+
const message =
|
|
302
|
+
err instanceof Error ? err.message : 'Failed to render diagram';
|
|
303
|
+
setError(message);
|
|
304
|
+
setSvgContent('');
|
|
305
|
+
setIsVertical(false);
|
|
306
|
+
setIsRendering(false);
|
|
199
307
|
if (mermaidRef.current) {
|
|
200
|
-
mermaidRef.current.innerHTML =
|
|
201
|
-
<div class="p-4 text-destructive bg-destructive/10 border border-destructive/20 rounded-sm">
|
|
202
|
-
<p class="font-semibold">Mermaid Diagram Error</p>
|
|
203
|
-
<p class="text-sm">${error instanceof Error ? error.message : 'Unknown error'}</p>
|
|
204
|
-
</div>
|
|
205
|
-
`;
|
|
308
|
+
mermaidRef.current.innerHTML = '';
|
|
206
309
|
}
|
|
207
310
|
}
|
|
208
311
|
};
|
|
209
312
|
|
|
210
|
-
// Clear previous timer
|
|
211
313
|
if (renderTimerRef.current) {
|
|
212
314
|
clearTimeout(renderTimerRef.current);
|
|
213
315
|
}
|
|
214
|
-
|
|
215
|
-
// Debounce: wait 500ms after last update
|
|
216
|
-
renderTimerRef.current = setTimeout(() => {
|
|
217
|
-
renderChart();
|
|
218
|
-
}, 500);
|
|
316
|
+
renderTimerRef.current = setTimeout(renderChart, debounceMs);
|
|
219
317
|
|
|
220
318
|
return () => {
|
|
319
|
+
// Invalidate any in-flight async render from this effect run.
|
|
320
|
+
renderSeqRef.current++;
|
|
221
321
|
if (renderTimerRef.current) {
|
|
222
322
|
clearTimeout(renderTimerRef.current);
|
|
323
|
+
renderTimerRef.current = null;
|
|
223
324
|
}
|
|
224
325
|
};
|
|
225
|
-
}, [chart, theme, isCompact, isMermaidCodeComplete, cleanupMermaidErrors]);
|
|
326
|
+
}, [chart, theme, isCompact, debounceMs, isMermaidCodeComplete, cleanupMermaidErrors]);
|
|
226
327
|
|
|
227
|
-
return {
|
|
228
|
-
mermaidRef,
|
|
229
|
-
svgContent,
|
|
230
|
-
isVertical,
|
|
231
|
-
isRendering,
|
|
232
|
-
};
|
|
328
|
+
return { mermaidRef, svgContent, isVertical, isRendering, error };
|
|
233
329
|
}
|
|
@@ -1,28 +1,94 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hook for validating Mermaid code completeness
|
|
2
|
+
* Hook for validating Mermaid code completeness.
|
|
3
|
+
*
|
|
4
|
+
* Purpose: while a diagram is being *streamed* (token by token from an
|
|
5
|
+
* LLM, or typed) the source is briefly invalid. Rendering it would throw
|
|
6
|
+
* a parse error and flash an error panel on every keystroke. This
|
|
7
|
+
* heuristic detects "obviously still-being-written" source so the
|
|
8
|
+
* renderer can wait instead. It is intentionally conservative — false
|
|
9
|
+
* "complete" is fine (the real parser catches it), false "incomplete"
|
|
10
|
+
* just delays a render.
|
|
3
11
|
*/
|
|
4
12
|
|
|
5
13
|
import { useCallback } from 'react';
|
|
6
14
|
|
|
15
|
+
/** Diagram keywords that, alone, are a valid first line. */
|
|
16
|
+
const DIAGRAM_KEYWORDS = [
|
|
17
|
+
'graph',
|
|
18
|
+
'flowchart',
|
|
19
|
+
'sequenceDiagram',
|
|
20
|
+
'classDiagram',
|
|
21
|
+
'stateDiagram',
|
|
22
|
+
'stateDiagram-v2',
|
|
23
|
+
'erDiagram',
|
|
24
|
+
'journey',
|
|
25
|
+
'gantt',
|
|
26
|
+
'pie',
|
|
27
|
+
'mindmap',
|
|
28
|
+
'timeline',
|
|
29
|
+
'gitGraph',
|
|
30
|
+
'quadrantChart',
|
|
31
|
+
'requirementDiagram',
|
|
32
|
+
'C4Context',
|
|
33
|
+
'sankey-beta',
|
|
34
|
+
'xychart-beta',
|
|
35
|
+
'block-beta',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/** Count occurrences of a character that are not inside a quoted string. */
|
|
39
|
+
function countUnquoted(text: string, open: string, close: string): number {
|
|
40
|
+
let depth = 0;
|
|
41
|
+
let inQuote = false;
|
|
42
|
+
for (let i = 0; i < text.length; i++) {
|
|
43
|
+
const ch = text[i];
|
|
44
|
+
if (ch === '"') {
|
|
45
|
+
inQuote = !inQuote;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (inQuote) continue;
|
|
49
|
+
if (ch === open) depth++;
|
|
50
|
+
else if (ch === close) depth--;
|
|
51
|
+
}
|
|
52
|
+
return depth;
|
|
53
|
+
}
|
|
54
|
+
|
|
7
55
|
export function useMermaidValidation() {
|
|
8
56
|
const isMermaidCodeComplete = useCallback((code: string): boolean => {
|
|
9
57
|
if (!code || code.trim().length === 0) return false;
|
|
10
58
|
|
|
11
59
|
const trimmed = code.trim();
|
|
12
60
|
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
61
|
+
// Must start with a recognised diagram keyword. During streaming
|
|
62
|
+
// the very first tokens may be a partial keyword — wait for it.
|
|
63
|
+
const firstToken = trimmed.split(/[\s\n;]/)[0] ?? '';
|
|
64
|
+
const hasKnownType = DIAGRAM_KEYWORDS.some(
|
|
65
|
+
(kw) => firstToken === kw || trimmed.startsWith(kw),
|
|
66
|
+
);
|
|
67
|
+
if (!hasKnownType) return false;
|
|
16
68
|
|
|
17
|
-
|
|
69
|
+
const lines = trimmed.split('\n');
|
|
18
70
|
const lastLine = (lines[lines.length - 1] ?? '').trim();
|
|
19
71
|
|
|
20
|
-
//
|
|
21
|
-
if (
|
|
22
|
-
|
|
72
|
+
// Trailing edge with no destination: `A -->` / `A --` / `A -.->`.
|
|
73
|
+
if (/(-{1,3}>?|={1,3}>?|-\.->?|\.\.>?|--[ox])\s*$/.test(lastLine)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
// Trailing edge label still open: `A -->|label` (no closing `|`).
|
|
77
|
+
if (/-{1,3}>?\s*\|[^|]*$/.test(lastLine)) return false;
|
|
78
|
+
|
|
79
|
+
// Unbalanced shape brackets across the whole source (open > close).
|
|
80
|
+
if (countUnquoted(trimmed, '[', ']') > 0) return false;
|
|
81
|
+
if (countUnquoted(trimmed, '(', ')') > 0) return false;
|
|
82
|
+
// ER diagrams use `{` / `}` for crow's-foot cardinality
|
|
83
|
+
// (`||--o{`, `}|--||`) — those braces are deliberately unbalanced
|
|
84
|
+
// and must not be counted, or the diagram never renders.
|
|
85
|
+
if (!trimmed.startsWith('erDiagram') && countUnquoted(trimmed, '{', '}') > 0) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
23
88
|
|
|
24
|
-
//
|
|
25
|
-
|
|
89
|
+
// Odd number of double quotes — an unterminated string literal.
|
|
90
|
+
const quoteCount = (trimmed.match(/"/g) ?? []).length;
|
|
91
|
+
if (quoteCount % 2 !== 0) return false;
|
|
26
92
|
|
|
27
93
|
return true;
|
|
28
94
|
}, []);
|
|
@@ -32,6 +32,12 @@ export interface MermaidProps {
|
|
|
32
32
|
* Mermaid.client implementation for the full rationale.
|
|
33
33
|
*/
|
|
34
34
|
scrollIsolation?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Debounce window (ms) before (re)rendering after `chart` changes.
|
|
37
|
+
* Lower feels snappier for static charts; higher reduces churn while
|
|
38
|
+
* a diagram is streamed in. Default 300.
|
|
39
|
+
*/
|
|
40
|
+
debounceMs?: number;
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
const Mermaid: React.FC<MermaidProps> = (props) => {
|