import React, { useCallback, useMemo } from 'react'; import ReactMarkdown from 'react-markdown'; import 'katex/dist/katex.min.css'; import RemarkMath from 'remark-math'; // Math syntax import RemarkBreaks from 'remark-breaks'; // Line break import RehypeKatex from 'rehype-katex'; // Math render import RemarkGfm from 'remark-gfm'; // Special markdown syntax import RehypeExternalLinks from 'rehype-external-links'; import styles from './index.module.scss'; import dynamic from 'next/dynamic'; import { Box } from '@chakra-ui/react'; import { CodeClassNameEnum } from './utils'; const CodeLight = dynamic(() => import('./codeBlock/CodeLight'), { ssr: false }); const MermaidCodeBlock = dynamic(() => import('./img/MermaidCodeBlock'), { ssr: false }); const MdImage = dynamic(() => import('./img/Image'), { ssr: false }); const EChartsCodeBlock = dynamic(() => import('./img/EChartsCodeBlock'), { ssr: false }); const IframeCodeBlock = dynamic(() => import('./codeBlock/Iframe'), { ssr: false }); const IframeHtmlCodeBlock = dynamic(() => import('./codeBlock/iframe-html'), { ssr: false }); const VideoBlock = dynamic(() => import('./codeBlock/Video'), { ssr: false }); const AudioBlock = dynamic(() => import('./codeBlock/Audio'), { ssr: false }); const ChatGuide = dynamic(() => import('./chat/Guide'), { ssr: false }); const QuestionGuide = dynamic(() => import('./chat/QuestionGuide'), { ssr: false }); const A = dynamic(() => import('./A'), { ssr: false }); type Props = { source?: string; showAnimation?: boolean; isDisabled?: boolean; forbidZhFormat?: boolean; }; const Markdown = (props: Props) => { const source = props.source || ''; if (source.length < 200000) { return ; } return {source}; }; const MarkdownRender = ({ source = '', showAnimation, isDisabled, forbidZhFormat }: Props) => { const components = useMemo( () => ({ img: Image, pre: RewritePre, code: Code, a: A }), [] ); const formatSource = useMemo(() => { if (showAnimation || forbidZhFormat) return source; // 保护 URL 格式:https://, http://, /api/xxx const urlPlaceholders: string[] = []; const textWithProtectedUrls = source.replace( /(https?:\/\/[^\s<]+[^<.,:;"')\]\s]|\/api\/[^\s]+)(?=\s|$)/g, (match) => { urlPlaceholders.push(match); return `__URL_${urlPlaceholders.length - 1}__`; } ); // 处理中文与英文数字之间的分词 const textWithSpaces = textWithProtectedUrls .replace( /([\u4e00-\u9fa5\u3000-\u303f])([a-zA-Z0-9])|([a-zA-Z0-9])([\u4e00-\u9fa5\u3000-\u303f])/g, '$1$3 $2$4' ) // 处理引用标记 .replace(/\n*(\[QUOTE SIGN\]\(.*\))/g, '$1') // 处理 [quote:id] 格式引用,将 [quote:675934a198f46329dfc6d05a] 转换为 [675934a198f46329dfc6d05a]() .replace(/\[quote:?\s*([a-f0-9]{24})\](?!\()/gi, '[$1](QUOTE)') .replace(/\[([a-f0-9]{24})\](?!\()/g, '[$1](QUOTE)'); // 还原 URL const finalText = textWithSpaces.replace( /__URL_(\d+)__/g, (_, index) => urlPlaceholders[parseInt(index)] ); return finalText; }, [forbidZhFormat, showAnimation, source]); const urlTransform = useCallback((val: string) => { return val; }, []); return ( {formatSource} {isDisabled && } ); }; export default React.memo(Markdown); /* Custom dom */ function Code(e: any) { const { className, codeBlock, children } = e; const match = /language-(\w+)/.exec(className || ''); const codeType = match?.[1]; const strChildren = String(children); const Component = useMemo(() => { if (codeType === CodeClassNameEnum.mermaid) { return ; } if (codeType === CodeClassNameEnum.guide) { return ; } if (codeType === CodeClassNameEnum.questionGuide) { return ; } if (codeType === CodeClassNameEnum.echarts) { return ; } if (codeType === CodeClassNameEnum.iframe) { return ; } if (codeType && codeType.toLowerCase() === CodeClassNameEnum.html) { return ( {children} ); } if (codeType === CodeClassNameEnum.video) { return ; } if (codeType === CodeClassNameEnum.audio) { return ; } return ( {children} ); }, [codeType, className, codeBlock, match, children, strChildren]); return Component; } function Image({ src }: { src?: string }) { return ; } function RewritePre({ children }: any) { const modifiedChildren = React.Children.map(children, (child) => { if (React.isValidElement(child)) { // @ts-ignore return React.cloneElement(child, { codeBlock: true }); } return child; }); return <>{modifiedChildren}; }