Open History to see the last commit
1import { useState } from "react";
2
3import { MermaidDiagram } from "../chat/MermaidDiagram";
4import { ExpandedImageDialog } from "../chat/ExpandedImageDialog";
5
6export default function MermaidViewer(props: { readonly text: string }) {
7 const [expanded, setExpanded] = useState<string | null>(null);
8 // Mermaid lays out DOM before sanitizing its SVG. Refuse resource and config
9 // syntax before that pass so standalone files cannot initiate requests. Node
10 // metadata is YAML, whose escaped/aliased image keys cannot be checked lexically.
11 if (
12 /%%\s*\{|^\s*---|<\/?[A-Za-z]|<[!?]|@\s*\{|\b(?:img|image)\s*:|url\s*\(|@import|(?:https?|data|blob|file):/im.test(
13 props.text,
14 )
15 )
16 return (
17 <div role="alert" className="p-6 text-xs text-muted-foreground">
18 This diagram contains resource URLs, markup, node metadata, or custom configuration. Show
19 Mermaid source to inspect it safely.
20 </div>
21 );
22 return (
23 <div className="scrollbar-gutter-both min-h-0 flex-1 overflow-auto p-6 [&_*]:animate-none!">
24 <MermaidDiagram source={props.text} onExpand={setExpanded} />
25 {expanded ? (
26 <ExpandedImageDialog
27 preview={{ images: [{ src: expanded, name: "Mermaid diagram" }], index: 0 }}
28 onClose={() => setExpanded(null)}
29 />
30 ) : null}
31 </div>
32 );
33}