File size: 6,161 Bytes
81fc505 c223633 81fc505 c223633 ada5620 73ae812 8bdf3a8 0ac1aa1 b0ed8c8 0ac1aa1 c23aeab ada5620 3ecfe24 c23aeab b0ed8c8 3ecfe24 c23aeab b0ed8c8 c23aeab bcf42ee c23aeab bcf42ee c23aeab 8bdf3a8 c23aeab 507dbd3 c23aeab 8bdf3a8 81fc505 73ae812 81fc505 c223633 d093bd9 ada5620 0ac1aa1 0e9d509 b0ed8c8 d093bd9 bcf42ee 1dcb857 bcf42ee 1dcb857 d093bd9 c223633 8bdf3a8 81fc505 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | 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>
);
}
|