{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "message-bubble",
  "title": "AI Message Bubble",
  "description": "Chat message component with user/assistant variants and copy functionality",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "registry/default/blocks/ai/ai-message-bubble/components/elements/ai-message-bubble.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { Check, Copy, User } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nimport { AnthropicLogo } from \"@/components/ui/logos/anthropic\";\nimport { CohereLogo } from \"@/components/ui/logos/cohere\";\nimport { DeepSeekLogo } from \"@/components/ui/logos/deepseek\";\nimport { GeminiLogo } from \"@/components/ui/logos/gemini\";\nimport { GroqLogo } from \"@/components/ui/logos/groq\";\nimport { MetaLogo } from \"@/components/ui/logos/meta\";\nimport { MistralLogo } from \"@/components/ui/logos/mistral\";\nimport { OpenAILogo } from \"@/components/ui/logos/openai\";\nimport { XAILogo } from \"@/components/ui/logos/xai\";\n\ntype MessageRole = \"user\" | \"assistant\";\n\ntype Provider =\n  | \"openai\"\n  | \"anthropic\"\n  | \"google\"\n  | \"xai\"\n  | \"deepseek\"\n  | \"mistral\"\n  | \"groq\"\n  | \"cohere\"\n  | \"meta\";\n\nconst PROVIDER_LOGOS: Record<\n  Provider,\n  React.ComponentType<{ className?: string }>\n> = {\n  openai: OpenAILogo,\n  anthropic: AnthropicLogo,\n  google: GeminiLogo,\n  xai: XAILogo,\n  deepseek: DeepSeekLogo,\n  mistral: MistralLogo,\n  groq: GroqLogo,\n  cohere: CohereLogo,\n  meta: MetaLogo,\n};\n\ninterface AiMessageBubbleProps {\n  role: MessageRole;\n  content?: string;\n  provider?: Provider;\n  timestamp?: Date;\n  avatar?: React.ReactNode;\n  isStreaming?: boolean;\n  className?: string;\n  children?: React.ReactNode;\n}\n\nexport function AiMessageBubble({\n  role,\n  content,\n  provider,\n  timestamp,\n  avatar,\n  isStreaming = false,\n  className,\n  children,\n}: AiMessageBubbleProps) {\n  const [copied, setCopied] = React.useState(false);\n\n  const isUser = role === \"user\";\n\n  const handleCopy = React.useCallback(async () => {\n    await navigator.clipboard.writeText(content);\n    setCopied(true);\n    setTimeout(() => setCopied(false), 2000);\n  }, [content]);\n\n  const ProviderLogo = provider ? PROVIDER_LOGOS[provider] : null;\n\n  const defaultAvatar = isUser ? (\n    <div className=\"flex size-7 items-center justify-center border bg-foreground text-background\">\n      <User className=\"size-3.5\" />\n    </div>\n  ) : ProviderLogo ? (\n    <div className=\"flex size-7 items-center justify-center border bg-background\">\n      <ProviderLogo className=\"size-3.5\" />\n    </div>\n  ) : (\n    <div className=\"flex size-7 items-center justify-center border bg-background\">\n      <svg\n        className=\"size-3.5\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      >\n        <title>AI</title>\n        <path d=\"M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h1a7 7 0 0 1 7 7h1a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v1a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-1H2a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h1a7 7 0 0 1 7-7h1V5.73c-.6-.34-1-.99-1-1.73a2 2 0 0 1 2-2z\" />\n        <path d=\"M7.5 13a1.5 1.5 0 1 0 3 0 1.5 1.5 0 0 0-3 0Z\" />\n        <path d=\"M13.5 13a1.5 1.5 0 1 0 3 0 1.5 1.5 0 0 0-3 0Z\" />\n        <path d=\"M8 17h8\" />\n      </svg>\n    </div>\n  );\n\n  return (\n    <div\n      data-slot=\"ai-message-bubble\"\n      role=\"article\"\n      aria-label={isUser ? \"Your message\" : \"AI response\"}\n      className={cn(\n        \"group flex gap-3 font-mono\",\n        isUser && \"flex-row-reverse\",\n        className\n      )}\n    >\n      <div className=\"shrink-0\">{avatar || defaultAvatar}</div>\n\n      <div\n        className={cn(\n          \"relative max-w-[80%] px-3 py-2\",\n          isUser ? \"border bg-foreground text-background\" : \"text-foreground\"\n        )}\n      >\n        <div aria-live={isStreaming ? \"polite\" : undefined}>\n          {children ? (\n            <div className=\"prose prose-xs dark:prose-invert max-w-none text-[13px] leading-relaxed [&_h1]:text-base [&_h2]:text-sm [&_h3]:text-[13px] [&_h4]:text-[13px] [&_p]:text-[13px] [&_li]:text-[13px] [&_code]:text-xs [&_pre]:text-xs\">\n              {children}\n            </div>\n          ) : (\n            <p className=\"m-0 whitespace-pre-wrap text-[13px] leading-relaxed\">\n              {content}\n            </p>\n          )}\n          {isStreaming && (\n            <span className=\"ml-1 inline-block h-3.5 w-[2px] animate-pulse bg-current\" />\n          )}\n        </div>\n\n        {timestamp && (\n          <time className=\"mt-2 block text-[10px] uppercase tracking-wider opacity-60\">\n            {timestamp.toLocaleTimeString([], {\n              hour: \"2-digit\",\n              minute: \"2-digit\",\n            })}\n          </time>\n        )}\n\n        {!isUser && !isStreaming && (\n          <button\n            type=\"button\"\n            className=\"absolute -right-8 top-0.5 flex size-6 items-center justify-center border bg-background opacity-0 transition-opacity hover:bg-muted group-hover:opacity-100\"\n            onClick={handleCopy}\n          >\n            {copied ? (\n              <Check className=\"size-3\" />\n            ) : (\n              <Copy className=\"size-3\" />\n            )}\n            <span className=\"sr-only\">Copy message</span>\n          </button>\n        )}\n      </div>\n    </div>\n  );\n}\n\nexport type { AiMessageBubbleProps, MessageRole, Provider };\n",
      "type": "registry:component"
    }
  ],
  "docs": "Role-based chat bubbles with avatars, timestamps, and copy-on-hover for AI responses.",
  "categories": [
    "ai"
  ],
  "type": "registry:ui"
}