{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ai-chat-1",
  "title": "AI Chat 1: full chat app pane",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A complete assistant screen: collapsible conversation sidebar, model picker, messages with reasoning and tool calls, streaming replies, copy and regenerate, and a prompt with attachments.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/ai-chat.json",
    "https://ui.ballmac.com/r/ai-message.json",
    "https://ui.ballmac.com/r/button.json",
    "https://ui.ballmac.com/r/prompt-input.json",
    "https://ui.ballmac.com/r/reasoning-disclosure.json",
    "https://ui.ballmac.com/r/select.json",
    "https://ui.ballmac.com/r/streaming-text.json",
    "https://ui.ballmac.com/r/tool-call-card.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/blocks/ai-chat-1/ai-chat-1.tsx",
      "content": "// Ballmac UI: AI Chat 1. https://ui.ballmac.com/blocks/ai-chat-1\n\"use client\"\n\nimport * as React from \"react\"\nimport { MessageSquare, PanelLeft, Plus, RotateCcw } from \"lucide-react\"\n\nimport { Chat, ChatFooter, ChatMessages } from \"@/components/ballmac/ai-chat\"\nimport { Message, MessageAction, MessageActions, MessageAvatar, MessageContent, MessageCopyAction } from \"@/components/ballmac/ai-message\"\nimport { Button } from \"@/components/ballmac/button\"\nimport { PromptInput, PromptInputAttachButton, PromptInputAttachments, PromptInputSubmit, PromptInputTextarea, PromptInputToolbar } from \"@/components/ballmac/prompt-input\"\nimport { ReasoningDisclosure } from \"@/components/ballmac/reasoning-disclosure\"\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ballmac/select\"\nimport { StreamingText } from \"@/components/ballmac/streaming-text\"\nimport { ToolCallCard, type ToolCallCardProps } from \"@/components/ballmac/tool-call-card\"\nimport { cn } from \"@/lib/utils\"\n\ntype ChatTurn = {\n  id: string\n  role: \"user\" | \"assistant\"\n  text: string\n  /** Assistant turns only: still arriving. */\n  streaming?: boolean\n  /** Assistant turns only: reasoning shown above the answer. */\n  reasoning?: { text: string; seconds?: number }\n  /** Assistant turns only: tool calls made while answering. */\n  tools?: Pick<ToolCallCardProps, \"name\" | \"status\" | \"duration\" | \"input\" | \"result\">[]\n}\n\ntype AiChat1Props = Omit<React.ComponentProps<\"div\">, \"onSubmit\"> & {\n  /** Conversations in the sidebar. */\n  conversations?: { id: string; title: string }[]\n  /** The open conversation's id. */\n  activeConversation?: string\n  /** Called when a conversation is picked or \"New chat\" is pressed (with null). */\n  onSelectConversation?: (id: string | null) => void\n  /** The messages of the open conversation. */\n  messages?: ChatTurn[]\n  /** Models offered in the picker. */\n  models?: { id: string; label: string }[]\n  /** Selected model id. */\n  model?: string\n  /** Called when another model is picked. */\n  onModelChange?: (id: string) => void\n  /** Called with the prompt and attached files. */\n  onSubmit?: (value: string, files: File[]) => void\n  /** Called by the Stop button while a reply streams. */\n  onStop?: () => void\n  /** True while the assistant is replying. */\n  streaming?: boolean\n}\n\nconst demoConversations = [\n  { id: \"c1\", title: \"Q3 revenue summary\" },\n  { id: \"c2\", title: \"Fix flaky checkout test\" },\n  { id: \"c3\", title: \"Draft launch announcement\" },\n]\nconst demoMessages: ChatTurn[] = [\n  { id: \"m1\", role: \"user\", text: \"Why did the checkout test fail on the last three runs?\" },\n  {\n    id: \"m2\",\n    role: \"assistant\",\n    reasoning: { text: \"Check the CI logs for the failing assertion, then compare timings between passing and failing runs.\", seconds: 6 },\n    tools: [{ name: \"read_ci_logs\", status: \"success\", duration: 612, input: { job: \"e2e\", runs: 3 }, result: \"Timeout waiting for #pay-button (3/3)\" }],\n    text: \"All three failures time out waiting for the pay button. It renders after the payment form loads, which got slower after the new fraud check. Waiting for the form's ready state instead of a fixed timeout fixes it.\",\n  },\n]\nconst demoModels = [\n  { id: \"fast\", label: \"Fast\" },\n  { id: \"balanced\", label: \"Balanced\" },\n  { id: \"deep\", label: \"Deep reasoning\" },\n]\n\nfunction AiChat1({\n  conversations = demoConversations,\n  activeConversation = \"c2\",\n  onSelectConversation,\n  messages = demoMessages,\n  models = demoModels,\n  model: modelProp,\n  onModelChange,\n  onSubmit,\n  onStop,\n  streaming = false,\n  className,\n  ...props\n}: AiChat1Props) {\n  const [sidebar, setSidebar] = React.useState(true)\n  const [modelState, setModelState] = React.useState(models[1]?.id ?? models[0]?.id)\n  const model = modelProp ?? modelState\n  return (\n    <div data-slot=\"ai-chat-1\" className={cn(\"flex h-[720px] w-full overflow-hidden border bg-background\", className)} {...props}>\n      <aside hidden={!sidebar} className=\"hidden w-64 shrink-0 flex-col border-e bg-card md:flex [&[hidden]]:hidden\">\n        <div className=\"p-3\">\n          <Button variant=\"outline\" className=\"w-full justify-start\" onClick={() => onSelectConversation?.(null)}>\n            <Plus /> New chat\n          </Button>\n        </div>\n        <nav aria-label=\"Conversations\" className=\"min-h-0 flex-1 overflow-y-auto px-2 pb-3\">\n          <p className=\"px-2 pt-2 pb-1 font-mono text-[11px] tracking-[0.14em] text-muted-foreground uppercase\">Recent</p>\n          <ul className=\"space-y-0.5\">\n            {conversations.map((c) => (\n              <li key={c.id}>\n                <button\n                  type=\"button\"\n                  aria-current={c.id === activeConversation ? \"page\" : undefined}\n                  onClick={() => onSelectConversation?.(c.id)}\n                  className={cn(\n                    \"flex w-full items-center gap-2 rounded-md px-2 py-2 text-start text-sm outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n                    c.id === activeConversation ? \"bg-accent text-foreground\" : \"text-muted-foreground hover:bg-accent/60 hover:text-foreground\"\n                  )}\n                >\n                  <MessageSquare className=\"size-4 shrink-0\" aria-hidden=\"true\" />\n                  <span className=\"truncate\">{c.title}</span>\n                </button>\n              </li>\n            ))}\n          </ul>\n        </nav>\n      </aside>\n      <div className=\"flex min-w-0 flex-1 flex-col\">\n        <div className=\"flex h-14 shrink-0 items-center gap-2 border-b px-3\">\n          <Button variant=\"ghost\" size=\"icon-sm\" className=\"hidden md:inline-flex\" aria-label={sidebar ? \"Hide conversations\" : \"Show conversations\"} aria-pressed={sidebar} onClick={() => setSidebar((s) => !s)}>\n            <PanelLeft  className=\"rtl:-scale-x-100\"/>\n          </Button>\n          <p className=\"truncate text-sm font-medium\">{conversations.find((c) => c.id === activeConversation)?.title ?? \"New chat\"}</p>\n          <div className=\"ms-auto\">\n            <Select\n              value={model}\n              onValueChange={(v) => {\n                setModelState(v)\n                onModelChange?.(v)\n              }}\n            >\n              <SelectTrigger size=\"sm\" aria-label=\"Model\" className=\"w-40\">\n                <SelectValue />\n              </SelectTrigger>\n              <SelectContent align=\"end\">\n                {models.map((m) => (\n                  <SelectItem key={m.id} value={m.id}>\n                    {m.label}\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n          </div>\n        </div>\n        <Chat className=\"min-h-0 flex-1\">\n          <ChatMessages contentClassName=\"mx-auto w-full max-w-3xl px-4 py-6\">\n            {messages.map((m) =>\n              m.role === \"user\" ? (\n                <Message key={m.id} role=\"user\">\n                  <MessageContent>{m.text}</MessageContent>\n                </Message>\n              ) : (\n                <Message key={m.id} role=\"assistant\">\n                  <MessageAvatar>AI</MessageAvatar>\n                  <MessageContent>\n                    {m.reasoning && (\n                      <ReasoningDisclosure streaming={m.streaming && !m.text} duration={m.reasoning.seconds}>\n                        {m.reasoning.text}\n                      </ReasoningDisclosure>\n                    )}\n                    {m.tools?.map((t, i) => <ToolCallCard key={`${t.name}-${i}`} {...t} />)}\n                    <StreamingText text={m.text} streaming={m.streaming} />\n                  </MessageContent>\n                  {!m.streaming && (\n                    <MessageActions>\n                      <MessageCopyAction value={m.text} />\n                      <MessageAction label=\"Regenerate\">\n                        <RotateCcw />\n                      </MessageAction>\n                    </MessageActions>\n                  )}\n                </Message>\n              )\n            )}\n          </ChatMessages>\n          <ChatFooter contentClassName=\"mx-auto w-full max-w-3xl px-4 pb-4\">\n            <PromptInput onSubmit={(v, f) => onSubmit?.(v, f)} status={streaming ? \"streaming\" : \"idle\"} onStop={onStop}>\n              <PromptInputAttachments />\n              <PromptInputTextarea placeholder=\"Message the assistant…\" />\n              <PromptInputToolbar>\n                <PromptInputAttachButton />\n                <PromptInputSubmit className=\"ms-auto\" />\n              </PromptInputToolbar>\n            </PromptInput>\n            <p className=\"mt-2 text-center text-xs text-muted-foreground\">Answers can be wrong. Check important details.</p>\n          </ChatFooter>\n        </Chat>\n      </div>\n    </div>\n  )\n}\n\nexport { AiChat1, type AiChat1Props, type ChatTurn }\n",
      "type": "registry:component",
      "target": "@components/ballmac/blocks/ai-chat-1/ai-chat-1.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "ai",
      "chat",
      "assistant",
      "agent",
      "copilot",
      "app"
    ],
    "version": "1.0.0",
    "updated": "2026-09-28",
    "ai": {
      "summary": "A ready-made chat screen driven entirely by props. Map your chat state (for example the Vercel AI SDK's useChat messages) to `messages`, call your API in `onSubmit`, and pass `streaming` while a reply arrives.",
      "whenToUse": [
        "Assistant, copilot or agent products",
        "Internal AI tools that need history and model choice"
      ],
      "whenNotToUse": [
        "A small embedded chat widget (compose ai-chat, ai-message and prompt-input instead)"
      ],
      "composesWith": [
        "hero-3",
        "ai-chat",
        "prompt-input"
      ],
      "a11y": [],
      "customization": [
        "messages: { id, role, text, streaming?, reasoning?, tools? }[]",
        "conversations, activeConversation, onSelectConversation(id | null)",
        "models, model, onModelChange",
        "onSubmit(value, files), onStop, streaming"
      ]
    },
    "examples": [
      "ai-chat-1-demo"
    ],
    "url": "https://ui.ballmac.com/components/ai-chat-1"
  },
  "categories": [
    "blocks",
    "ai-chat"
  ],
  "type": "registry:block"
}