|
@@ -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>
|
|
|
)
|
|
|
})
|