{ "name": "message", "type": "registry:ui", "registryDependencies": [ "avatar", "tooltip" ], "dependencies": [ "react-markdown", "remark-gfm", "shiki", "marked", "remark-breaks" ], "devDependencies": [], "tailwind": {}, "cssVars": { "light": {}, "dark": {} }, "description": "A component for displaying chat messages with support for avatars, markdown content, and interactive actions", "files": [ { "path": "message.tsx", "content": "import { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\"\nimport { cn } from \"@/lib/utils\"\nimport { Markdown } from \"./markdown\"\n\nexport type MessageProps = {\n children: React.ReactNode\n className?: string\n} & React.HTMLProps\n\nconst Message = ({ children, className, ...props }: MessageProps) => (\n
\n {children}\n
\n)\n\nexport type MessageAvatarProps = {\n src: string\n alt: string\n fallback?: string\n delayMs?: number\n className?: string\n}\n\nconst MessageAvatar = ({\n src,\n alt,\n fallback,\n delayMs,\n className,\n}: MessageAvatarProps) => {\n return (\n \n \n {fallback && (\n {fallback}\n )}\n \n )\n}\n\nexport type MessageContentProps = {\n children: React.ReactNode\n markdown?: boolean\n className?: string\n} & React.ComponentProps &\n React.HTMLProps\n\nconst MessageContent = ({\n children,\n markdown = false,\n className,\n ...props\n}: MessageContentProps) => {\n const classNames = cn(\n \"rounded-lg p-2 text-foreground bg-secondary prose break-words whitespace-normal\",\n className\n )\n\n return markdown ? (\n \n {children as string}\n \n ) : (\n
\n {children}\n
\n )\n}\n\nexport type MessageActionsProps = {\n children: React.ReactNode\n className?: string\n} & React.HTMLProps\n\nconst MessageActions = ({\n children,\n className,\n ...props\n}: MessageActionsProps) => (\n \n {children}\n \n)\n\nexport type MessageActionProps = {\n className?: string\n tooltip: React.ReactNode\n children: React.ReactNode\n side?: \"top\" | \"bottom\" | \"left\" | \"right\"\n} & React.ComponentProps\n\nconst MessageAction = ({\n tooltip,\n children,\n className,\n side = \"top\",\n ...props\n}: MessageActionProps) => {\n return (\n \n \n {children}\n \n {tooltip}\n \n \n \n )\n}\n\nexport { Message, MessageAvatar, MessageContent, MessageActions, MessageAction }\n", "type": "registry:ui" }, { "path": "markdown.tsx", "content": "import { cn } from \"@/lib/utils\"\nimport { marked } from \"marked\"\nimport { memo, useId, useMemo } from \"react\"\nimport ReactMarkdown, { Components } from \"react-markdown\"\nimport remarkBreaks from \"remark-breaks\"\nimport remarkGfm from \"remark-gfm\"\nimport { CodeBlock, CodeBlockCode } from \"./code-block\"\n\nexport type MarkdownProps = {\n children: string\n id?: string\n className?: string\n components?: Partial\n}\n\nfunction parseMarkdownIntoBlocks(markdown: string): string[] {\n const tokens = marked.lexer(markdown)\n return tokens.map((token) => token.raw)\n}\n\nfunction extractLanguage(className?: string): string {\n if (!className) return \"plaintext\"\n const match = className.match(/language-(\\w+)/)\n return match ? match[1] : \"plaintext\"\n}\n\nconst INITIAL_COMPONENTS: Partial = {\n code: function CodeComponent({ className, children, ...props }) {\n const isInline =\n !props.node?.position?.start.line ||\n props.node?.position?.start.line === props.node?.position?.end.line\n\n if (isInline) {\n return (\n \n {children}\n \n )\n }\n\n const language = extractLanguage(className)\n\n return (\n \n \n \n )\n },\n pre: function PreComponent({ children }) {\n return <>{children}\n },\n}\n\nconst MemoizedMarkdownBlock = memo(\n function MarkdownBlock({\n content,\n components = INITIAL_COMPONENTS,\n }: {\n content: string\n components?: Partial\n }) {\n return (\n \n {content}\n \n )\n },\n function propsAreEqual(prevProps, nextProps) {\n return prevProps.content === nextProps.content\n }\n)\n\nMemoizedMarkdownBlock.displayName = \"MemoizedMarkdownBlock\"\n\nfunction MarkdownComponent({\n children,\n id,\n className,\n components = INITIAL_COMPONENTS,\n}: MarkdownProps) {\n const generatedId = useId()\n const blockId = id ?? generatedId\n const blocks = useMemo(() => parseMarkdownIntoBlocks(children), [children])\n\n return (\n
\n {blocks.map((block, index) => (\n \n ))}\n
\n )\n}\n\nconst Markdown = memo(MarkdownComponent)\nMarkdown.displayName = \"Markdown\"\n\nexport { Markdown }\n", "type": "registry:ui" }, { "path": "code-block.tsx", "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport React, { useEffect, useState } from \"react\"\nimport { codeToHtml } from \"shiki\"\n\nexport type CodeBlockProps = {\n children?: React.ReactNode\n className?: string\n} & React.HTMLProps\n\nfunction CodeBlock({ children, className, ...props }: CodeBlockProps) {\n return (\n \n {children}\n \n )\n}\n\nexport type CodeBlockCodeProps = {\n code: string\n language?: string\n theme?: string\n className?: string\n} & React.HTMLProps\n\nfunction CodeBlockCode({\n code,\n language = \"tsx\",\n theme = \"github-light\",\n className,\n ...props\n}: CodeBlockCodeProps) {\n const [highlightedHtml, setHighlightedHtml] = useState(null)\n\n useEffect(() => {\n async function highlight() {\n if (!code) {\n setHighlightedHtml(\"
\")\n return\n }\n\n const html = await codeToHtml(code, { lang: language, theme })\n setHighlightedHtml(html)\n }\n highlight()\n }, [code, language, theme])\n\n const classNames = cn(\n \"w-full overflow-x-auto text-[13px] [&>pre]:px-4 [&>pre]:py-4\",\n className\n )\n\n // SSR fallback: render plain code if not hydrated yet\n return highlightedHtml ? (\n \n ) : (\n
\n
\n        {code}\n      
\n
\n )\n}\n\nexport type CodeBlockGroupProps = React.HTMLAttributes\n\nfunction CodeBlockGroup({\n children,\n className,\n ...props\n}: CodeBlockGroupProps) {\n return (\n \n {children}\n \n )\n}\n\nexport { CodeBlockGroup, CodeBlockCode, CodeBlock }\n", "type": "registry:ui" } ] }