import React, { useCallback, useEffect, useRef, useState } from 'react' import mermaid from 'mermaid' import { usePrevious } from 'ahooks' import { useTranslation } from 'react-i18next' import { ExclamationTriangleIcon } from '@heroicons/react/24/outline' import { cleanUpSvgCode } from './utils' import LoadingAnim from '@/app/components/base/chat/chat/loading-anim' import cn from '@/utils/classnames' import ImagePreview from '@/app/components/base/image-uploader/image-preview' let mermaidAPI: any mermaidAPI = null if (typeof window !== 'undefined') mermaidAPI = mermaid.mermaidAPI const svgToBase64 = (svgGraph: string) => { const svgBytes = new TextEncoder().encode(svgGraph) const blob = new Blob([svgBytes], { type: 'image/svg+xml;charset=utf-8' }) return new Promise((resolve, reject) => { const reader = new FileReader() reader.onloadend = () => resolve(reader.result) reader.onerror = reject reader.readAsDataURL(blob) }) } const Flowchart = ( { ref, ...props }: { PrimitiveCode: string } & { ref: React.RefObject; }, ) => { const { t } = useTranslation() const [svgCode, setSvgCode] = useState(null) const [look, setLook] = useState<'classic' | 'handDrawn'>('classic') const prevPrimitiveCode = usePrevious(props.PrimitiveCode) const [isLoading, setIsLoading] = useState(true) const timeRef = useRef(0) const [errMsg, setErrMsg] = useState('') const [imagePreviewUrl, setImagePreviewUrl] = useState('') const renderFlowchart = useCallback(async (PrimitiveCode: string) => { setSvgCode(null) setIsLoading(true) try { if (typeof window !== 'undefined' && mermaidAPI) { const svgGraph = await mermaidAPI.render('flowchart', PrimitiveCode) const base64Svg: any = await svgToBase64(cleanUpSvgCode(svgGraph.svg)) setSvgCode(base64Svg) setIsLoading(false) } } catch (error) { if (prevPrimitiveCode === props.PrimitiveCode) { setIsLoading(false) setErrMsg((error as Error).message) } } }, [props.PrimitiveCode]) useEffect(() => { if (typeof window !== 'undefined') { mermaid.initialize({ startOnLoad: true, theme: 'neutral', look, flowchart: { htmlLabels: true, useMaxWidth: true, }, }) renderFlowchart(props.PrimitiveCode) } }, [look]) useEffect(() => { if (timeRef.current) window.clearTimeout(timeRef.current) timeRef.current = window.setTimeout(() => { renderFlowchart(props.PrimitiveCode) }, 300) }, [props.PrimitiveCode]) return ( // eslint-disable-next-line ts/ban-ts-comment // @ts-expect-error (
{ svgCode &&
setImagePreviewUrl(svgCode)}> {svgCode && mermaid_chart}
} {isLoading &&
} { errMsg &&
  {errMsg}
} { imagePreviewUrl && ( setImagePreviewUrl('')} />) }
) ) } Flowchart.displayName = 'Flowchart' export default Flowchart