123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- 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 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 = React.forwardRef((props: {
- PrimitiveCode: string
- }, ref) => {
- 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<NodeJS.Timeout>()
- 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(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)
- clearTimeout(timeRef.current)
- timeRef.current = setTimeout(() => {
- renderFlowchart(props.PrimitiveCode)
- }, 300)
- }, [props.PrimitiveCode])
- return (
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- <div ref={ref}>
- <div className="msh-segmented msh-segmented-sm css-23bs09 css-var-r1">
- <div className="msh-segmented-group">
- <label className="msh-segmented-item flex items-center space-x-1 m-2 w-[200px]">
- <div key='classic'
- className={cn('flex items-center justify-center mb-4 w-[calc((100%-8px)/2)] h-8 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg cursor-pointer system-sm-medium text-text-secondary',
- look === 'classic' && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary',
- )}
- onClick={() => setLook('classic')}
- >
- <div className="msh-segmented-item-label">{t('app.mermaid.classic')}</div>
- </div>
- <div key='handDrawn'
- className={cn(
- 'flex items-center justify-center mb-4 w-[calc((100%-8px)/2)] h-8 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg cursor-pointer system-sm-medium text-text-secondary',
- look === 'handDrawn' && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary',
- )}
- onClick={() => setLook('handDrawn')}
- >
- <div className="msh-segmented-item-label">{t('app.mermaid.handDrawn')}</div>
- </div>
- </label>
- </div>
- </div>
- {
- svgCode
- && <div className="mermaid cursor-pointer h-auto w-full object-fit: cover" onClick={() => setImagePreviewUrl(svgCode)}>
- {svgCode && <img src={svgCode} 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>
- }
- {
- imagePreviewUrl && (<ImagePreview title='mermaid_chart' url={imagePreviewUrl} onCancel={() => setImagePreviewUrl('')} />)
- }
- </div>
- )
- })
- export default Flowchart
|