From 2fead2cfef04929e7aa224d6dff0e6b8412104c4 Mon Sep 17 00:00:00 2001 From: jramos Date: Thu, 2 Apr 2026 15:37:00 -0600 Subject: [PATCH] feat(kb): render Mermaid diagrams in Knowledge Base viewer Installs mermaid v11 and adds a custom ReactMarkdown code renderer that intercepts fenced mermaid blocks and renders them as SVG diagrams using the dark theme. SVGs are made responsive (width: 100%). Non-mermaid code blocks are unchanged. Co-Authored-By: Claude Sonnet 4.6 --- frontend/package.json | 1 + .../src/components/KnowledgeBaseViewer.js | 88 ++++++++++++++++++- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index b0f1df0..d7619ec 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,6 +8,7 @@ "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^13.5.0", "lucide-react": "^0.563.0", + "mermaid": "^11.14.0", "react": "^19.2.4", "react-dom": "^19.2.4", "react-markdown": "^10.1.0", diff --git a/frontend/src/components/KnowledgeBaseViewer.js b/frontend/src/components/KnowledgeBaseViewer.js index ef95789..a4a7144 100644 --- a/frontend/src/components/KnowledgeBaseViewer.js +++ b/frontend/src/components/KnowledgeBaseViewer.js @@ -1,7 +1,72 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import ReactMarkdown from 'react-markdown'; +import mermaid from 'mermaid'; import { X, Download, Loader, AlertCircle, FileText, File } from 'lucide-react'; +mermaid.initialize({ + startOnLoad: false, + theme: 'dark', + darkMode: true, + themeVariables: { + background: '#0f172a', + primaryColor: '#1e3a5f', + primaryTextColor: '#e2e8f0', + primaryBorderColor: '#0ea5e9', + lineColor: '#475569', + secondaryColor: '#1a2e1a', + tertiaryColor: '#2d1f14', + edgeLabelBackground: '#1e293b', + clusterBkg: '#1e293b', + titleColor: '#e2e8f0', + fontFamily: 'monospace' + } +}); + +let mermaidCounter = 0; + +function MermaidDiagram({ code }) { + const ref = useRef(null); + const [svgError, setSvgError] = useState(''); + + useEffect(() => { + let cancelled = false; + const id = `mermaid-kb-${++mermaidCounter}`; + mermaid.render(id, code) + .then(({ svg }) => { + if (!cancelled && ref.current) { + ref.current.innerHTML = svg; + // Make SVG responsive + const svgEl = ref.current.querySelector('svg'); + if (svgEl) { + svgEl.removeAttribute('width'); + svgEl.removeAttribute('height'); + svgEl.style.width = '100%'; + svgEl.style.maxWidth = '100%'; + } + } + }) + .catch((err) => { + if (!cancelled) setSvgError(err.message || 'Failed to render diagram'); + }); + return () => { cancelled = true; }; + }, [code]); + + if (svgError) { + return ( +
+        Mermaid render error: {svgError}
+      
+ ); + } + + return ( +
+ ); +} + const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api'; export default function KnowledgeBaseViewer({ article, onClose }) { @@ -167,7 +232,26 @@ export default function KnowledgeBaseViewer({ article, onClose }) { {/* Markdown Rendering */} {isMarkdown && (
- {content} + ; + } + return ( + + {children} + + ); + } + }} + > + {content} +
)}