import React, { Suspense, useEffect, useState } from "react" import Markdown from "react-markdown" import remarkGfm from "remark-gfm" import { cn } from "@/lib/utils" import { CopyButton } from "@/components/ui/copy-button" interface MarkdownRendererProps { children: string } export function MarkdownRenderer({ children }: MarkdownRendererProps) { return (
{children}
) } interface HighlightedPre extends React.HTMLAttributes { children: string language: string } const HighlightedPre = React.memo( ({ children, language, ...props }: HighlightedPre) => { const [tokens, setTokens] = useState(null) const [isSupported, setIsSupported] = useState(false) useEffect(() => { let mounted = true const loadAndHighlight = async () => { try { const { codeToTokens, bundledLanguages } = await import("shiki") if (!mounted) return if (!(language in bundledLanguages)) { setIsSupported(false) return } setIsSupported(true) const { tokens: highlightedTokens } = await codeToTokens(children, { lang: language as keyof typeof bundledLanguages, defaultColor: false, themes: { light: "github-light", dark: "github-dark", }, }) if (mounted) { setTokens(highlightedTokens) } } catch (error) { if (mounted) { setIsSupported(false) } } } loadAndHighlight() return () => { mounted = false } }, [children, language]) if (!isSupported) { return
{children}
} if (!tokens) { return
{children}
} return (
        
          {tokens.map((line, lineIndex) => (
            
              
                {line.map((token, tokenIndex) => {
                  const style =
                    typeof token.htmlStyle === "string"
                      ? undefined
                      : token.htmlStyle

                  return (
                    
                      {token.content}
                    
                  )
                })}
              
              {lineIndex !== tokens.length - 1 && "\n"}
            
          ))}
        
      
) } ) HighlightedPre.displayName = "HighlightedCode" interface CodeBlockProps extends React.HTMLAttributes { children: React.ReactNode className?: string language: string } const CodeBlock = ({ children, className, language, ...restProps }: CodeBlockProps) => { const code = typeof children === "string" ? children : childrenTakeAllStringContents(children) const preClass = cn( "overflow-x-scroll rounded-md border bg-background/50 p-4 font-mono text-sm [scrollbar-width:none]", className ) return (
{children} } > {code}
) } function childrenTakeAllStringContents(element: any): string { if (typeof element === "string") { return element } if (element?.props?.children) { let children = element.props.children if (Array.isArray(children)) { return children .map((child) => childrenTakeAllStringContents(child)) .join("") } else { return childrenTakeAllStringContents(children) } } return "" } const COMPONENTS = { h1: withClass("h1", "text-2xl font-semibold"), h2: withClass("h2", "font-semibold text-xl"), h3: withClass("h3", "font-semibold text-lg"), h4: withClass("h4", "font-semibold text-base"), h5: withClass("h5", "font-medium"), strong: withClass("strong", "font-semibold"), a: withClass("a", "text-primary underline underline-offset-2"), blockquote: withClass("blockquote", "border-l-2 border-primary pl-4"), code: ({ children, className, node, ...rest }: any) => { const match = /language-(\w+)/.exec(className || "") return match ? ( {children} ) : ( &]:rounded-md [:not(pre)>&]:bg-background/50 [:not(pre)>&]:px-1 [:not(pre)>&]:py-0.5" )} {...rest} > {children} ) }, pre: ({ children }: any) => children, ol: withClass("ol", "list-decimal space-y-2 pl-6"), ul: withClass("ul", "list-disc space-y-2 pl-6"), li: withClass("li", "my-1.5"), table: withClass( "table", "w-full border-collapse overflow-y-auto rounded-md border border-foreground/20" ), th: withClass( "th", "border border-foreground/20 px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right" ), td: withClass( "td", "border border-foreground/20 px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right" ), tr: withClass("tr", "m-0 border-t p-0 even:bg-muted"), p: withClass("p", "whitespace-pre-wrap"), hr: withClass("hr", "border-foreground/20"), } function withClass(Tag: keyof JSX.IntrinsicElements, classes: string) { const Component = ({ node, ...props }: any) => ( ) Component.displayName = Tag return Component } export default MarkdownRenderer