diff --git a/src/interface/web/app/components/chatMessage/chatMessage.tsx b/src/interface/web/app/components/chatMessage/chatMessage.tsx index ebd32b04..062ecb4d 100644 --- a/src/interface/web/app/components/chatMessage/chatMessage.tsx +++ b/src/interface/web/app/components/chatMessage/chatMessage.tsx @@ -399,6 +399,43 @@ export function TrainOfThought(props: TrainOfThoughtProps) { ); } +// Clean mermaid chart by removing/fixing invalid syntax patterns +function cleanMermaidChart(chart: string): string { + return chart + .split("\n") + .filter((line) => !line.trim().match(/^title\s*\[.*\]\s*$/i)) // Remove invalid title[...] lines + .map((line) => { + // Fix parentheses inside square bracket node labels: [Text (with parens)] + // Mermaid interprets () as special syntax, so we need to quote the content + // Replace [Label (text)] with ["Label (text)"] + return line.replace(/\[([^\]]*\([^\]]*\)[^\]]*)\]/g, '["$1"]'); + }) + .join("\n"); +} + +// Extract mermaid code blocks from markdown content +function extractMermaidBlocks(content: string): { cleanedContent: string; mermaidBlocks: string[] } { + const mermaidBlocks: string[] = []; + // Match ```mermaid ... ``` code blocks + // Allow optional whitespace before/after delimiters and handle various line endings + const mermaidRegex = /```\s*mermaid\s*\r?\n([\s\S]*?)```/gi; + + const cleanedContent = content.replace(mermaidRegex, (match, mermaidCode) => { + const trimmedCode = mermaidCode.trim(); + if (trimmedCode) { + // Clean the mermaid chart before adding + const cleanedChart = cleanMermaidChart(trimmedCode); + if (cleanedChart.trim()) { + mermaidBlocks.push(cleanedChart); + } + } + // Replace with empty string to remove from markdown + return ""; + }); + + return { cleanedContent, mermaidBlocks }; +} + const ChatMessage = forwardRef((props, ref) => { const [copySuccess, setCopySuccess] = useState(false); const [isHovering, setIsHovering] = useState(false); @@ -408,6 +445,7 @@ const ChatMessage = forwardRef((props, ref) => const [interrupted, setInterrupted] = useState(false); const [excalidrawData, setExcalidrawData] = useState(""); const [mermaidjsData, setMermaidjsData] = useState(""); + const [inlineMermaidBlocks, setInlineMermaidBlocks] = useState([]); // State for file content preview on file link click, hover const [previewOpen, setPreviewOpen] = useState(false); @@ -472,6 +510,11 @@ const ChatMessage = forwardRef((props, ref) => setMermaidjsData(props.chatMessage.mermaidjsDiagram); } + // Extract mermaid blocks from the message content + const { cleanedContent, mermaidBlocks } = extractMermaidBlocks(message); + message = cleanedContent; + setInlineMermaidBlocks(mermaidBlocks); + // Replace file links with base64 data message = renderCodeGenImageInline(message, props.chatMessage.codeContext); @@ -1065,6 +1108,9 @@ const ChatMessage = forwardRef((props, ref) => {excalidrawData && } {mermaidjsData && } + {inlineMermaidBlocks.map((chart, index) => ( + + ))}