[Feature] Support loading for mermaid. (#6004)

Co-authored-by: 靖谦 <jingqian@kaiwu.cloud>
This commit is contained in:
Jinq Qian 2024-07-05 21:01:50 +08:00 committed by GitHub
parent 3f0da88ff7
commit 9f16739518
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 27 additions and 15 deletions

View File

@ -103,7 +103,7 @@ const useLazyLoad = (ref: RefObject<Element>): boolean => {
// visit https://reactjs.org/docs/error-decoder.html?invariant=185 for the full message // visit https://reactjs.org/docs/error-decoder.html?invariant=185 for the full message
// or use the non-minified dev environment for full errors and additional helpful warnings. // or use the non-minified dev environment for full errors and additional helpful warnings.
const CodeBlock: CodeComponent = memo(({ inline, className, children, ...props }) => { const CodeBlock: CodeComponent = memo(({ inline, className, children, ...props }) => {
const [isSVG, setIsSVG] = useState(false) const [isSVG, setIsSVG] = useState(true)
const match = /language-(\w+)/.exec(className || '') const match = /language-(\w+)/.exec(className || '')
const language = match?.[1] const language = match?.[1]
const languageShowName = getCorrectCapitalizationLanguageName(language || '') const languageShowName = getCorrectCapitalizationLanguageName(language || '')

View File

@ -1,6 +1,7 @@
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import mermaid from 'mermaid' import mermaid from 'mermaid'
import CryptoJS from 'crypto-js' import CryptoJS from 'crypto-js'
import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
let mermaidAPI: any let mermaidAPI: any
mermaidAPI = null mermaidAPI = null
@ -23,12 +24,24 @@ const style = {
overflow: 'auto', overflow: 'auto',
} }
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 = React.forwardRef((props: { const Flowchart = React.forwardRef((props: {
PrimitiveCode: string PrimitiveCode: string
}, ref) => { }, ref) => {
const [svgCode, setSvgCode] = useState(null) const [svgCode, setSvgCode] = useState(null)
const chartId = useRef(`flowchart_${CryptoJS.MD5(props.PrimitiveCode).toString()}`) const chartId = useRef(`flowchart_${CryptoJS.MD5(props.PrimitiveCode).toString()}`)
const [isRender, setIsRender] = useState(true) const [isRender, setIsRender] = useState(false)
const [isLoading, setIsLoading] = useState(true)
const clearFlowchartCache = () => { const clearFlowchartCache = () => {
for (let i = localStorage.length - 1; i >= 0; --i) { for (let i = localStorage.length - 1; i >= 0; --i) {
@ -43,14 +56,19 @@ const Flowchart = React.forwardRef((props: {
const cachedSvg: any = localStorage.getItem(chartId.current) const cachedSvg: any = localStorage.getItem(chartId.current)
if (cachedSvg) { if (cachedSvg) {
setSvgCode(cachedSvg) setSvgCode(cachedSvg)
setIsLoading(false)
return return
} }
if (typeof window !== 'undefined' && mermaidAPI) { if (typeof window !== 'undefined' && mermaidAPI) {
const svgGraph = await mermaidAPI.render(chartId.current, PrimitiveCode) const svgGraph = await mermaidAPI.render(chartId.current, PrimitiveCode)
// eslint-disable-next-line @typescript-eslint/no-use-before-define 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) const base64Svg: any = await svgToBase64(svgGraph.svg)
setSvgCode(base64Svg) setSvgCode(base64Svg)
setIsLoading(false)
if (chartId.current && base64Svg) if (chartId.current && base64Svg)
localStorage.setItem(chartId.current, base64Svg) localStorage.setItem(chartId.current, base64Svg)
} }
@ -62,17 +80,6 @@ const Flowchart = React.forwardRef((props: {
} }
} }
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 handleReRender = () => { const handleReRender = () => {
setIsRender(false) setIsRender(false)
setSvgCode(null) setSvgCode(null)
@ -99,10 +106,15 @@ const Flowchart = React.forwardRef((props: {
<div ref={ref}> <div ref={ref}>
{ {
isRender isRender
&& <div id={chartId.current} className="mermaid" style={style}> && <div className="mermaid" style={style}>
{svgCode && <img src={svgCode} style={{ width: '100%', height: 'auto' }} alt="Mermaid chart" />} {svgCode && <img src={svgCode} style={{ width: '100%', height: 'auto' }} alt="Mermaid chart" />}
</div> </div>
} }
{isLoading
&& <div className='py-4 px-[26px]'>
<LoadingAnim type='text' />
</div>
}
</div> </div>
) )
}) })