Frontend-Data-Eyond / src /app /components /chat /renderers /MarkdownRenderer.tsx
ishaq101's picture
[NOTICKET] Major update, re-stylign and upgrade using maintiva demo setup
c0ddd13
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import type { Components } from "react-markdown";
function preprocessMarkdown(content: string): string {
let result = content.replace(/\|\|/g, "|\n|");
const lines = result.split("\n");
const processed = lines.map((line) => {
const tableStart = line.indexOf("|");
if (tableStart > 0) {
const tableContent = line.slice(tableStart);
if ((tableContent.match(/\|/g) ?? []).length >= 2) {
return line.slice(0, tableStart).trimEnd() + "\n\n" + tableContent;
}
}
return line;
});
result = processed.join("\n");
result = result.replace(/([^|\n])\n(\|)/g, "$1\n\n$2");
return result;
}
const components: Components = {
p: ({ children }) => (
<p className="text-sm text-neutral-800 leading-relaxed mb-3 last:mb-0">{children}</p>
),
h1: ({ children }) => (
<h1 className="text-xl font-bold text-neutral-900 mt-4 mb-2 first:mt-0">{children}</h1>
),
h2: ({ children }) => (
<h2 className="text-lg font-semibold text-neutral-800 mt-4 mb-2 first:mt-0">{children}</h2>
),
h3: ({ children }) => (
<h3 className="text-base font-semibold text-neutral-700 mt-3 mb-1.5 first:mt-0">{children}</h3>
),
ul: ({ children }) => (
<ul className="list-disc list-outside pl-5 mb-3 space-y-1 text-sm text-neutral-800">{children}</ul>
),
ol: ({ children }) => (
<ol className="list-decimal list-outside pl-5 mb-3 space-y-1 text-sm text-neutral-800">{children}</ol>
),
li: ({ children }) => <li className="leading-relaxed">{children}</li>,
code: ({ children, className }) => {
const isBlock = className?.startsWith("language-");
const language = className?.replace("language-", "") ?? "";
if (isBlock) {
return (
<div className="my-3 rounded-xl overflow-hidden border border-neutral-200">
{language && (
<div className="bg-neutral-50 border-b border-neutral-200 px-4 py-2">
<span className="text-xs font-mono text-neutral-500">{language}</span>
</div>
)}
<div className="p-4 bg-neutral-50 overflow-x-auto">
<code className="text-xs font-mono leading-relaxed text-neutral-800 whitespace-pre">
{children}
</code>
</div>
</div>
);
}
return (
<code className="px-1.5 py-0.5 rounded-md bg-neutral-100 text-brand-green font-mono text-xs">
{children}
</code>
);
},
pre: ({ children }) => <>{children}</>,
blockquote: ({ children }) => (
<blockquote className="border-l-4 border-brand-green pl-4 py-1 my-3 text-sm text-neutral-600 italic bg-brand-green-50 rounded-r-xl">
{children}
</blockquote>
),
a: ({ children, href }) => (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="text-brand-green underline underline-offset-2 hover:text-brand-green/80 transition-colors"
>
{children}
</a>
),
strong: ({ children }) => <strong className="font-semibold">{children}</strong>,
hr: () => <hr className="border-neutral-200 my-3" />,
table: ({ children }) => (
<div className="my-3 overflow-x-auto rounded-xl border border-neutral-200 shadow-sm">
<table className="w-full text-sm border-collapse">{children}</table>
</div>
),
thead: ({ children }) => (
<thead className="bg-neutral-50 border-b border-neutral-200">{children}</thead>
),
th: ({ children }) => (
<th className="px-4 py-3 text-left text-xs font-semibold text-neutral-600 uppercase tracking-wider">
{children}
</th>
),
td: ({ children }) => (
<td className="px-4 py-3 text-sm text-neutral-800 border-b border-neutral-100">{children}</td>
),
tr: ({ children }) => (
<tr className="hover:bg-neutral-50 transition-colors">{children}</tr>
),
};
interface MarkdownRendererProps {
content: string;
skipPreprocess?: boolean;
}
export default function MarkdownRenderer({ content, skipPreprocess }: MarkdownRendererProps) {
const processed = skipPreprocess ? content : preprocessMarkdown(content);
if (!skipPreprocess) {
const hasCR = content.includes("\r");
const hasDoubleNewline = content.includes("\n\n");
// console.log(
// `[MarkdownRenderer] skipPreprocess=false contentLen=${content.length} same=${content === processed} hasCR=${hasCR} hasDoubleNewline=${hasDoubleNewline}`
// );
// console.log("[MarkdownRenderer] CONTENT JSON →", JSON.stringify(content.slice(0, 600)));
}
return (
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeKatex]}
components={components}
>
{processed}
</ReactMarkdown>
);
}