import { cn } from "@/lib/utils"; import { useMemo } from "react"; import Markdown from "react-markdown"; import rehypeMathjax from "rehype-mathjax"; import rehypeRaw from "rehype-raw"; import remarkGfm from "remark-gfm"; import CodeComponent from "./code-component"; type MarkdownRendererProps = { chatMessage: string; }; const preprocessChatMessage = (text: string): string => { // Handle tags let processed = text .replace(//g, "``") .replace(/<\/think>/g, "``"); // Clean up tables if present if (isMarkdownTable(processed)) { processed = cleanupTableEmptyCells(processed); } return processed; }; export const isMarkdownTable = (text: string): boolean => { if (!text?.trim()) return false; // Single regex to detect markdown table with header separator return /\|.*\|.*\n\s*\|[\s\-:]+\|/m.test(text); }; export const cleanupTableEmptyCells = (text: string): string => { return text .split("\n") .filter((line) => { const trimmed = line.trim(); // Keep non-table lines if (!trimmed.includes("|")) return true; // Keep separator rows (contain only |, -, :, spaces) if (/^\|[\s\-:]+\|$/.test(trimmed)) return true; // For data rows, check if any cell has content const cells = trimmed.split("|").slice(1, -1); // Remove delimiter cells return cells.some((cell) => cell.trim() !== ""); }) .join("\n"); }; export const MarkdownRenderer = ({ chatMessage }: MarkdownRendererProps) => { // Process the chat message to handle tags and clean up tables const processedChatMessage = preprocessChatMessage(chatMessage); // Memoize the components object to prevent CodeComponent recreation const markdownComponents = useMemo( () => ({ p({ node, ...props }: { node?: any; [key: string]: any }) { return (

{props.children}

); }, ol({ node, ...props }: { node?: any; [key: string]: any }) { return
    {props.children}
; }, ul({ node, ...props }: { node?: any; [key: string]: any }) { return
    {props.children}
; }, li({ node, ...props }: { node?: any; [key: string]: any }) { return
  • {props.children}
  • ; }, h1({ node, ...props }: { node?: any; [key: string]: any }) { return (

    {props.children}

    ); }, h2({ node, ...props }: { node?: any; [key: string]: any }) { return (

    {props.children}

    ); }, h3({ node, ...props }: { node?: any; [key: string]: any }) { return (

    {props.children}

    ); }, h4({ node, ...props }: { node?: any; [key: string]: any }) { return (

    {props.children}

    ); }, hr({ node, ...props }: { node?: any; [key: string]: any }) { return
    ; }, blockquote({ node, ...props }: { node?: any; [key: string]: any }) { return (
    {props.children}
    ); }, pre({ node, ...props }: { node?: any; [key: string]: any }) { return <>{props.children}; }, table: ({ node, ...props }: { node?: any; [key: string]: any }) => { return (
    {props.children}
    ); }, thead: ({ node, ...props }: { node?: any; [key: string]: any }) => { return ( {props.children} ); }, tbody: ({ node, ...props }: { node?: any; [key: string]: any }) => { return ( {props.children} ); }, th: ({ node, ...props }: { node?: any; [key: string]: any }) => { return ( {props.children} ); }, td: ({ node, ...props }: { node?: any; [key: string]: any }) => { return ( {props.children} ); }, code: ({ node, className, inline, children, ...props }: { node?: any; [key: string]: any; }) => { let content = children as string; if ( Array.isArray(children) && children.length === 1 && typeof children[0] === "string" ) { content = children[0] as string; } if (typeof content === "string") { if (content.length) { if (content[0] === "▍") { return ; } // Specifically handle tags that were wrapped in backticks if (content === "" || content === "") { return ( {content} ); } } const match = /language-(\w+)/.exec(className || ""); return !inline ? ( ) : ( {content} ); } }, }), [] ); return (
    {processedChatMessage}
    ); };