"use client"; import { memo, useMemo, type ReactNode } from "react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import remarkMath from "remark-math"; import rehypeKatex from "rehype-katex"; import rehypeHighlight from "rehype-highlight"; import { Eye } from "lucide-react"; import { CopyButton } from "../ui"; const PREVIEWABLE = new Set(["html", "svg", "mermaid", "jsx", "tsx", "react", "markdown", "md", "python", "py", "typescript", "ts"]); export interface MarkdownProps { text: string; onPreview?: (code: string, lang: string) => void; className?: string } function Code({ className, children, onPreview, ...rest }: { className?: string; children?: ReactNode; onPreview?: MarkdownProps["onPreview"]; node?: unknown }) { const lang = /language-([\w+-]+)/.exec(className ?? "")?.[1]; const raw = String(children ?? "").replace(/\n$/, ""); if (!lang && !raw.includes("\n")) return {children}; return ( <>
{lang ?? "texte"} {lang && PREVIEWABLE.has(lang) && onPreview && }
{children}
); } export const Markdown = memo(function Markdown({ text, onPreview, className }: MarkdownProps) { // Normalize \[ \] and \( \) LaTeX delimiters that some models emit const src = useMemo(() => text.replace(/\\\[((?:.|\n)*?)\\\]/g, (_, m) => `$$${m}$$`).replace(/\\\((.+?)\\\)/g, (_, m) => `$${m}$`), [text]); return (
, pre: ({ children }) => <>{children}, a: ({ href, children }) => {children}, table: ({ children }) =>
{children}
, }}> {src}
); });