mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-28 00:56:26 +00:00
添加mermaid图表接口 (#85)
* 添加mermaid图表接口 * 添加类型文件 * Update package.json * Create next-env.d.ts
This commit is contained in:
63
client/src/components/Markdown/MermaidCodeBlock.tsx
Normal file
63
client/src/components/Markdown/MermaidCodeBlock.tsx
Normal file
@@ -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<MermaidCodeBlockProps> = ({ code }) => {
|
||||
const [svg, setSvg] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const codeTimeoutIdRef = useRef<number | null>(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 ? (
|
||||
<div className="loading">
|
||||
<img src="/imgs/loading.gif" alt="Loading..." />
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="mermaid-wrapper"
|
||||
dangerouslySetInnerHTML={svg ? { __html: svg } : undefined}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MermaidCodeBlock;
|
@@ -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 (
|
||||
<ReactMarkdown
|
||||
className={`markdown ${styles.markdown} ${
|
||||
isChatting ? (source === '' ? styles.waitingAnimation : styles.animation) : ''
|
||||
}`}
|
||||
className={`markdown ${styles.markdown}
|
||||
${
|
||||
isChatting
|
||||
? source === ""
|
||||
? styles.waitingAnimation
|
||||
: styles.animation
|
||||
: ""
|
||||
}
|
||||
`}
|
||||
remarkPlugins={[remarkMath]}
|
||||
rehypePlugins={[remarkGfm, rehypeKatex]}
|
||||
components={{
|
||||
pre: 'div',
|
||||
pre: "div",
|
||||
code({ node, inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
const match = /language-(\w+)/.exec(className ||'');
|
||||
const code = String(children);
|
||||
|
||||
return !inline || match ? (
|
||||
<Box my={3} borderRadius={'md'} overflow={'overlay'} backgroundColor={'#222'}>
|
||||
if (match && match[1] === "mermaid") {
|
||||
return <MermaidCodeBlock code={code} />;
|
||||
}
|
||||
|
||||
return !inline && match ? (
|
||||
<Box my={3} borderRadius={"md"} overflow={"overlay"} backgroundColor={"#222"}>
|
||||
<Flex
|
||||
className="code-header"
|
||||
py={2}
|
||||
px={5}
|
||||
backgroundColor={useColorModeValue('#323641', 'gray.600')}
|
||||
color={'#fff'}
|
||||
fontSize={'sm'}
|
||||
userSelect={'none'}
|
||||
backgroundColor={useColorModeValue("#323641", "gray.600")}
|
||||
color={"#fff"}
|
||||
fontSize={"sm"}
|
||||
userSelect={"none"}
|
||||
>
|
||||
<Box flex={1}>{match?.[1]}</Box>
|
||||
<Flex cursor={'pointer'} onClick={() => copyData(code)} alignItems={'center'}>
|
||||
<Icon name={'copy'} width={15} height={15} fill={'#fff'}></Icon>
|
||||
<Flex cursor={"pointer"} onClick={() => copyData(code)} alignItems={"center"}>
|
||||
<Icon name={"copy"} width={15} height={15} fill={"#fff"}></Icon>
|
||||
<Box ml={1}>复制代码</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<SyntaxHighlighter
|
||||
style={codeLight as any}
|
||||
language={match?.[1]}
|
||||
PreTag="pre"
|
||||
{...props}
|
||||
>
|
||||
<SyntaxHighlighter style={codeLight as any} language={match?.[1]} PreTag="pre" {...props}>
|
||||
{code}
|
||||
</SyntaxHighlighter>
|
||||
</Box>
|
||||
) : (
|
||||
<code className={className} {...props}>
|
||||
{code}
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
},
|
||||
}}
|
||||
linkTarget="_blank"
|
||||
>
|
||||
|
19
client/src/types/mermaid.d.ts
vendored
Normal file
19
client/src/types/mermaid.d.ts
vendored
Normal file
@@ -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;
|
||||
|
||||
|
Reference in New Issue
Block a user