Download client/src/components/AnswerRenderer.jsx from soxogvv/MNEMO: direct link, hf CLI and curl.
- Browser
- Download file 6.16 kB
-
https://huggingface.co/spaces/soxogvv/MNEMO/resolve/main/client/src/components/AnswerRenderer.jsx
- Command line
-
hf download hf://spaces/soxogvv/MNEMO/client/src/components/AnswerRenderer.jsx
-
curl -L -o AnswerRenderer.jsx https://huggingface.co/spaces/soxogvv/MNEMO/resolve/main/client/src/components/AnswerRenderer.jsx
6.16 kB
| import ReactMarkdown from "react-markdown"; | |
| import remarkGfm from "remark-gfm"; | |
| import remarkMath from "remark-math"; | |
| import rehypeHighlight from "rehype-highlight"; | |
| import rehypeKatex from "rehype-katex"; | |
| import "katex/dist/katex.min.css"; | |
| import Mermaid from "./Mermaid"; | |
| function preprocessContent(text) { | |
| if (!text) return ""; | |
| let processedText = text; | |
| const imageBlockRegex = /((?:!\[.*?\]\(.*?\)(?:\s| )*){2,})/g; | |
| processedText = processedText.replace(imageBlockRegex, (match) => { | |
| return `\n\`\`\`slider\n${match.trim()}\n\`\`\`\n`; | |
| }); | |
| const parts = processedText.split(/(```[\s\S]*?```|`[^`\n]+`|!\[[\s\S]*?\]\([\s\S]*?\))/g); | |
| const processedParts = parts.map(part => { | |
| if (part.startsWith("```") || part.startsWith("`") || part.startsWith("![")) return part; | |
| let processed = part; | |
| const mermaidRegex = /^(?:graph|flowchart|classDiagram|stateDiagram|erDiagram|sequenceDiagram|gantt|pie|quadrantChart|xychart|mindmap|timeline)[\s\S]+?(?=\n\n|\n$|$)/gm; | |
| processed = processed.replace(mermaidRegex, (match) => { | |
| if (match.trim().split("\n").length > 1) { | |
| return `\n\`\`\`mermaid\n${match.trim()}\n\`\`\`\n`; | |
| } | |
| return match; | |
| }); | |
| const treeRegex = /((?:^[ \t]*[βββββββ€βΌ|].*$\n?)+)/gm; | |
| processed = processed.replace(treeRegex, (match) => { | |
| const lines = match.trim().split("\n"); | |
| const hasStrongTreeChars = /[βββββββ€βΌββ]/.test(match); | |
| if (lines.length > 1 || hasStrongTreeChars) { | |
| return `\n\`\`\`tree\n${match.trim()}\n\`\`\`\n`; | |
| } | |
| return match; | |
| }); | |
| processed = processed.replace(/^[ \t\u00a0]*[β’ββΒ·][ \t\u00a0]*/gm, "- "); | |
| processed = processed.replace(/\\\[([\s\S]*?)\\\]/g, "$$$$1$$"); | |
| processed = processed.replace(/\\\(([\s\S]*?)\\\)/g, "$$1$"); | |
| processed = processed.replace(/(\((.+?)\)|\[(.+?)\])/g, (match, full, p1, p2) => { | |
| const content = (p1 || p2 || "").trim(); | |
| if (!content) return match; | |
| const hasMathOperator = /[\\_^={}+\-*/]/.test(content); | |
| const isSingleVar = /^[a-z]$/i.test(content); | |
| const isExpression = /^[a-z0-9] *[+\-*/=] *[a-z0-9]/i.test(content); | |
| if (hasMathOperator || isSingleVar || isExpression) { | |
| const delim = (match.startsWith("[") || content.length > 50) ? "$$" : "$"; | |
| return `${delim}${match}${delim}`; | |
| } | |
| return match; | |
| }); | |
| const lines = processed.split("\n"); | |
| const resultLines = lines.map(line => { | |
| const trimmed = line.trim(); | |
| if ( | |
| (trimmed.includes("\\") || /[_^]/.test(trimmed) || /^[a-z] *=/i.test(trimmed)) && | |
| !trimmed.includes("$") && | |
| !trimmed.startsWith("#") && | |
| !trimmed.startsWith("-") && | |
| !trimmed.startsWith("*") && | |
| trimmed.length > 0 | |
| ) { | |
| if (/^[a-zA-Z0-9] *=/.test(trimmed) || /^\\[a-zA-Z]+/.test(trimmed)) { | |
| return `$$${trimmed}$$`; | |
| } | |
| } | |
| return line; | |
| }); | |
| return resultLines.join("\n"); | |
| }); | |
| let finalProcessed = processedParts.join(""); | |
| finalProcessed = finalProcessed.replace(/([^\n])\n- /g, "$1\n\n- "); | |
| return finalProcessed; | |
| } | |
| export function AnswerRenderer({ answer }) { | |
| const processedAnswer = preprocessContent(answer); | |
| return ( | |
| <div className="answer-body"> | |
| <ReactMarkdown | |
| remarkPlugins={[remarkGfm, remarkMath]} | |
| rehypePlugins={[rehypeHighlight, rehypeKatex]} | |
| components={{ | |
| code({ node, inline, className, children, ...props }) { | |
| const match = /language-(\w+)/.exec(className || ""); | |
| const content = String(children).replace(/\n$/, ""); | |
| if (!inline && match?.[1] === "mermaid") { | |
| return <Mermaid chart={content} />; | |
| } | |
| if (!inline && match?.[1] === "slider") { | |
| const imageMatches = content.match(/!\[.*?\]\((.*?)\)/g) || []; | |
| const imageUrls = imageMatches.map(m => { | |
| const urlMatch = m.match(/\((.*?)\)/); | |
| return urlMatch ? urlMatch[1] : null; | |
| }).filter(Boolean); | |
| return ( | |
| <div className="image-slider-container"> | |
| {imageUrls.map((url, idx) => ( | |
| <div key={idx} className="slider-item"> | |
| <img src={url} alt={`Image ${idx + 1}`} loading="lazy" /> | |
| </div> | |
| ))} | |
| </div> | |
| ); | |
| } | |
| const isTree = match?.[1] === "tree" || | |
| /[βββββββ€βΌββ]/.test(content) || | |
| (content.includes("|") && /[ββ]/.test(content)) || | |
| /^[ \t]*[|β].*$/m.test(content); | |
| if (!inline && isTree) { | |
| const handleCopy = () => { | |
| navigator.clipboard.writeText(content); | |
| }; | |
| return ( | |
| <div className="chatgpt-style-block"> | |
| <div className="block-header"> | |
| <span className="block-title">STRUCTURE</span> | |
| <button className="copy-button" title="Copy code" onClick={handleCopy}> | |
| <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg> | |
| </button> | |
| </div> | |
| <pre className="block-content"> | |
| <code>{children}</code> | |
| </pre> | |
| </div> | |
| ); | |
| } | |
| return !inline && match ? ( | |
| <pre className={className}> | |
| <code className={className} {...props}> | |
| {children} | |
| </code> | |
| </pre> | |
| ) : ( | |
| <code className={className} {...props}> | |
| {children} | |
| </code> | |
| ); | |
| }, | |
| }} | |
| > | |
| {processedAnswer} | |
| </ReactMarkdown> | |
| </div> | |
| ); | |
| } | |