diff --git a/apps/desktop/src/app/chat/right-rail/preview-file.tsx b/apps/desktop/src/app/chat/right-rail/preview-file.tsx index 48e0649647a..ca4c65f2e99 100644 --- a/apps/desktop/src/app/chat/right-rail/preview-file.tsx +++ b/apps/desktop/src/app/chat/right-rail/preview-file.tsx @@ -6,7 +6,7 @@ import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react' -import { Fragment, lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react' import ShikiHighlighter from 'react-shiki' import { Streamdown } from 'streamdown' @@ -14,6 +14,7 @@ import { requestComposerFocus, requestComposerInsertRefs } from '@/app/chat/comp import { droppedFileInlineRef } from '@/app/chat/composer/inline-refs' import { HERMES_PATHS_MIME } from '@/app/chat/hooks/use-composer-actions' import { isAddSelectionShortcut } from '@/app/right-sidebar/terminal/selection' +import { RichCodeBlock } from '@/components/assistant-ui/embeds' import { CodeEditor } from '@/components/chat/code-editor' import { FileDiffPanel } from '@/components/chat/diff-lines' import { chunkTextLines, useFixedRowWindow } from '@/components/chat/fixed-row-window' @@ -34,10 +35,6 @@ import { setPreviewDirty } from '@/store/preview-edit' import { $currentCwd } from '@/store/session' import { notifyWorkspaceChanged } from '@/store/workspace-events' -const MermaidDiagram = lazy(() => - import('@/components/chat/mermaid-block').then(m => ({ default: m.MermaidBlock })) -) - const SHIKI_THEME = { dark: 'github-dark-default', light: 'github-light-default' } as const const TEXT_PREVIEW_MAX_BYTES = 512 * 1024 const SOURCE_CHUNK_LINES = 200 @@ -295,17 +292,9 @@ function MarkdownCode({ className, children, ...props }: ComponentProps<'code'>) ) } - if (language === 'mermaid') { - return ( - Loading diagram…} - > - - - ) - } + const code = String(children).replace(/\n$/, '') - return ( + const highlighted = ( ) showLanguage={false} theme={SHIKI_THEME} > - {String(children).replace(/\n$/, '')} + {code} ) + + // ```mermaid / ```svg fences route to the shared lazy renderers (same + // registry the chat transcript uses); everything else stays on Shiki. + return } const MARKDOWN_COMPONENTS = { diff --git a/apps/desktop/src/components/chat/mermaid-block.tsx b/apps/desktop/src/components/chat/mermaid-block.tsx deleted file mode 100644 index 157dc0d2e03..00000000000 --- a/apps/desktop/src/components/chat/mermaid-block.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { useEffect, useRef, useState } from 'react' - -let mermaidPromise: Promise | null = null - -function loadMermaid() { - if (!mermaidPromise) { - mermaidPromise = import('mermaid').then(async mod => { - const m = mod.default - - m.initialize({ - securityLevel: 'strict', - startOnLoad: false, - theme: document.documentElement.classList.contains('dark') ? 'dark' : 'default' - }) - - return mod - }) - } - - return mermaidPromise -} - -interface MermaidBlockProps { - chart: string -} - -export function MermaidBlock({ chart }: MermaidBlockProps) { - const containerRef = useRef(null) - const [error, setError] = useState(null) - const [svg, setSvg] = useState(null) - - useEffect(() => { - let cancelled = false - - async function render() { - try { - const { default: mermaid } = await loadMermaid() - const id = `mermaid-${Math.random().toString(36).slice(2, 10)}` - const { svg: rendered } = await mermaid.render(id, chart) - - if (!cancelled) { - setSvg(rendered) - setError(null) - } - } catch (err) { - if (!cancelled) { - setError(err instanceof Error ? err.message : String(err)) - setSvg(null) - } - } - } - - void render() - - return () => { - cancelled = true - } - }, [chart]) - - if (error) { - return ( -
-
Mermaid diagram error
-
{error}
-
- View source -
{chart}
-
-
- ) - } - - if (!svg) { - return
Rendering diagram…
- } - - return ( -
- ) -}