import { memo, useMemo } from "react" import DOMPurify from "dompurify" import ReactMarkdown from "react-markdown" import type { Components } from "react-markdown" interface HTMLContentRendererProps { content: string className?: string } /** * Detects if content is likely HTML based on common HTML patterns */ const isHTMLContent = (content: string): boolean => { // Check for HTML tags, entities, and DOCTYPE const htmlPatterns = [ /<[a-z][\s\S]*>/i, // HTML tags /&[a-z]+;/i, // HTML entities //i, // Closing tags ] return htmlPatterns.some((pattern) => pattern.test(content)) } export const HTMLContentRenderer = memo( ({ content, className = "" }: HTMLContentRendererProps) => { const { isHTML, isMarkdown, processedContent } = useMemo(() => { const contentIsHTML = isHTMLContent(content) if (contentIsHTML) { return { isHTML: true, isMarkdown: false, processedContent: DOMPurify.sanitize(content), } } let processed = content if (content.includes("\n$ ")) { processed = content.replace(/^\$ (.*$)/gm, "```bash\n$ $1\n```") } if ( content.trim().startsWith("{") && content.includes('"') && content.includes(":") ) { const lines = content.split("\n") let inJsonBlock = false const jsonLines: string[] = [] const otherLines: string[] = [] for (const line of lines) { if (line.trim() === "{" || line.trim() === "[") { inJsonBlock = true } if (inJsonBlock) { jsonLines.push(line) if (line.trim() === "}" || line.trim() === "]") { inJsonBlock = false } } else { otherLines.push(line) } } if (jsonLines.length > 0 && jsonLines.join("\n").trim()) { const jsonBlock = jsonLines.join("\n") const otherContent = otherLines.join("\n") processed = otherContent + (otherContent ? "\n\n" : "") + "```json\n" + jsonBlock + "\n```" } } return { isHTML: false, isMarkdown: true, processedContent: processed, } }, [content]) if (isHTML) { return (
) } if (isMarkdown) { try { const components: Components = { h1: ({ children }) => (

{children}

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

{children}

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

{children}

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

{children}

), h5: ({ children }) => (
{children}
), h6: ({ children }) => (
{children}
), p: ({ children }) => (

{children}

), strong: ({ children }) => ( {children} ), em: ({ children }) => ( {children} ), code: ({ children, className }) => ( {children} ), pre: ({ children }) => (
							{children}
						
), blockquote: ({ children }) => (
{children}
), a: ({ children, href }) => ( {children} ), ul: ({ children }) => ( ), ol: ({ children }) => (
    {children}
), li: ({ children }) =>
  • {children}
  • , } return (
    {processedContent}
    ) } catch { return (

    {processedContent}

    ) } } }, ) HTMLContentRenderer.displayName = "HTMLContentRenderer"