{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ai-chat-1",
  "title": "Assistant Thread With Timestamps",
  "description": "AI assistant chat interface with user and assistant bubbles, avatars, message timestamps, auto-following scroll, and a single-line composer.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "avatar",
    "bubble",
    "button",
    "card",
    "input",
    "message",
    "message-scroller",
    "separator"
  ],
  "files": [
    {
      "path": "registry/blocks/ai-chat/1/ai-chat-block.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport { Bubble, BubbleContent } from \"@/components/ui/bubble\"\nimport { Button } from \"@/components/ui/button\"\nimport { Card, CardContent, CardFooter, CardHeader } from \"@/components/ui/card\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  Message,\n  MessageAvatar,\n  MessageContent,\n  MessageFooter,\n} from \"@/components/ui/message\"\nimport {\n  MessageScroller,\n  MessageScrollerButton,\n  MessageScrollerContent,\n  MessageScrollerItem,\n  MessageScrollerProvider,\n  MessageScrollerViewport,\n} from \"@/components/ui/message-scroller\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { IconPlaceholder } from \"@/components/icons/icon-placeholder\"\n\ntype MessageRole = \"user\" | \"assistant\"\n\ninterface ChatMessage {\n  id: number\n  role: MessageRole\n  content: string\n  time: string\n}\n\nconst userAvatar = \"https://i.pravatar.cc/150?img=24\"\nconst userName = \"Jordan Davis\"\n\nconst INITIAL_MESSAGES: ChatMessage[] = [\n  {\n    id: 1,\n    role: \"assistant\",\n    content:\n      \"Hello! I'm Acme AI. How can I help you today? You can ask me anything about your account, products, or support topics.\",\n    time: \"9:41 AM\",\n  },\n  {\n    id: 2,\n    role: \"user\",\n    content: \"Can you summarise last quarter's revenue for the EMEA region?\",\n    time: \"9:42 AM\",\n  },\n  {\n    id: 3,\n    role: \"assistant\",\n    content:\n      \"Sure! EMEA revenue for Q3 2024 was $2.84M, up 18.3% quarter-over-quarter. The strongest contributors were the UK (+$420k), Germany (+$310k), and France (+$275k). Churn remained low at 2.1%, and new logo ARR accounted for 34% of the total.\",\n    time: \"9:42 AM\",\n  },\n  {\n    id: 4,\n    role: \"user\",\n    content: \"Great. Which product line drove the most growth?\",\n    time: \"9:43 AM\",\n  },\n  {\n    id: 5,\n    role: \"assistant\",\n    content:\n      \"The Acme Pro tier led growth, contributing $1.12M (+26% QoQ). Enterprise seats added 41 upsells this quarter. The legacy Starter plan declined by $88k as customers migrated to Pro, which is expected per our migration roadmap.\",\n    time: \"9:43 AM\",\n  },\n]\n\nconst CANNED_REPLIES = [\n  \"Got it. Let me pull that together. Based on the latest data, the trend is holding steady with healthy growth across your top segments.\",\n  \"Good question. The numbers point to a positive trajectory; I'd recommend reviewing the dashboard for the segment-level breakdown.\",\n  \"Here's a quick take: the key drivers remain consistent with last quarter, and there are no anomalies worth flagging right now.\",\n  \"Sure thing. I've drafted a summary you can share. It highlights the headline figure, the main growth lever, and the current churn rate.\",\n]\n\nfunction formatTime() {\n  return new Date().toLocaleTimeString([], {\n    hour: \"numeric\",\n    minute: \"2-digit\",\n  })\n}\n\nexport default function AiChatBlock() {\n  const [messages, setMessages] =\n    React.useState<ChatMessage[]>(INITIAL_MESSAGES)\n  const [draft, setDraft] = React.useState(\"\")\n  const nextId = React.useRef(INITIAL_MESSAGES.length + 1)\n  const replyIdx = React.useRef(0)\n  const replyTimers = React.useRef<ReturnType<typeof setTimeout>[]>([])\n\n  React.useEffect(() => {\n    const timers = replyTimers.current\n    return () => timers.forEach(clearTimeout)\n  }, [])\n\n  function send() {\n    const text = draft.trim()\n    if (!text) return\n\n    const userMsg: ChatMessage = {\n      id: nextId.current++,\n      role: \"user\",\n      content: text,\n      time: formatTime(),\n    }\n    setMessages((prev) => [...prev, userMsg])\n    setDraft(\"\")\n\n    const reply = CANNED_REPLIES[replyIdx.current % CANNED_REPLIES.length]\n    replyIdx.current += 1\n    const timer = setTimeout(() => {\n      setMessages((prev) => [\n        ...prev,\n        {\n          id: nextId.current++,\n          role: \"assistant\",\n          content: reply,\n          time: formatTime(),\n        },\n      ])\n    }, 600)\n    replyTimers.current.push(timer)\n  }\n\n  return (\n    <section className=\"flex w-full items-center justify-center bg-background px-6 py-12 text-foreground\">\n      <MessageScrollerProvider autoScroll defaultScrollPosition=\"end\">\n        <Card className=\"w-full max-w-xl border border-border shadow-sm\">\n          <CardHeader className=\"px-4 py-3\">\n            <div className=\"flex items-center gap-3\">\n              <span className=\"flex size-7 shrink-0 items-center justify-center rounded-md bg-primary text-primary-foreground\">\n                <IconPlaceholder\n                  lucide=\"Bot\"\n                  tabler=\"IconRobot\"\n                  hugeicons=\"RobotIcon\"\n                  phosphor=\"Robot\"\n                  remixicon=\"RiRobot2Line\"\n                  className=\"size-4\"\n                  aria-hidden=\"true\"\n                />\n              </span>\n              <div className=\"flex flex-col gap-0.5\">\n                <span className=\"text-sm leading-none font-semibold tracking-tight\">\n                  Acme AI Assistant\n                </span>\n                <span className=\"flex items-center gap-1 text-[11px] text-muted-foreground\">\n                  <span className=\"inline-block size-1.5 bg-primary\" />\n                  Online, responds instantly\n                </span>\n              </div>\n              <span className=\"ml-auto flex items-center gap-1 text-[11px] font-medium text-muted-foreground\">\n                <IconPlaceholder\n                  lucide=\"Sparkles\"\n                  tabler=\"IconSparkles\"\n                  hugeicons=\"SparklesIcon\"\n                  phosphor=\"Sparkle\"\n                  remixicon=\"RiSparklingLine\"\n                  className=\"size-3.5\"\n                  aria-hidden=\"true\"\n                />\n                GPT-4o\n              </span>\n            </div>\n          </CardHeader>\n\n          <Separator />\n\n          <CardContent className=\"p-0\">\n            <MessageScroller className=\"h-[420px]\">\n              <MessageScrollerViewport>\n                <MessageScrollerContent className=\"gap-5 px-4 py-4\">\n                  {messages.map((msg) => (\n                    <MessageScrollerItem\n                      key={msg.id}\n                      messageId={String(msg.id)}\n                      scrollAnchor={msg.role === \"user\"}\n                    >\n                      <Message align={msg.role === \"user\" ? \"end\" : \"start\"}>\n                        <MessageAvatar>\n                          <Avatar className=\"size-7 border border-border\">\n                            {msg.role === \"user\" ? (\n                              <>\n                                <AvatarImage\n                                  src={userAvatar}\n                                  alt={userName}\n                                  className=\"grayscale\"\n                                />\n                                <AvatarFallback className=\"text-[10px] font-medium\">\n                                  JD\n                                </AvatarFallback>\n                              </>\n                            ) : (\n                              <AvatarFallback className=\"bg-primary text-primary-foreground\">\n                                <IconPlaceholder\n                                  lucide=\"Sparkles\"\n                                  tabler=\"IconSparkles\"\n                                  hugeicons=\"SparklesIcon\"\n                                  phosphor=\"Sparkle\"\n                                  remixicon=\"RiSparklingFill\"\n                                  className=\"size-4\"\n                                  aria-hidden=\"true\"\n                                />\n                              </AvatarFallback>\n                            )}\n                          </Avatar>\n                        </MessageAvatar>\n                        <MessageContent>\n                          <Bubble\n                            variant={msg.role === \"user\" ? \"default\" : \"muted\"}\n                          >\n                            <BubbleContent className=\"whitespace-pre-line\">\n                              {msg.content}\n                            </BubbleContent>\n                          </Bubble>\n                          <MessageFooter className=\"tabular-nums\">\n                            {msg.time}\n                          </MessageFooter>\n                        </MessageContent>\n                      </Message>\n                    </MessageScrollerItem>\n                  ))}\n                </MessageScrollerContent>\n              </MessageScrollerViewport>\n              <MessageScrollerButton />\n            </MessageScroller>\n          </CardContent>\n\n          <CardFooter className=\"px-4 py-3\">\n            <form\n              onSubmit={(e) => {\n                e.preventDefault()\n                send()\n              }}\n              className=\"flex w-full items-center gap-2\"\n            >\n              <Input\n                value={draft}\n                onChange={(e) => setDraft(e.target.value)}\n                placeholder=\"Type a message…\"\n                aria-label=\"Message\"\n                className=\"h-8 flex-1 text-xs\"\n              />\n              <Button\n                type=\"submit\"\n                size=\"icon\"\n                className=\"size-8 shrink-0\"\n                aria-label=\"Send message\"\n                disabled={!draft.trim()}\n              >\n                <IconPlaceholder\n                  lucide=\"Send\"\n                  tabler=\"IconSend\"\n                  hugeicons=\"SentIcon\"\n                  phosphor=\"PaperPlane\"\n                  remixicon=\"RiSendPlaneLine\"\n                  className=\"size-3.5\"\n                  aria-hidden=\"true\"\n                />\n              </Button>\n            </form>\n          </CardFooter>\n        </Card>\n      </MessageScrollerProvider>\n    </section>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "meta": {
    "height": "706px",
    "tier": "free"
  },
  "categories": [
    "ai-chat"
  ],
  "type": "registry:block"
}