"use client" import ReactMarkdown from "react-markdown" import remarkGfm from "remark-gfm" import rehypeHighlight from "rehype-highlight" import "highlight.js/styles/github-dark.css" import { DiffTool } from "./diff-tool" import { useState, isValidElement, type ReactNode } from "react" import { cn } from "@/lib/utils" import { Copy } from "lucide-react" interface MarkdownRendererProps { content: string className?: string tone?: "default" | "bubble" } // Parse diff tool calls from markdown content function parseDiffTools(content: string) { const diffToolRegex = /```diff-tool\n([\s\S]*?)\n```/g const tools: Array<{ match: string; props: any }> = [] let match while ((match = diffToolRegex.exec(content)) !== null) { try { const props = JSON.parse(match[1]) tools.push({ match: match[0], props }) } catch (e) { console.error('Failed to parse diff tool:', e) } } return tools } export function MarkdownRenderer({ content, className = "", tone = "default" }: MarkdownRendererProps) { // Parse diff tools from content const diffTools = parseDiffTools(content) let processedContent = content // Replace diff tool calls with placeholders diffTools.forEach((tool, index) => { processedContent = processedContent.replace(tool.match, `__DIFF_TOOL_${index}__`) }) const baseTone = tone === "bubble" ? "text-charcoal dark:text-white" : "text-charcoal dark:text-foreground" const mutedTone = tone === "bubble" ? "text-charcoal/80 dark:text-white/80" : "text-charcoal/80 dark:text-foreground/75" return (
{ const text = typeof children === 'string' ? children : children?.toString() || '' const diffToolMatch = text.match(/^__DIFF_TOOL_(\d+)__$/) if (diffToolMatch) { const index = parseInt(diffToolMatch[1]) const tool = diffTools[index] if (tool) { return ( ) } } return (

{children}

) }, // Custom styling for different elements h1: ({ children }) => (

{children}

), h2: ({ children }) => (

{children}

), h3: ({ children }) => (

{children}

), ul: ({ children }) => (
    {children}
), ol: ({ children }) => (
    {children}
), li: ({ children }) => (
  • {children}
  • ), code: ({ children, className }) => { // Check if this is inline code (no language class) or block code const isInline = !className if (isInline) { return ( {children} ) } return ( {children} ) }, pre: ({ children, className }) => ( {children} ), blockquote: ({ children }) => (
    {children}
    ), a: ({ children, href }) => ( {children} ), strong: ({ children }) => ( {children} ), em: ({ children }) => ( {children} ), table: ({ children }) => (
    {children}
    ), thead: ({ children }) => ( {children} ), tbody: ({ children }) => ( {children} ), tr: ({ children }) => ( {children} ), th: ({ children }) => ( {children} ), td: ({ children }) => ( {children} ), }} > {content}
    ) } function PreWithCopy({ children, className }: { children?: ReactNode; className?: string }) { const [copied, setCopied] = useState(false) const text = extractCodeText(children) const handleCopy = async () => { try { await navigator.clipboard.writeText(text.trimEnd()) setCopied(true) setTimeout(() => setCopied(false), 1200) } catch (error) { console.error("[markdown] Code copy failed", error) } } return (
            {children}
          
    ) } function extractCodeText(node: ReactNode): string { if (typeof node === "string") { return node } if (Array.isArray(node)) { return node.map(extractCodeText).join("") } if (isValidElement(node)) { return extractCodeText(node.props.children) } return "" }