"use client"; import * as React from "react"; import { cn } from "@/lib/utils"; import type { Components } from "react-markdown"; import { Copy, Check, ExternalLink } from "lucide-react"; import hljs from "highlight.js"; import "highlight.js/styles/github.css"; import DOMPurify from "dompurify"; /** * Markdown Components for Streamdown * * This module provides customized components for rendering markdown content with syntax highlighting. * It uses highlight.js for code syntax highlighting and supports streaming content updates. * * @example * ```tsx * import { createMarkdownComponents } from './markdown-components'; * import { Streamdown } from 'streamdown'; * * const MarkdownRenderer = ({ content }) => { * const components = createMarkdownComponents(); * return {content}; * }; * ``` */ /** * Determines if a text block looks like code based on common code patterns * @param text - The text to analyze * @returns boolean indicating if the text appears to be code */ const looksLikeCode = (text: string): boolean => { const codeIndicators = [ /^import\s+/m, /^function\s+/m, /^class\s+/m, /^const\s+/m, /^let\s+/m, /^var\s+/m, /[{}[\]();]/, /^\s*\/\//m, /^\s*\/\*/m, /=>/, /^export\s+/m, ]; return codeIndicators.some((pattern) => pattern.test(text)); }; /** * Header component for code blocks with language display and copy functionality */ const CodeHeader = ({ language, code, }: { language?: string; code?: string; }) => { const [copied, setCopied] = React.useState(false); const copyToClipboard = () => { if (!code) return; navigator.clipboard.writeText(code); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return (
{language}
); }; /** * Creates a set of components for use with streamdown * @returns Components object for streamdown */ export const createMarkdownComponents = (): Components => ({ code: function Code({ className, children, ...props }) { const match = /language-(\w+)/.exec(className ?? ""); const content = String(children).replace(/\n$/, ""); const deferredContent = React.useDeferredValue(content); const highlighted = React.useMemo(() => { if (!match || !looksLikeCode(deferredContent)) return null; try { return hljs.highlight(deferredContent, { language: match[1] }).value; } catch { return deferredContent; } }, [deferredContent, match]); if (match && looksLikeCode(content)) { return (
              
            
); } return ( {children} ); }, /** * Paragraph component with minimal vertical margin */ p: ({ children }) =>

{children}

, /** * Heading 1 component with large text and proper spacing * Used for main section headers */ h1: ({ children }) => (

{children}

), /** * Heading 2 component for subsection headers * Slightly smaller than h1 with adjusted spacing */ h2: ({ children }) => (

{children}

), /** * Heading 3 component for minor sections * Used for smaller subdivisions within h2 sections */ h3: ({ children }) => (

{children}

), /** * Heading 4 component for the smallest section divisions * Maintains consistent text size with adjusted spacing */ h4: ({ children }) => (

{children}

), /** * Unordered list component with disc-style bullets * Indented from the left margin */ ul: ({ children }) => , /** * Ordered list component with decimal numbering * Indented from the left margin */ ol: ({ children }) =>
    {children}
, /** * List item component with normal line height * Used within both ordered and unordered lists */ li: ({ children }) =>
  • {children}
  • , /** * Blockquote component for quoted content * Features a left border and italic text with proper spacing */ blockquote: ({ children }) => (
    {children}
    ), /** * Anchor component for links * Opens links in new tab with security attributes * Includes hover underline effect */ a: ({ href, children }) => ( {children} ), /** * Horizontal rule component * Creates a visual divider with proper spacing */ hr: () =>
    , /** * Table container component * Handles overflow for wide tables with proper spacing */ table: ({ children }) => (
    {children}
    ), /** * Table header cell component * Features bold text and distinct background */ th: ({ children }) => ( {children} ), /** * Table data cell component * Consistent styling with header cells */ td: ({ children }) => ( {children} ), });