diff --git a/package-lock.json b/package-lock.json
index fd572cad7..e00137395 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,7 +27,6 @@
"@trpc/server": "^10.43.3",
"@vercel/analytics": "^1.1.1",
"browser-fs-access": "^0.35.0",
- "dompurify": "^3.0.6",
"eventsource-parser": "^1.1.1",
"idb-keyval": "^6.2.1",
"mermaid": "^10.6.1",
diff --git a/package.json b/package.json
index 9a5c7b8d0..38e7608e4 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,6 @@
"@trpc/server": "^10.43.3",
"@vercel/analytics": "^1.1.1",
"browser-fs-access": "^0.35.0",
- "dompurify": "^3.0.6",
"eventsource-parser": "^1.1.1",
"idb-keyval": "^6.2.1",
"mermaid": "^10.6.1",
diff --git a/src/apps/chat/components/message/ChatMessage.tsx b/src/apps/chat/components/message/ChatMessage.tsx
index de71e6047..d79b1e63f 100644
--- a/src/apps/chat/components/message/ChatMessage.tsx
+++ b/src/apps/chat/components/message/ChatMessage.tsx
@@ -197,7 +197,7 @@ export function ChatMessage(props: {
message: DMessage,
showDate?: boolean, diffPreviousText?: string,
hideAvatars?: boolean, codeBackground?: string,
- noMarkdown?: boolean, filterOnlyCode?: boolean,
+ noMarkdown?: boolean, diagramMode?: boolean,
isBottom?: boolean, noBottomBorder?: boolean,
isImagining?: boolean, isSpeaking?: boolean,
onMessageDelete?: () => void,
@@ -467,7 +467,10 @@ export function ChatMessage(props: {
...blockSx,
flexGrow: 0,
overflowX: 'auto',
- ...(!!props.filterOnlyCode && { boxShadow: 'md' }),
+ ...(!!props.diagramMode && {
+ // width: '100%',
+ boxShadow: 'md',
+ }),
}}>
{props.showDate === true && (
@@ -489,13 +492,13 @@ export function ChatMessage(props: {
{/* sequence of render components, for each Block */}
{!errorMessage && parseBlocks(collapsedText, fromSystem, textDiffs)
- .filter(block => block.type === 'code' || !props.filterOnlyCode)
+ .filter(block => !props.diagramMode || block.type === 'code')
.map(
(block, index) =>
block.type === 'html'
?
: block.type === 'code'
- ?
+ ?
: block.type === 'image'
?
: block.type === 'latex'
diff --git a/src/apps/chat/components/message/RenderCodeMermaid.tsx b/src/apps/chat/components/message/RenderCodeMermaid.tsx
index 91dbe9073..b7be73212 100644
--- a/src/apps/chat/components/message/RenderCodeMermaid.tsx
+++ b/src/apps/chat/components/message/RenderCodeMermaid.tsx
@@ -1,71 +1,93 @@
import * as React from 'react';
-import type { MermaidConfig } from 'mermaid';
import { Box } from '@mui/joy';
-import { SxProps } from '@mui/system';
+
+import { appTheme } from '~/common/app.theme';
const RenderMermaidDynamic = React.lazy(async () => {
+
+ // dynamic import
const { default: mermaidAPI } = await import('mermaid');
- const { default: DOMPurify } = await import('dompurify');
- const mermaidConfig: MermaidConfig = {
+ mermaidAPI.initialize({
startOnLoad: false,
- theme: 'default',
- // ... any other Mermaid configuration options
- };
- mermaidAPI.initialize(mermaidConfig);
+ // gfx options
+ fontFamily: appTheme.fontFamily.code,
+ altFontFamily: appTheme.fontFamily.body,
+
+ // style configuration
+ fontSize: 8,
+ htmlLabels: true,
+ securityLevel: 'loose',
+ theme: 'forest',
+
+ // per-chart configuration
+ mindmap: { useMaxWidth: false },
+ flowchart: { useMaxWidth: false },
+ sequence: { useMaxWidth: false },
+ timeline: { useMaxWidth: false },
+ class: { useMaxWidth: false },
+ state: { useMaxWidth: false },
+ pie: { useMaxWidth: false },
+ er: { useMaxWidth: false },
+ gantt: { useMaxWidth: false },
+ gitGraph: { useMaxWidth: false },
+ });
+
const MermaidDiagram = React.memo((props: { mermaidCode: string }) => {
// state
+ const isMounted = React.useRef(false);
const [svgCode, setSvgCode] = React.useState(null);
const [error, setError] = React.useState(null);
const mermaidContainerRef = React.useRef(null);
+
+ // [effect] re-render on code changes
React.useEffect(() => {
- let isMounted = true;
- // Generate a unique ID for each diagram
- const id = `mermaid-${Math.random().toString(36).substring(2, 9)}`;
-
- // Render the diagram
-
- mermaidAPI
- .render(id, props.mermaidCode)
- .then(renderResult => {
- if (!isMounted) return;
- const svg = DOMPurify.sanitize(renderResult.svg);
- setSvgCode(svg);
- // if (mermaidContainerRef.current)
- // renderResult.bindFunctions?.(mermaidContainerRef.current);
- })
- .catch(error => {
- console.error('Mermaid rendering failed:', error);
- setError('Mermaid rendering issue: ' + JSON.stringify(error));
- })
- .finally(() => {
- // ...
- });
+ const updateSvgCode = () =>
+ mermaidAPI
+ .render(
+ `mermaid-${Math.random().toString(36).substring(2, 9)}`,
+ props.mermaidCode,
+ mermaidContainerRef.current!,
+ )
+ .then(({ svg /*, bindFunctions*/ }) => {
+ if (mermaidContainerRef.current && isMounted.current) {
+ setSvgCode(svg);
+ // bindFunctions?.(mermaidContainerRef.current);
+ }
+ })
+ .catch((error) =>
+ console.error('Mermaid rendering failed:', error),
+ );
+ // mounting state and 'strict mode' debounce
+ isMounted.current = true;
+ const timeout = setTimeout(updateSvgCode, 0);
return () => {
- isMounted = false;
+ isMounted.current = false;
+ clearTimeout(timeout);
};
}, [props.mermaidCode]);
+
if (error)
return Error: {error}
;
return (
-
);
});
- // Assign a displayName to the component for better debugging
MermaidDiagram.displayName = 'MermaidDiagram';
return { default: MermaidDiagram };
diff --git a/src/modules/aifn/digrams/DiagramsModal.tsx b/src/modules/aifn/digrams/DiagramsModal.tsx
index 418598cbd..72c13ce21 100644
--- a/src/modules/aifn/digrams/DiagramsModal.tsx
+++ b/src/modules/aifn/digrams/DiagramsModal.tsx
@@ -176,7 +176,7 @@ export function DiagramsModal(props: { config: DiagramConfig, onClose: () => voi
{!!message && (!abortController || showOptions) && (
setMessage({ ...message, text })}
sx={{
diff --git a/src/modules/aifn/digrams/diagrams.data.ts b/src/modules/aifn/digrams/diagrams.data.ts
index 0c4294eb4..11c8001c4 100644
--- a/src/modules/aifn/digrams/diagrams.data.ts
+++ b/src/modules/aifn/digrams/diagrams.data.ts
@@ -37,10 +37,10 @@ mindmap
function mermaidDiagramPrompt(diagramType: DiagramType): { sys: string, usr: string } {
let promptDetails = diagramType === 'auto'
- ? 'You create a valid Mermaid diagram markdown (```mermaid\\n...), ready to be rendered into a diagram or mindmap. Ensure the code contains no external references, and all names are properly enclosed in double quotes and escaped if necessary. Choose the most suitable diagram type from the following supported types: flowchart, sequence, class, state, erd, gantt, pie, git, or mindmap.'
+ ? 'You create a valid Mermaid diagram markdown (```mermaid\\n...), ready to be rendered into a diagram. Ensure the code contains no external references, and all names are properly enclosed in double quotes and escaped if necessary. Choose the most suitable diagram type from the following supported types: flowchart, sequence, class, state, erd, gantt, pie, git.'
: 'You create a valid Mermaid mindmap markdown (```mermaid\\n...), ready to be rendered into a mind map. Ensure the code contains no external references, and all names are properly enclosed in double quotes and escaped if necessary. For example:\n' + mermaidMindmapExample + '\n';
return {
- sys: `You are an AI that generates Mermaid code based on provided text. ${promptDetails}`,
+ sys: `You are an AI that generates correct Mermaid code based on provided text. ${promptDetails}`,
usr: `Generate the Mermaid code for a ${diagramType === 'auto' ? 'suitable diagram' : 'mind map'} that represents the preceding assistant message.`,
};
}