import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import rehypeRaw from 'rehype-raw'; import { CodeRenderer } from './CodeRenderer'; import { MermaidRenderer } from './MermaidRenderer'; import type { RenderableContent } from '../../types'; interface ContentRendererProps { content: string; className?: string; } export function ContentRenderer({ content, className = '' }: ContentRendererProps) { // Detect content type and render accordingly const renderableContent = detectContentType(content); switch (renderableContent.type) { case 'mermaid': return ; case 'code': return ( ); case 'markdown': default: return (
; } return ( 5} /> ); } return ( {children} ); }, // Custom link rendering a({ href, children, ...props }) { return ( {children} ); }, // Custom table rendering table({ children, ...props }) { return (
{children}
); }, // Custom blockquote rendering blockquote({ children, ...props }) { return (
{children}
); }, }} > {content}
); } } function detectContentType(content: string): RenderableContent { const trimmedContent = content.trim(); // Check for mermaid diagrams if (trimmedContent.startsWith('```mermaid') && trimmedContent.endsWith('```')) { const mermaidContent = trimmedContent.slice(10, -3).trim(); return { type: 'mermaid', content: mermaidContent, }; } // Check for code blocks const codeBlockMatch = trimmedContent.match(/^```(\w+)?\n([\s\S]*?)\n```$/); if (codeBlockMatch) { return { type: 'code', content: codeBlockMatch[2], language: codeBlockMatch[1] || 'text', }; } // Check for HTML content if (trimmedContent.startsWith('<') && trimmedContent.endsWith('>')) { const htmlTagMatch = trimmedContent.match(/^<(\w+)[\s\S]*<\/\1>$/); if (htmlTagMatch) { return { type: 'html', content: trimmedContent, }; } } // Default to markdown return { type: 'markdown', content: trimmedContent, }; }