import { isValidElement } from "react"; import ReactMarkdown from "react-markdown"; import type { Components } from "react-markdown"; import remarkGfm from "remark-gfm"; import remarkMath from "remark-math"; import rehypeKatex from "rehype-katex"; import { SortableMarkdownTable } from "./SortableMarkdownTable"; import { PlotlyChartBlock, type PlotlyChartSpec } from "./PlotlyChartBlock"; function repairMarkdownMarkers(line: string) { return line .replace(/([A-Za-z0-9])(?=\*\*)/g, "$1 ") .replace(/(\*\*[^*\n]+\*\*)(?=[A-Za-z0-9])/g, "$1 ") .replace(/\*\*([^*\n]*?)\*\*/g, (_, inner) => `**${inner.trim()}**`); } function normalizeReadableMarkdown(content: string) { const lines = content.replace(/\r\n/g, "\n").split("\n"); let inCodeBlock = false; const normalizedLines = lines.map((line) => { if (line.trimStart().startsWith("```")) { inCodeBlock = !inCodeBlock; return line; } if (inCodeBlock) return line; return repairMarkdownMarkers(line) .replace(/([.!?])\s*-\s*(?=[A-Z0-9*])/g, "$1\n\n- ") .replace(/(^|\n)-(?=\*\*|[A-Z0-9])/g, "$1- ") .replace(/(? { const trimmed = line.trim(); if (!trimmed || /^[-*+]\s/.test(trimmed) || /^\d+\.\s/.test(trimmed)) return line; const previous = normalizedLines.slice(0, index).reverse().find((item) => item.trim()); const next = normalizedLines.slice(index + 1).find((item) => item.trim()); const isStandaloneBold = /^\*\*.+\*\*(?:\s+.+)?$/.test(trimmed); const belongsToBoldList = previous?.trim().endsWith(":") || previous?.trim().startsWith("**") || next?.trim().startsWith("**"); return isStandaloneBold && belongsToBoldList ? `- ${trimmed}` : line; }) .join("\n") .replace(/\n{3,}/g, "\n\n") .trim(); } const markdownComponents: Components = { p: ({ children }) =>
{children}
, ul: ({ children }) =>{children};
}
return {children};
},
pre: ({ children }) => {
const child = Array.isArray(children) ? children[0] : children;
const isChart = isValidElement(child) && child.type === PlotlyChartBlock;
if (isChart) return <>{children}>;
return {children};
},
table: ({ children }) =>