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>
  );
}