index.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import React, { useEffect, useRef, useState } from 'react'
  2. import mermaid from 'mermaid'
  3. import CryptoJS from 'crypto-js'
  4. import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
  5. let mermaidAPI: any
  6. mermaidAPI = null
  7. if (typeof window !== 'undefined') {
  8. mermaid.initialize({
  9. startOnLoad: true,
  10. theme: 'default',
  11. flowchart: {
  12. htmlLabels: true,
  13. useMaxWidth: true,
  14. },
  15. })
  16. mermaidAPI = mermaid.mermaidAPI
  17. }
  18. const style = {
  19. minWidth: '480px',
  20. height: 'auto',
  21. overflow: 'auto',
  22. }
  23. const svgToBase64 = (svgGraph: string) => {
  24. const svgBytes = new TextEncoder().encode(svgGraph)
  25. const blob = new Blob([svgBytes], { type: 'image/svg+xml;charset=utf-8' })
  26. return new Promise((resolve, reject) => {
  27. const reader = new FileReader()
  28. reader.onloadend = () => resolve(reader.result)
  29. reader.onerror = reject
  30. reader.readAsDataURL(blob)
  31. })
  32. }
  33. const Flowchart = React.forwardRef((props: {
  34. PrimitiveCode: string
  35. }, ref) => {
  36. const [svgCode, setSvgCode] = useState(null)
  37. const chartId = useRef(`flowchart_${CryptoJS.MD5(props.PrimitiveCode).toString()}`)
  38. const [isRender, setIsRender] = useState(false)
  39. const [isLoading, setIsLoading] = useState(true)
  40. const clearFlowchartCache = () => {
  41. for (let i = localStorage.length - 1; i >= 0; --i) {
  42. const key = localStorage.key(i)
  43. if (key && key.startsWith('flowchart_'))
  44. localStorage.removeItem(key)
  45. }
  46. }
  47. const renderFlowchart = async (PrimitiveCode: string) => {
  48. try {
  49. const cachedSvg: any = localStorage.getItem(chartId.current)
  50. if (cachedSvg) {
  51. setSvgCode(cachedSvg)
  52. setIsLoading(false)
  53. return
  54. }
  55. if (typeof window !== 'undefined' && mermaidAPI) {
  56. const svgGraph = await mermaidAPI.render(chartId.current, PrimitiveCode)
  57. const dom = new DOMParser().parseFromString(svgGraph.svg, 'text/xml')
  58. if (!dom.querySelector('g.main'))
  59. throw new Error('empty svg')
  60. const base64Svg: any = await svgToBase64(svgGraph.svg)
  61. setSvgCode(base64Svg)
  62. setIsLoading(false)
  63. if (chartId.current && base64Svg)
  64. localStorage.setItem(chartId.current, base64Svg)
  65. }
  66. }
  67. catch (error) {
  68. clearFlowchartCache()
  69. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  70. handleReRender()
  71. }
  72. }
  73. const handleReRender = () => {
  74. setIsRender(false)
  75. setSvgCode(null)
  76. if (chartId.current)
  77. localStorage.removeItem(chartId.current)
  78. setTimeout(() => {
  79. setIsRender(true)
  80. renderFlowchart(props.PrimitiveCode)
  81. }, 100)
  82. }
  83. useEffect(() => {
  84. setIsRender(false)
  85. setTimeout(() => {
  86. setIsRender(true)
  87. renderFlowchart(props.PrimitiveCode)
  88. }, 100)
  89. }, [props.PrimitiveCode])
  90. return (
  91. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  92. // @ts-expect-error
  93. <div ref={ref}>
  94. {
  95. isRender
  96. && <div className="mermaid" style={style}>
  97. {svgCode && <img src={svgCode} style={{ width: '100%', height: 'auto' }} alt="Mermaid chart" />}
  98. </div>
  99. }
  100. {isLoading
  101. && <div className='py-4 px-[26px]'>
  102. <LoadingAnim type='text' />
  103. </div>
  104. }
  105. </div>
  106. )
  107. })
  108. export default Flowchart