import React from 'react'; /** * Props for the MarkdownMessage component. */ interface MarkdownMessageProps { /** Raw markdown content to render */ content: string; /** Custom CSS classes for the container */ className?: string; } /** * Lightweight Markdown Parser and Renderer. * * @description * A simplified markdown renderer that converts basic syntax (Headers, Bold, Italic, * Links, Inline Code, Lists) into sanitized HTML. Optimized for chat messages * where a full-blown parser might be overkill. * * @ai-rules * 1. Sanitization: Uses regex for conversion. Be cautious with complex markdown structures. * 2. Styling: Injected HTML uses specific Tailwind classes for consistent typography. * 3. Security: Uses `dangerouslySetInnerHTML`. Ensure `content` source is trusted or pre-sanitized if necessary. */ export function MarkdownMessage({ content, className = '' }: MarkdownMessageProps) { // Convert simple Markdown to HTML const convertMarkdownToHtml = (markdown: string): string => { let html = markdown; // Escape existing HTML html = html.replace(//g, '>'); // Headers (large to small to avoid conflicts) html = html.replace( /^### (.*$)/gim, '

$1

' ); html = html.replace( /^## (.*$)/gim, '

$1

' ); html = html.replace( /^# (.*$)/gim, '

$1

' ); // Bold html = html.replace(/\*\*(.*?)\*\*/g, '$1'); // Italic html = html.replace(/\*(.*?)\*/g, '$1'); // Links html = html.replace( /\[([^\]]+)\]\(([^)]+)\)/g, '$1' ); // Code inline html = html.replace( /`([^`]+)`/g, '$1' ); // Unordered lists (bullet points) - properly wrap in ul html = html.replace(/((?:^[•\-] .*$(?:\n|$))+)/gim, match => { const items = match .trim() .split('\n') .map( item => `
  • ${item.replace(/^[•\-]\s+/, '')}
  • ` ) .join(''); return ``; }); // Ordered lists - properly wrap in ol html = html.replace(/((?:^\d+\. .*$(?:\n|$))+)/gim, match => { const items = match .trim() .split('\n') .map( item => `
  • ${item.replace(/^\d+\.\s+/, '')}
  • ` ) .join(''); return `
      ${items}
    `; }); // GFM Tables — must run before line-break transforms html = html.replace(/((?:^\|.+\|[ \t]*(?:\r?\n|$))+)/gm, match => { const rows = match .trim() .split(/\r?\n/) .filter(line => line.trim()); if (rows.length < 2) return match; const isSeparatorRow = /^\|[\s\-:|]+\|$/.test(rows[1].trim()); if (!isSeparatorRow) return match; const parseCells = (row: string) => row .split('|') .slice(1, -1) .map(cell => cell.trim()); const headerCells = parseCells(rows[0]) .map( cell => `${cell}` ) .join(''); const bodyRows = rows .slice(2) .map( row => `${parseCells(row) .map(cell => `${cell}`) .join('')}` ) .join(''); return `
    ${headerCells}${bodyRows}
    `; }); // Emojis and icons (keep as is) // Line breaks (preserve double line breaks as paragraphs) html = html.replace(/\n\n/g, '

    '); html = html.replace(/\n/g, '
    '); // Wrap in initial paragraph html = '

    ' + html + '

    '; // Clean up empty paragraphs html = html.replace(/]*>\s*<\/p>/g, ''); return html; }; const htmlContent = convertMarkdownToHtml(content); return (
    ); }