import ReactMarkdown from "react-markdown"; import { Image } from "antd"; import RemarkMath from "remark-math"; import RemarkBreaks from "remark-breaks"; import RehypeKatex from "rehype-katex"; import RemarkGfm from "remark-gfm"; import RehypeHighlight from "rehype-highlight"; import { useRef, useState, useEffect, useMemo } from "react"; import { copyToClipboard } from "@/utils/chat"; import mermaid from "mermaid"; import LoadingIcon from "@/assets/public/three-dots.svg"; import React from "react"; import { useDebouncedCallback } from "use-debounce"; import "katex/dist/katex.min.css"; // Types interface MermaidProps { code: string; } interface PreCodeProps { children: React.ReactNode; } interface MarkdownContentProps { content: string; } interface MarkdownProps { content: string; loading?: boolean; fontSize?: number; fontFamily?: string; defaultShow?: boolean; } /** * Mermaid 图表渲染组件 */ export function Mermaid(props: MermaidProps) { const ref = useRef(null); const [hasError, setHasError] = useState(false); useEffect(() => { if (props.code && ref.current) { mermaid .run({ nodes: [ref.current], suppressErrors: true, }) .catch((e) => { setHasError(true); console.error("[Mermaid] ", e.message); }); } }, [props.code]); if (hasError) { return null; } return (
{props.code}
); } /** * 代码块渲染组件(带语法高亮和复制功能) */ export function PreCode(props: PreCodeProps) { const ref = useRef(null); const refText = ref.current?.innerText; const [mermaidCode, setMermaidCode] = useState(""); useEffect(() => { if (ref.current) { const codeElements = ref.current.querySelectorAll( "code" ) as NodeListOf; const wrapLanguages = [ "", "think", "md", "markdown", "text", "txt", "plaintext", "tex", "latex", ]; codeElements.forEach((codeElement) => { let languageClass = codeElement.className.match(/language-(\w+)/); let name = languageClass ? languageClass[1] : ""; if (wrapLanguages.includes(name)) { codeElement.style.whiteSpace = "pre-wrap"; } }); } }, [refText]); return ( <>
         {
            if (ref.current) {
              const code = ref.current.innerText;
              copyToClipboard(code);
            }
          }}
        >
        {props.children}
      
{mermaidCode.length > 0 && ( )} ); } /** * LaTeX 公式预处理 */ function preprocessLaTeX(content: string) { const pattern = /(```[\s\S]*?```|`.*?`)|\\\[([\s\S]*?[^\\])\\\]|\\\((.*?)\\\)|(\$\$[\s\S]*?\$\$)|(\$(?!\s)[\s\S]*?\S\$)/g; return content.replace( pattern, (match, codeBlock, squareBracket, roundBracket, doubleDollar, singleDollar) => { if (codeBlock) { return codeBlock; } let innerContent = ""; let isBlock = false; if (squareBracket) { innerContent = squareBracket; isBlock = true; } else if (roundBracket) { innerContent = roundBracket; isBlock = false; } else if (doubleDollar) { innerContent = doubleDollar.slice(2, -2); isBlock = true; } else if (singleDollar) { innerContent = singleDollar.slice(1, -1); isBlock = false; } else { return match; } // 修复数字前的反斜杠(如 \200,000 -> 200,000) innerContent = innerContent.replace(/\\(\d)/g, "$1"); // 修复分号间隔(如 400;kg -> 400\;kg) innerContent = innerContent.replace(/(\d+)\s*;\s*/g, "$1\\;"); return isBlock ? `$$${innerContent}$$` : `$${innerContent}$`; } ); } /** * Markdown 内容渲染核心组件 */ function _MarkdownContent(props: MarkdownContentProps) { const escapedContent = useMemo(() => { return preprocessLaTeX(props.content); }, [props.content]); return ( ( ), code: ({ className, children }) => { if (className && className.includes('language-think')) { return ( {children} ); } return children; }, div: (pProps) =>