diff --git a/client/next-env.d.ts b/client/next-env.d.ts
new file mode 100644
index 000000000..4f11a03dc
--- /dev/null
+++ b/client/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/client/package.json b/client/package.json
index a681c4123..e5f3a99bd 100644
--- a/client/package.json
+++ b/client/package.json
@@ -55,7 +55,8 @@
"sass": "^1.58.3",
"tunnel": "^0.0.6",
"wxpay-v3": "^3.0.2",
- "zustand": "^4.3.5"
+ "zustand": "^4.3.5",
+ "mermaid": "^8.13.5"
},
"devDependencies": {
"@svgr/webpack": "^6.5.1",
diff --git a/client/src/components/Markdown/MermaidCodeBlock.tsx b/client/src/components/Markdown/MermaidCodeBlock.tsx
new file mode 100644
index 000000000..e312e2731
--- /dev/null
+++ b/client/src/components/Markdown/MermaidCodeBlock.tsx
@@ -0,0 +1,63 @@
+import React, { FC, useEffect, useState, useRef } from 'react';
+import mermaid from 'mermaid';
+import { Spinner } from '@chakra-ui/react';
+
+interface MermaidCodeBlockProps {
+ code: string;
+}
+
+const MermaidCodeBlock: FC = ({ code }) => {
+ const [svg, setSvg] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const codeTimeoutIdRef = useRef(null);
+
+ useEffect(() => {
+ if (codeTimeoutIdRef.current) {
+ clearTimeout(codeTimeoutIdRef.current);
+ }
+
+ codeTimeoutIdRef.current = window.setTimeout(() => {
+ setLoading(true);
+
+ const mermaidAPI = (mermaid as any).mermaidAPI as any;
+ mermaidAPI.initialize({ startOnLoad: false, theme: 'forest' });
+
+ try {
+ mermaidAPI.parse(code);
+ mermaidAPI.render('mermaid-svg', code, (svgCode: string) => {
+ setSvg(svgCode);
+ setLoading(false);
+ });
+ } catch (error) {
+ console.error('Error parsing Mermaid code:', '\n', error, '\n', 'Code:', code);
+ setLoading(false);
+ return;
+ }
+ }, 1000);
+ }, [code]);
+
+ useEffect(() => {
+ return () => {
+ if (codeTimeoutIdRef.current) {
+ clearTimeout(codeTimeoutIdRef.current);
+ }
+ };
+ }, []);
+
+ return (
+ <>
+ {loading ? (
+
+

+
+ ) : (
+
+ )}
+ >
+ );
+};
+
+export default MermaidCodeBlock;
diff --git a/client/src/components/Markdown/index.tsx b/client/src/components/Markdown/index.tsx
index fc25f7af3..1a2b43c5f 100644
--- a/client/src/components/Markdown/index.tsx
+++ b/client/src/components/Markdown/index.tsx
@@ -1,4 +1,4 @@
-import React, { memo, useMemo } from 'react';
+import React, { memo, useMemo, useEffect } from 'react';
import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { Box, Flex, useColorModeValue } from '@chakra-ui/react';
@@ -7,6 +7,7 @@ import Icon from '@/components/Icon';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';
+import MermaidCodeBlock from './MermaidCodeBlock';
import 'katex/dist/katex.min.css';
import styles from './index.module.scss';
@@ -29,49 +30,54 @@ const Markdown = ({
return (
+ if (match && match[1] === "mermaid") {
+ return ;
+ }
+
+ return !inline && match ? (
+
{match?.[1]}
- copyData(code)} alignItems={'center'}>
-
+ copyData(code)} alignItems={"center"}>
+
复制代码
-
+
{code}
) : (
- {code}
+ {children}
);
- }
+ },
}}
linkTarget="_blank"
>
diff --git a/client/src/types/mermaid.d.ts b/client/src/types/mermaid.d.ts
new file mode 100644
index 000000000..a303d0259
--- /dev/null
+++ b/client/src/types/mermaid.d.ts
@@ -0,0 +1,19 @@
+declare module "mermaid" {
+ import mermaidAPI from "mermaid";
+ const mermaid: any;
+ export default mermaid;
+
+ // 扩展 mermaidAPI
+ interface MermaidAPI extends mermaidAPI.mermaidAPI {
+ contentLoaded: (
+ targetEl: Element,
+ options?: mermaidAPI.mermaidAPI.Config
+ ) => void;
+ }
+
+ const mermaidAPIInstance: MermaidAPI;
+ export default mermaidAPIInstance;
+ }
+type Dispatch = (action: Action) => void;
+
+
\ No newline at end of file