|
|
|
@@ -1,6 +1,8 @@ |
|
|
|
import React, { useEffect, useRef, useState } from 'react' |
|
|
|
import mermaid from 'mermaid' |
|
|
|
import { usePrevious } from 'ahooks' |
|
|
|
import CryptoJS from 'crypto-js' |
|
|
|
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline' |
|
|
|
import LoadingAnim from '@/app/components/base/chat/chat/loading-anim' |
|
|
|
|
|
|
|
let mermaidAPI: any |
|
|
|
@@ -40,32 +42,15 @@ const Flowchart = React.forwardRef((props: { |
|
|
|
}, ref) => { |
|
|
|
const [svgCode, setSvgCode] = useState(null) |
|
|
|
const chartId = useRef(`flowchart_${CryptoJS.MD5(props.PrimitiveCode).toString()}`) |
|
|
|
const [isRender, setIsRender] = useState(false) |
|
|
|
const prevPrimitiveCode = usePrevious(props.PrimitiveCode) |
|
|
|
const [isLoading, setIsLoading] = useState(true) |
|
|
|
|
|
|
|
const clearFlowchartCache = () => { |
|
|
|
for (let i = localStorage.length - 1; i >= 0; --i) { |
|
|
|
const key = localStorage.key(i) |
|
|
|
if (key && key.startsWith('flowchart_')) |
|
|
|
localStorage.removeItem(key) |
|
|
|
} |
|
|
|
} |
|
|
|
const timeRef = useRef<NodeJS.Timeout>() |
|
|
|
const [errMsg, setErrMsg] = useState('') |
|
|
|
|
|
|
|
const renderFlowchart = async (PrimitiveCode: string) => { |
|
|
|
try { |
|
|
|
const cachedSvg: any = localStorage.getItem(chartId.current) |
|
|
|
if (cachedSvg) { |
|
|
|
setSvgCode(cachedSvg) |
|
|
|
setIsLoading(false) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
if (typeof window !== 'undefined' && mermaidAPI) { |
|
|
|
const svgGraph = await mermaidAPI.render(chartId.current, PrimitiveCode) |
|
|
|
const dom = new DOMParser().parseFromString(svgGraph.svg, 'text/xml') |
|
|
|
if (!dom.querySelector('g.main')) |
|
|
|
throw new Error('empty svg') |
|
|
|
|
|
|
|
const base64Svg: any = await svgToBase64(svgGraph.svg) |
|
|
|
setSvgCode(base64Svg) |
|
|
|
setIsLoading(false) |
|
|
|
@@ -74,30 +59,26 @@ const Flowchart = React.forwardRef((props: { |
|
|
|
} |
|
|
|
} |
|
|
|
catch (error) { |
|
|
|
clearFlowchartCache() |
|
|
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define |
|
|
|
handleReRender() |
|
|
|
if (prevPrimitiveCode === props.PrimitiveCode) { |
|
|
|
setIsLoading(false) |
|
|
|
setErrMsg((error as Error).message) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const handleReRender = () => { |
|
|
|
setIsRender(false) |
|
|
|
setSvgCode(null) |
|
|
|
if (chartId.current) |
|
|
|
localStorage.removeItem(chartId.current) |
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
setIsRender(true) |
|
|
|
renderFlowchart(props.PrimitiveCode) |
|
|
|
}, 100) |
|
|
|
} |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
setIsRender(false) |
|
|
|
setTimeout(() => { |
|
|
|
setIsRender(true) |
|
|
|
const cachedSvg: any = localStorage.getItem(chartId.current) |
|
|
|
if (cachedSvg) { |
|
|
|
setSvgCode(cachedSvg) |
|
|
|
setIsLoading(false) |
|
|
|
return |
|
|
|
} |
|
|
|
if (timeRef.current) |
|
|
|
clearTimeout(timeRef.current) |
|
|
|
|
|
|
|
timeRef.current = setTimeout(() => { |
|
|
|
renderFlowchart(props.PrimitiveCode) |
|
|
|
}, 100) |
|
|
|
}, 300) |
|
|
|
}, [props.PrimitiveCode]) |
|
|
|
|
|
|
|
return ( |
|
|
|
@@ -105,16 +86,24 @@ const Flowchart = React.forwardRef((props: { |
|
|
|
// @ts-expect-error |
|
|
|
<div ref={ref}> |
|
|
|
{ |
|
|
|
isRender |
|
|
|
&& <div className="mermaid" style={style}> |
|
|
|
{svgCode && <img src={svgCode} style={{ width: '100%', height: 'auto' }} alt="Mermaid chart" />} |
|
|
|
</div> |
|
|
|
svgCode |
|
|
|
&& <div className="mermaid" style={style}> |
|
|
|
{svgCode && <img src={svgCode} style={{ width: '100%', height: 'auto' }} alt="Mermaid chart" />} |
|
|
|
</div> |
|
|
|
} |
|
|
|
{isLoading |
|
|
|
&& <div className='py-4 px-[26px]'> |
|
|
|
<LoadingAnim type='text' /> |
|
|
|
</div> |
|
|
|
} |
|
|
|
{ |
|
|
|
errMsg |
|
|
|
&& <div className='py-4 px-[26px]'> |
|
|
|
<ExclamationTriangleIcon className='w-6 h-6 text-red-500' /> |
|
|
|
|
|
|
|
{errMsg} |
|
|
|
</div> |
|
|
|
} |
|
|
|
</div> |
|
|
|
) |
|
|
|
}) |