Render mermaid diagram wrapped in markdown codeblocks on web app

This commit is contained in:
Debanjum
2025-12-29 15:52:06 -08:00
parent 19900e42ef
commit ff4b9f3502

View File

@@ -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<HTMLDivElement, ChatMessageProps>((props, ref) => { const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>((props, ref) => {
const [copySuccess, setCopySuccess] = useState<boolean>(false); const [copySuccess, setCopySuccess] = useState<boolean>(false);
const [isHovering, setIsHovering] = useState<boolean>(false); const [isHovering, setIsHovering] = useState<boolean>(false);
@@ -408,6 +445,7 @@ const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>((props, ref) =>
const [interrupted, setInterrupted] = useState<boolean>(false); const [interrupted, setInterrupted] = useState<boolean>(false);
const [excalidrawData, setExcalidrawData] = useState<string>(""); const [excalidrawData, setExcalidrawData] = useState<string>("");
const [mermaidjsData, setMermaidjsData] = useState<string>(""); const [mermaidjsData, setMermaidjsData] = useState<string>("");
const [inlineMermaidBlocks, setInlineMermaidBlocks] = useState<string[]>([]);
// State for file content preview on file link click, hover // State for file content preview on file link click, hover
const [previewOpen, setPreviewOpen] = useState<boolean>(false); const [previewOpen, setPreviewOpen] = useState<boolean>(false);
@@ -472,6 +510,11 @@ const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>((props, ref) =>
setMermaidjsData(props.chatMessage.mermaidjsDiagram); 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 // Replace file links with base64 data
message = renderCodeGenImageInline(message, props.chatMessage.codeContext); message = renderCodeGenImageInline(message, props.chatMessage.codeContext);
@@ -1065,6 +1108,9 @@ const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>((props, ref) =>
</Dialog> </Dialog>
{excalidrawData && <ExcalidrawComponent data={excalidrawData} />} {excalidrawData && <ExcalidrawComponent data={excalidrawData} />}
{mermaidjsData && <Mermaid chart={mermaidjsData} />} {mermaidjsData && <Mermaid chart={mermaidjsData} />}
{inlineMermaidBlocks.map((chart, index) => (
<Mermaid key={`inline-mermaid-${index}`} chart={chart} />
))}
</div> </div>
<div className={styles.teaserReferencesContainer}> <div className={styles.teaserReferencesContainer}>
<TeaserReferencesSection <TeaserReferencesSection