From 99d1085e6a8db38cf98bbce76c38a56bc3000008 Mon Sep 17 00:00:00 2001 From: Xi Zhang Date: Tue, 16 Jun 2026 00:45:07 +0100 Subject: [PATCH] feat: update version to 0.1.1 and enhance MermaidDiagram with zoom functionality --- README.md | 4 + package-lock.json | 4 +- package.json | 2 +- src/app/components/MermaidDiagram.tsx | 105 +++++++++++++++++++++----- 4 files changed, 94 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 7dfdf28..e6c915a 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,10 @@ We welcome contributions! See the [Contributing Guidelines](./CONTRIBUTING.md) f Every contribution brings us one step closer to a future where AI accelerates scientific breakthroughs for all of humanity. + + + +

🔝Back to top

## 📚 Acknowledgments diff --git a/package-lock.json b/package-lock.json index d1883aa..5a83fc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@evoscientist/webui", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@evoscientist/webui", - "version": "0.1.0", + "version": "0.1.1", "license": "Apache-2.0", "dependencies": { "@langchain/core": "1.1.19", diff --git a/package.json b/package.json index dafb97f..fe9b9d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@evoscientist/webui", - "version": "0.1.0", + "version": "0.1.1", "description": "Web UI for EvoScientist — a self-evolving AI scientist built on DeepAgents/LangGraph.", "author": "Xi Zhang ", "type": "module", diff --git a/src/app/components/MermaidDiagram.tsx b/src/app/components/MermaidDiagram.tsx index 60b38d1..a2d9024 100644 --- a/src/app/components/MermaidDiagram.tsx +++ b/src/app/components/MermaidDiagram.tsx @@ -8,6 +8,8 @@ import { DialogDescription, DialogTitle, } from "@/components/ui/dialog"; +import { RotateCcw, ZoomIn, ZoomOut } from "lucide-react"; +import { useTheme } from "@/providers/ThemeProvider"; interface MermaidDiagramProps { code: string; @@ -20,28 +22,35 @@ interface MermaidDiagramProps { // call through this promise chain. Also caches the mermaid import + the // one-time initialize() so they don't run per-diagram. type MermaidModule = typeof import("mermaid").default; +type MermaidTheme = "default" | "dark"; let mermaidLoader: Promise | null = null; let renderChain: Promise = Promise.resolve(); function loadMermaid(): Promise { if (!mermaidLoader) { - mermaidLoader = import("mermaid").then((mod) => { - mod.default.initialize({ - startOnLoad: false, - theme: "dark", - // "strict" forbids raw HTML in node labels — safer for AI-generated - // diagram source rendered alongside user content. - securityLevel: "strict", - }); - return mod.default; - }); + mermaidLoader = import("mermaid").then((mod) => mod.default); } return mermaidLoader; } -async function renderMermaid(id: string, code: string): Promise { +function initializeMermaid(mermaid: MermaidModule, theme: MermaidTheme) { + mermaid.initialize({ + startOnLoad: false, + theme, + // "strict" forbids raw HTML in node labels — safer for AI-generated + // diagram source rendered alongside user content. + securityLevel: "strict", + }); +} + +async function renderMermaid( + id: string, + code: string, + theme: MermaidTheme +): Promise { const next = renderChain.then(async () => { const mermaid = await loadMermaid(); + initializeMermaid(mermaid, theme); // parse() with suppressErrors gives a boolean instead of the "red bomb" // error-SVG that render() produces on bad input. const parsed = await mermaid.parse(code, { suppressErrors: true }); @@ -60,6 +69,9 @@ export const MermaidDiagram = React.memo( // useId is React 19's stable-across-renders unique id. Mermaid needs a // CSS-safe id; useId returns strings like ":r3:" so strip the colons. const reactId = useId().replace(/:/g, ""); + const { resolvedTheme } = useTheme(); + const mermaidTheme: MermaidTheme = + resolvedTheme === "dark" ? "dark" : "default"; const [svg, setSvg] = useState(null); useEffect(() => { @@ -69,7 +81,8 @@ export const MermaidDiagram = React.memo( // ends (this effect re-runs because isStreaming is in the deps). if (isStreaming) return; let cancelled = false; - renderMermaid(`mermaid-${reactId}`, code) + setSvg(null); + renderMermaid(`mermaid-${reactId}`, code, mermaidTheme) .then((result) => { if (!cancelled) setSvg(result); }) @@ -79,7 +92,7 @@ export const MermaidDiagram = React.memo( return () => { cancelled = true; }; - }, [code, reactId, isStreaming]); + }, [code, reactId, isStreaming, mermaidTheme]); // During streaming the source grows token-by-token. Avoid the syntax // highlighter here — it re-tokenizes the full string on every keystroke @@ -106,6 +119,16 @@ MermaidDiagram.displayName = "MermaidDiagram"; const MermaidPreview = React.memo<{ svg: string }>(({ svg }) => { const [open, setOpen] = useState(false); + const [zoom, setZoom] = useState(1.25); + const zoomPercent = Math.round(zoom * 100); + + useEffect(() => { + if (open) setZoom(1.25); + }, [open]); + + const zoomOut = () => setZoom((value) => Math.max(0.75, value - 0.25)); + const zoomIn = () => setZoom((value) => Math.min(2.5, value + 0.25)); + return ( <> + + {zoomPercent}% + + + + +
+
+