{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "eve-chat",
  "type": "registry:item",
  "title": "Eve Chat",
  "description": "Chat page for an Eve agent, rendering the session through an assistant-ui thread.",
  "dependencies": [
    "@assistant-ui/eve",
    "@assistant-ui/react",
    "lucide-react",
    "zustand",
    "class-variance-authority",
    "@assistant-ui/react-markdown",
    "remark-gfm",
    "tw-shimmer",
    "radix-ui"
  ],
  "docs": "Eve installs registry files without touching CSS, so add the reasoning and collapsible styles to app/globals.css, and replace the default auth policy in agent/channels/eve.ts before deploying: https://www.assistant-ui.com/docs/runtimes/eve/quickstart",
  "meta": {
    "eve": {
      "requires": ">=0.27.6"
    }
  },
  "css": {
    "@import \"tw-shimmer\"": {},
    "@custom-variant data-open (&:where([data-state=\"open\"], [data-open]:not([data-open=\"false\"])))": {},
    "@custom-variant data-closed (&:where([data-state=\"closed\"], [data-closed]:not([data-closed=\"false\"])))": {},
    "@keyframes collapsible-down": {
      "from": {
        "height": "0"
      },
      "to": {
        "height": "var(--radix-collapsible-content-height, var(--collapsible-panel-height, auto))"
      }
    },
    "@keyframes collapsible-up": {
      "from": {
        "height": "var(--radix-collapsible-content-height, var(--collapsible-panel-height, auto))"
      },
      "to": {
        "height": "0"
      }
    }
  },
  "files": [
    {
      "type": "registry:file",
      "path": "app/page.tsx",
      "target": "app/page.tsx",
      "sourcePath": "apps/registry/templates/eve/app/page.tsx",
      "content": "\"use client\";\n\nimport { Thread } from \"@/components/assistant-ui/elements/thread.aui\";\nimport { AssistantRuntimeProvider } from \"@assistant-ui/react\";\nimport { useEveAgentRuntime } from \"@assistant-ui/eve\";\n\nexport default function Home() {\n  const runtime = useEveAgentRuntime();\n\n  return (\n    <AssistantRuntimeProvider runtime={runtime}>\n      <div className=\"h-dvh\">\n        <Thread />\n      </div>\n    </AssistantRuntimeProvider>\n  );\n}\n"
    },
    {
      "type": "registry:file",
      "path": "components/assistant-ui/elements/thread.aui.tsx",
      "target": "components/assistant-ui/elements/thread.aui.tsx",
      "sourcePath": "packages/ui/src/components/react/assistant-ui/elements/thread.aui.tsx",
      "content": "\"use client\";\n\nimport {\n  ComposerAddAttachment,\n  ComposerAttachments,\n  UserMessageAttachments,\n} from \"@/components/assistant-ui/elements/attachment.aui\";\nimport { File } from \"@/components/assistant-ui/elements/file\";\nimport { ThreadFollowupSuggestions } from \"@/components/assistant-ui/elements/follow-up-suggestions.aui\";\nimport { Image } from \"@/components/assistant-ui/elements/image\";\nimport { MarkdownText } from \"@/components/assistant-ui/elements/markdown-text\";\nimport {\n  Reasoning,\n  ReasoningContent,\n  ReasoningRoot,\n  ReasoningText,\n  ReasoningTrigger,\n} from \"@/components/assistant-ui/elements/reasoning.aui\";\nimport { ToolFallback } from \"@/components/assistant-ui/elements/tool-fallback.aui\";\nimport {\n  ToolGroupContent,\n  ToolGroupRoot,\n  ToolGroupTrigger,\n} from \"@/components/assistant-ui/elements/tool-group.aui\";\nimport { TooltipIconButton } from \"@/components/assistant-ui/elements/tooltip-icon-button\";\nimport { Button } from \"@/components/ui/button\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  ActionBarMorePrimitive,\n  ActionBarPrimitive,\n  AuiIf,\n  type AssistantState,\n  BranchPickerPrimitive,\n  ComposerPrimitive,\n  ErrorPrimitive,\n  groupPartByType,\n  MessagePrimitive,\n  SuggestionPrimitive,\n  ThreadPrimitive,\n  type FileMessagePartComponent,\n  type ImageMessagePartComponent,\n  type ToolCallMessagePartComponent,\n  useAuiState,\n} from \"@assistant-ui/react\";\nimport {\n  ArrowDownIcon,\n  ArrowUpIcon,\n  CheckIcon,\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  CopyIcon,\n  DownloadIcon,\n  MicIcon,\n  MoreHorizontalIcon,\n  PencilIcon,\n  RefreshCwIcon,\n  SquareIcon,\n} from \"lucide-react\";\nimport {\n  createContext,\n  useContext,\n  type ComponentType,\n  type FC,\n  type PropsWithChildren,\n} from \"react\";\n\nexport type ThreadGroupPart = MessagePrimitive.GroupedParts.GroupPart;\n\n/**\n * Optional component overrides for the thread. `AssistantMessage` and\n * `Welcome` replace whole sections; the remaining slots override how the\n * assistant message renders tool calls and part groups. Tool UIs registered\n * by name (toolkit `render`, `useAssistantDataUI`) take precedence over\n * `ToolFallback`.\n */\nexport type ThreadComponents = {\n  AssistantMessage?: ComponentType | undefined;\n  Welcome?: ComponentType | undefined;\n  ToolFallback?: ToolCallMessagePartComponent | undefined;\n  ToolGroup?:\n    | ComponentType<PropsWithChildren<{ group: ThreadGroupPart }>>\n    | undefined;\n  ReasoningGroup?:\n    | ComponentType<PropsWithChildren<{ group: ThreadGroupPart }>>\n    | undefined;\n};\n\nexport type ThreadProps = {\n  components?: ThreadComponents | undefined;\n  autoFocus?: boolean | undefined;\n};\n\nconst EMPTY_COMPONENTS: ThreadComponents = {};\n\nconst ThreadComponentsContext =\n  createContext<ThreadComponents>(EMPTY_COMPONENTS);\n\n// Startup exposes a loading placeholder thread; treat it as a new chat so\n// the composer mounts centered. Loads after startup keep the docked layout.\nconst isNewChatView = (s: AssistantState) =>\n  s.thread.messages.length === 0 &&\n  (!s.thread.isLoading || s.threads.isLoading);\n\n// A switched thread that is still fetching its history: skeleton, not welcome.\nconst isHistoryLoadingView = (s: AssistantState) =>\n  s.thread.messages.length === 0 &&\n  s.thread.isLoading &&\n  !s.thread.isDisabled &&\n  !s.threads.isLoading;\n\nconst ThreadHistorySkeleton: FC = () => (\n  <div\n    data-slot=\"aui_thread-history-skeleton\"\n    role=\"status\"\n    className=\"animate-in fade-in fill-mode-both flex flex-col gap-y-6 [animation-delay:150ms] [animation-duration:200ms]\"\n  >\n    <span className=\"sr-only\">Loading conversation</span>\n    <Skeleton className=\"ml-auto h-9 w-2/5 rounded-xl motion-reduce:animate-none\" />\n    <div className=\"flex flex-col gap-y-2\">\n      <Skeleton className=\"h-4 w-11/12 motion-reduce:animate-none\" />\n      <Skeleton className=\"h-4 w-4/5 motion-reduce:animate-none\" />\n      <Skeleton className=\"h-4 w-3/5 motion-reduce:animate-none\" />\n    </div>\n    <Skeleton className=\"ml-auto h-9 w-1/3 rounded-xl motion-reduce:animate-none\" />\n    <div className=\"flex flex-col gap-y-2\">\n      <Skeleton className=\"h-4 w-10/12 motion-reduce:animate-none\" />\n      <Skeleton className=\"h-4 w-2/3 motion-reduce:animate-none\" />\n    </div>\n  </div>\n);\n\nexport const Thread: FC<ThreadProps> = ({\n  components = EMPTY_COMPONENTS,\n  autoFocus = true,\n}) => {\n  const isEmpty = useAuiState(isNewChatView);\n\n  return (\n    <ThreadComponentsContext.Provider value={components}>\n      <ThreadRoot isEmpty={isEmpty} autoFocus={autoFocus} />\n    </ThreadComponentsContext.Provider>\n  );\n};\n\nconst ThreadRoot: FC<{ isEmpty: boolean; autoFocus: boolean }> = ({\n  isEmpty,\n  autoFocus,\n}) => {\n  const { Welcome = ThreadWelcome } = useContext(ThreadComponentsContext);\n\n  return (\n    <ThreadPrimitive.Root\n      className=\"aui-root aui-thread-root bg-background @container flex h-full flex-col\"\n      style={{\n        [\"--thread-max-width\" as string]: \"44rem\",\n        [\"--composer-bg\" as string]: \"var(--color-card)\",\n        [\"--composer-radius\" as string]: \"1.5rem\",\n        [\"--composer-padding\" as string]: \"8px\",\n      }}\n    >\n      <ThreadPrimitive.Viewport\n        turnAnchor=\"top\"\n        data-slot=\"aui_thread-viewport\"\n        className=\"relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth\"\n      >\n        <div\n          className={cn(\n            \"mx-auto flex w-full max-w-(--thread-max-width) flex-1 flex-col px-4 pt-4\",\n            isEmpty && \"justify-center\",\n          )}\n        >\n          <AuiIf condition={isNewChatView}>\n            <Welcome />\n          </AuiIf>\n          <AuiIf condition={isHistoryLoadingView}>\n            <ThreadHistorySkeleton />\n          </AuiIf>\n\n          <div\n            data-slot=\"aui_message-group\"\n            className=\"mb-14 flex flex-col gap-y-6 empty:hidden\"\n          >\n            <ThreadPrimitive.Messages>\n              {() => <ThreadMessage />}\n            </ThreadPrimitive.Messages>\n          </div>\n\n          <ThreadPrimitive.ViewportFooter\n            className={cn(\n              \"aui-thread-viewport-footer bg-background flex flex-col gap-4 overflow-visible pb-4 md:pb-6\",\n              !isEmpty &&\n                \"sticky bottom-0 mt-auto rounded-t-(--composer-radius)\",\n            )}\n          >\n            <ThreadScrollToBottom />\n            <ThreadFollowupSuggestions />\n            <Composer autoFocus={autoFocus} />\n            <AuiIf condition={(s) => isNewChatView(s) && s.composer.isEmpty}>\n              <ThreadSuggestions />\n            </AuiIf>\n          </ThreadPrimitive.ViewportFooter>\n        </div>\n      </ThreadPrimitive.Viewport>\n    </ThreadPrimitive.Root>\n  );\n};\n\nconst ThreadMessage: FC = () => {\n  const { AssistantMessage: AssistantMessageComponent = AssistantMessage } =\n    useContext(ThreadComponentsContext);\n  const role = useAuiState((s) => s.message.role);\n  const isEditing = useAuiState((s) => s.message.composer.isEditing);\n\n  if (isEditing) return <EditComposer />;\n  if (role === \"user\") return <UserMessage />;\n  return <AssistantMessageComponent />;\n};\n\nconst ThreadScrollToBottom: FC = () => {\n  return (\n    <ThreadPrimitive.ScrollToBottom asChild>\n      <TooltipIconButton\n        tooltip=\"Scroll to bottom\"\n        variant=\"outline\"\n        className=\"aui-thread-scroll-to-bottom dark:border-border dark:bg-background dark:hover:bg-accent absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible\"\n      >\n        <ArrowDownIcon />\n      </TooltipIconButton>\n    </ThreadPrimitive.ScrollToBottom>\n  );\n};\n\nconst ThreadWelcome: FC = () => {\n  return (\n    <div className=\"aui-thread-welcome-root mb-6 flex flex-col items-center px-4 text-center\">\n      <h1 className=\"aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in fill-mode-both text-2xl font-medium tracking-tight duration-200\">\n        How can I help you today?\n      </h1>\n    </div>\n  );\n};\n\nconst ThreadSuggestions: FC = () => {\n  return (\n    <div className=\"aui-thread-welcome-suggestions flex w-full flex-wrap items-center justify-center gap-2 px-4\">\n      <ThreadPrimitive.Suggestions>\n        {() => <ThreadSuggestionItem />}\n      </ThreadPrimitive.Suggestions>\n    </div>\n  );\n};\n\nconst ThreadSuggestionItem: FC = () => {\n  return (\n    <div className=\"aui-thread-welcome-suggestion-display fade-in slide-in-from-bottom-2 animate-in fill-mode-both duration-200\">\n      <SuggestionPrimitive.Trigger send asChild>\n        <Button\n          variant=\"ghost\"\n          className=\"aui-thread-welcome-suggestion text-foreground hover:bg-muted border-border/60 h-auto gap-1.5 rounded-full border px-3.5 py-1.5 text-sm font-normal whitespace-nowrap transition-colors\"\n        >\n          <SuggestionPrimitive.Title className=\"aui-thread-welcome-suggestion-text-1\" />\n          <SuggestionPrimitive.Description className=\"aui-thread-welcome-suggestion-text-2 empty:hidden\" />\n        </Button>\n      </SuggestionPrimitive.Trigger>\n    </div>\n  );\n};\n\nconst Composer: FC<{ autoFocus: boolean }> = ({ autoFocus }) => {\n  return (\n    <ComposerPrimitive.Root className=\"aui-composer-root relative flex w-full flex-col\">\n      <ComposerPrimitive.AttachmentDropzone asChild>\n        <div\n          data-slot=\"aui_composer-shell\"\n          className=\"border-border/60 data-[dragging=true]:border-ring focus-within:border-border dark:border-muted-foreground/15 dark:focus-within:border-muted-foreground/30 flex w-full cursor-text flex-col gap-2 rounded-(--composer-radius) border bg-(--composer-bg) p-(--composer-padding) transition-[border-color] data-[dragging=true]:border-dashed data-[dragging=true]:bg-[color-mix(in_oklab,var(--color-accent)_50%,var(--color-background))]\"\n        >\n          <ComposerAttachments />\n          <ComposerPrimitive.Input\n            placeholder=\"Send a message...\"\n            className=\"aui-composer-input caret-primary placeholder:text-muted-foreground/60 max-h-48 min-h-10 w-full resize-none bg-transparent px-2.5 py-1 text-base leading-6 outline-none\"\n            rows={1}\n            autoFocus={autoFocus}\n            enterKeyHint=\"send\"\n            aria-label=\"Message input\"\n          />\n          <ComposerAction />\n        </div>\n      </ComposerPrimitive.AttachmentDropzone>\n    </ComposerPrimitive.Root>\n  );\n};\n\nconst ComposerAction: FC = () => {\n  return (\n    <div className=\"aui-composer-action-wrapper relative flex items-center justify-between\">\n      <ComposerAddAttachment />\n      <div className=\"flex items-center gap-1.5\">\n        <AuiIf condition={(s) => s.thread.capabilities.dictation}>\n          <AuiIf condition={(s) => s.composer.dictation == null}>\n            <ComposerPrimitive.Dictate asChild>\n              <TooltipIconButton\n                tooltip=\"Voice input\"\n                side=\"bottom\"\n                type=\"button\"\n                variant=\"ghost\"\n                size=\"icon\"\n                className=\"aui-composer-dictate text-muted-foreground hover:text-foreground size-7 rounded-full\"\n                aria-label=\"Start voice input\"\n              >\n                <MicIcon className=\"aui-composer-dictate-icon size-4\" />\n              </TooltipIconButton>\n            </ComposerPrimitive.Dictate>\n          </AuiIf>\n          <AuiIf condition={(s) => s.composer.dictation != null}>\n            <ComposerPrimitive.StopDictation asChild>\n              <TooltipIconButton\n                tooltip=\"Stop dictation\"\n                side=\"bottom\"\n                type=\"button\"\n                variant=\"ghost\"\n                size=\"icon\"\n                className=\"aui-composer-stop-dictation text-destructive size-7 rounded-full\"\n                aria-label=\"Stop voice input\"\n              >\n                <SquareIcon className=\"aui-composer-stop-dictation-icon size-3.5 animate-pulse fill-current\" />\n              </TooltipIconButton>\n            </ComposerPrimitive.StopDictation>\n          </AuiIf>\n        </AuiIf>\n        <AuiIf condition={(s) => !s.thread.isRunning}>\n          <ComposerPrimitive.Send asChild>\n            <TooltipIconButton\n              tooltip=\"Send message\"\n              side=\"bottom\"\n              type=\"button\"\n              variant=\"default\"\n              size=\"icon\"\n              className=\"aui-composer-send size-7 rounded-full\"\n              aria-label=\"Send message\"\n            >\n              <ArrowUpIcon className=\"aui-composer-send-icon size-4\" />\n            </TooltipIconButton>\n          </ComposerPrimitive.Send>\n        </AuiIf>\n        <AuiIf condition={(s) => s.thread.isRunning}>\n          <ComposerPrimitive.Cancel asChild>\n            <Button\n              type=\"button\"\n              variant=\"default\"\n              size=\"icon\"\n              className=\"aui-composer-cancel size-7 rounded-full\"\n              aria-label=\"Stop generating\"\n            >\n              <SquareIcon className=\"aui-composer-cancel-icon size-3.5 fill-current\" />\n            </Button>\n          </ComposerPrimitive.Cancel>\n        </AuiIf>\n      </div>\n    </div>\n  );\n};\n\nconst MessageError: FC = () => {\n  return (\n    <MessagePrimitive.Error>\n      <ErrorPrimitive.Root className=\"aui-message-error-root border-destructive bg-destructive/10 text-destructive dark:bg-destructive/5 mt-2 rounded-md border p-3 text-sm dark:text-red-200\">\n        <ErrorPrimitive.Message className=\"aui-message-error-message line-clamp-2\" />\n      </ErrorPrimitive.Root>\n    </MessagePrimitive.Error>\n  );\n};\n\nconst AssistantMessage: FC = () => {\n  const {\n    ToolFallback: ToolFallbackComponent = ToolFallback,\n    ToolGroup,\n    ReasoningGroup,\n  } = useContext(ThreadComponentsContext);\n\n  const ACTION_BAR_PT = \"pt-1.5\";\n  // Keep the action bar inside the contained root's paint box, then cancel its reserved space in flow.\n  const ACTION_BAR_HEIGHT = `min-h-7.5 ${ACTION_BAR_PT}`;\n\n  return (\n    <MessagePrimitive.Root\n      data-slot=\"aui_assistant-message-root\"\n      data-role=\"assistant\"\n      className=\"fade-in slide-in-from-bottom-1 animate-in relative -mb-7.5 pb-7.5 duration-150 [contain-intrinsic-size:auto_200px] [content-visibility:auto]\"\n    >\n      <div\n        data-slot=\"aui_assistant-message-content\"\n        className=\"text-foreground px-2 leading-relaxed wrap-break-word\"\n      >\n        <MessagePrimitive.GroupedParts\n          groupBy={groupPartByType({\n            reasoning: [\"group-chainOfThought\", \"group-reasoning\"],\n            \"tool-call\": [\"group-chainOfThought\", \"group-tool\"],\n            \"standalone-tool-call\": [],\n          })}\n        >\n          {({ part, children }) => {\n            switch (part.type) {\n              case \"group-chainOfThought\":\n                return <div data-slot=\"aui_chain-of-thought\">{children}</div>;\n              case \"group-tool\":\n                if (ToolGroup) {\n                  return <ToolGroup group={part}>{children}</ToolGroup>;\n                }\n                return (\n                  <ToolGroupRoot variant=\"ghost\">\n                    <ToolGroupTrigger\n                      count={part.indices.length}\n                      active={part.status.type === \"running\"}\n                    />\n                    <ToolGroupContent>{children}</ToolGroupContent>\n                  </ToolGroupRoot>\n                );\n              case \"group-reasoning\": {\n                if (ReasoningGroup) {\n                  return (\n                    <ReasoningGroup group={part}>{children}</ReasoningGroup>\n                  );\n                }\n                const running = part.status.type === \"running\";\n                return (\n                  <ReasoningRoot streaming={running}>\n                    <ReasoningTrigger active={running} />\n                    <ReasoningContent aria-busy={running}>\n                      <ReasoningText>{children}</ReasoningText>\n                    </ReasoningContent>\n                  </ReasoningRoot>\n                );\n              }\n              case \"text\":\n                return <MarkdownText />;\n              case \"reasoning\":\n                return <Reasoning {...part} />;\n              case \"tool-call\":\n                return part.toolUI ?? <ToolFallbackComponent {...part} />;\n              case \"data\":\n                return part.dataRendererUI;\n              case \"file\":\n                return (\n                  <div data-slot=\"aui_assistant-message-file\" className=\"py-1\">\n                    <File {...part} />\n                  </div>\n                );\n              case \"image\":\n                return (\n                  <div data-slot=\"aui_assistant-message-image\" className=\"py-1\">\n                    <Image {...part} />\n                  </div>\n                );\n              case \"indicator\":\n                return (\n                  <span\n                    data-slot=\"aui_assistant-message-indicator\"\n                    className=\"animate-pulse font-sans\"\n                    aria-label=\"Assistant is working\"\n                  >\n                    {\"●\"}\n                  </span>\n                );\n              default:\n                return null;\n            }\n          }}\n        </MessagePrimitive.GroupedParts>\n        <MessageError />\n      </div>\n\n      <div\n        data-slot=\"aui_assistant-message-footer\"\n        className={cn(\"ms-2 flex items-center\", ACTION_BAR_HEIGHT)}\n      >\n        <BranchPicker />\n        <AssistantActionBar />\n      </div>\n    </MessagePrimitive.Root>\n  );\n};\n\nconst AssistantActionBar: FC = () => {\n  return (\n    <ActionBarPrimitive.Root\n      hideWhenRunning\n      autohide=\"not-last\"\n      className=\"aui-assistant-action-bar-root text-muted-foreground animate-in fade-in col-start-3 row-start-2 -ms-1 flex gap-1 duration-200\"\n    >\n      <ActionBarPrimitive.Copy asChild>\n        <TooltipIconButton tooltip=\"Copy\">\n          <AuiIf condition={(s) => s.message.isCopied}>\n            <CheckIcon className=\"animate-in zoom-in-50 fade-in duration-200 ease-out\" />\n          </AuiIf>\n          <AuiIf condition={(s) => !s.message.isCopied}>\n            <CopyIcon className=\"animate-in zoom-in-75 fade-in duration-150\" />\n          </AuiIf>\n        </TooltipIconButton>\n      </ActionBarPrimitive.Copy>\n      <ActionBarPrimitive.Reload asChild>\n        <TooltipIconButton tooltip=\"Refresh\">\n          <RefreshCwIcon />\n        </TooltipIconButton>\n      </ActionBarPrimitive.Reload>\n      <ActionBarMorePrimitive.Root>\n        <ActionBarMorePrimitive.Trigger asChild>\n          <TooltipIconButton\n            tooltip=\"More\"\n            className=\"data-[state=open]:bg-accent\"\n          >\n            <MoreHorizontalIcon />\n          </TooltipIconButton>\n        </ActionBarMorePrimitive.Trigger>\n        <ActionBarMorePrimitive.Content\n          side=\"bottom\"\n          align=\"start\"\n          sideOffset={6}\n          className=\"aui-action-bar-more-content bg-popover text-popover-foreground data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:animate-out data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-xl border p-1.5\"\n        >\n          <ActionBarPrimitive.ExportMarkdown asChild>\n            <ActionBarMorePrimitive.Item className=\"aui-action-bar-more-item hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground flex cursor-pointer items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm outline-none select-none\">\n              <DownloadIcon className=\"size-4\" />\n              Export as Markdown\n            </ActionBarMorePrimitive.Item>\n          </ActionBarPrimitive.ExportMarkdown>\n        </ActionBarMorePrimitive.Content>\n      </ActionBarMorePrimitive.Root>\n    </ActionBarPrimitive.Root>\n  );\n};\n\nconst UserFilePart: FileMessagePartComponent = (part) => (\n  <div data-slot=\"aui_user-message-file\" className=\"py-1\">\n    <File {...part} />\n  </div>\n);\n\nconst UserImagePart: ImageMessagePartComponent = (part) => (\n  <div data-slot=\"aui_user-message-image\" className=\"py-1\">\n    <Image {...part} />\n  </div>\n);\n\nconst UserMessage: FC = () => {\n  return (\n    <MessagePrimitive.Root\n      data-slot=\"aui_user-message-root\"\n      className=\"fade-in slide-in-from-bottom-1 animate-in grid auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] content-start gap-y-2 px-2 duration-150 [contain-intrinsic-size:auto_200px] [content-visibility:auto] [&:where(>*)]:col-start-2\"\n      data-role=\"user\"\n    >\n      <UserMessageAttachments />\n\n      <div className=\"aui-user-message-content-wrapper relative col-start-2 min-w-0\">\n        <div className=\"aui-user-message-content peer bg-muted text-foreground rounded-xl px-4 py-2 wrap-break-word empty:hidden\">\n          <MessagePrimitive.Parts\n            components={{ File: UserFilePart, Image: UserImagePart }}\n          />\n        </div>\n        <div className=\"aui-user-action-bar-wrapper absolute start-0 top-1/2 -translate-x-full -translate-y-1/2 pe-2 peer-empty:hidden rtl:translate-x-full\">\n          <UserActionBar />\n        </div>\n      </div>\n\n      <BranchPicker\n        data-slot=\"aui_user-branch-picker\"\n        className=\"col-span-full col-start-1 row-start-3 -me-1 justify-end\"\n      />\n    </MessagePrimitive.Root>\n  );\n};\n\nconst UserActionBar: FC = () => {\n  return (\n    <ActionBarPrimitive.Root\n      hideWhenRunning\n      autohide=\"not-last\"\n      className=\"aui-user-action-bar-root flex flex-col items-end\"\n    >\n      <ActionBarPrimitive.Edit asChild>\n        <TooltipIconButton tooltip=\"Edit\" className=\"aui-user-action-edit\">\n          <PencilIcon />\n        </TooltipIconButton>\n      </ActionBarPrimitive.Edit>\n    </ActionBarPrimitive.Root>\n  );\n};\n\nconst EditComposer: FC = () => {\n  return (\n    <MessagePrimitive.Root\n      data-slot=\"aui_edit-composer-wrapper\"\n      className=\"flex flex-col px-2 [contain-intrinsic-size:auto_200px] [content-visibility:auto]\"\n    >\n      <ComposerPrimitive.Root className=\"aui-edit-composer-root border-border/60 dark:border-muted-foreground/15 ms-auto flex w-full max-w-[85%] cursor-text flex-col rounded-(--composer-radius) border bg-(--composer-bg)\">\n        <ComposerPrimitive.Input\n          className=\"aui-edit-composer-input text-foreground min-h-14 w-full resize-none bg-transparent px-4 pt-3 pb-1 text-base outline-none\"\n          autoFocus\n        />\n        <div className=\"aui-edit-composer-footer mx-2.5 mb-2.5 flex items-center gap-1.5 self-end\">\n          <ComposerPrimitive.Cancel asChild>\n            <Button\n              variant=\"ghost\"\n              size=\"sm\"\n              className=\"h-8 rounded-full px-3.5\"\n            >\n              Cancel\n            </Button>\n          </ComposerPrimitive.Cancel>\n          <ComposerPrimitive.Send asChild>\n            <Button size=\"sm\" className=\"h-8 rounded-full px-3.5\">\n              Update\n            </Button>\n          </ComposerPrimitive.Send>\n        </div>\n      </ComposerPrimitive.Root>\n    </MessagePrimitive.Root>\n  );\n};\n\nconst BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({\n  className,\n  ...rest\n}) => {\n  return (\n    <BranchPickerPrimitive.Root\n      hideWhenSingleBranch\n      className={cn(\n        \"aui-branch-picker-root text-muted-foreground -ms-2 me-2 inline-flex items-center text-xs\",\n        className,\n      )}\n      {...rest}\n    >\n      <BranchPickerPrimitive.Previous asChild>\n        <TooltipIconButton tooltip=\"Previous\">\n          <ChevronLeftIcon />\n        </TooltipIconButton>\n      </BranchPickerPrimitive.Previous>\n      <span className=\"aui-branch-picker-state font-medium\">\n        <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />\n      </span>\n      <BranchPickerPrimitive.Next asChild>\n        <TooltipIconButton tooltip=\"Next\">\n          <ChevronRightIcon />\n        </TooltipIconButton>\n      </BranchPickerPrimitive.Next>\n    </BranchPickerPrimitive.Root>\n  );\n};\n"
    },
    {
      "type": "registry:file",
      "path": "components/assistant-ui/elements/attachment.aui.tsx",
      "target": "components/assistant-ui/elements/attachment.aui.tsx",
      "sourcePath": "packages/ui/src/components/react/assistant-ui/elements/attachment.aui.radix.tsx",
      "content": "\"use client\";\n\nimport {\n  type PropsWithChildren,\n  useState,\n  type FC,\n  isValidElement,\n} from \"react\";\nimport {\n  XIcon,\n  PlusIcon,\n  FileText,\n  Loader2Icon,\n  AlertCircleIcon,\n} from \"lucide-react\";\nimport {\n  AttachmentPrimitive,\n  ComposerPrimitive,\n  MessagePrimitive,\n  useAuiState,\n  useAui,\n} from \"@assistant-ui/react\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport {\n  Dialog,\n  DialogTitle,\n  DialogContent,\n  DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n} from \"@/components/ui/avatar\";\nimport { TooltipIconButton } from \"@/components/assistant-ui/elements/tooltip-icon-button\";\nimport { useAttachmentSrc } from \"@/hooks/use-attachment-src\";\nimport { cn } from \"@/lib/utils\";\n\ntype AttachmentPreviewProps = {\n  src: string;\n};\n\nconst AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {\n  const [isLoaded, setIsLoaded] = useState(false);\n  return (\n    <img\n      src={src}\n      alt=\"Attachment preview\"\n      className={cn(\n        \"block h-auto max-h-[80vh] w-auto max-w-full rounded-sm object-contain transition-opacity duration-300 motion-reduce:transition-none\",\n        isLoaded\n          ? \"aui-attachment-preview-image-loaded opacity-100\"\n          : \"aui-attachment-preview-image-loading opacity-0\",\n      )}\n      onLoad={() => setIsLoaded(true)}\n    />\n  );\n};\n\nconst AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {\n  const src = useAttachmentSrc();\n\n  if (!src) return children;\n\n  return (\n    <Dialog>\n      <DialogTrigger\n        className=\"aui-attachment-preview-trigger cursor-zoom-in\"\n        asChild\n      >\n        {isValidElement(children) ? (\n          children\n        ) : (\n          <button type=\"button\">{children}</button>\n        )}\n      </DialogTrigger>\n      <DialogContent className=\"aui-attachment-preview-dialog-content [&>button]:bg-foreground/60 [&>button]:hover:bg-foreground/80 [&_svg]:text-background p-2 sm:max-w-3xl [&>button]:rounded-full [&>button]:p-1 [&>button]:opacity-100 [&>button]:ring-0!\">\n        <DialogTitle className=\"aui-sr-only sr-only\">\n          Image Attachment Preview\n        </DialogTitle>\n        <div className=\"aui-attachment-preview bg-background relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden rounded-sm\">\n          <AttachmentPreview src={src} />\n        </div>\n      </DialogContent>\n    </Dialog>\n  );\n};\n\nconst AttachmentThumb: FC = () => {\n  const src = useAttachmentSrc();\n\n  return (\n    <Avatar className=\"aui-attachment-tile-avatar h-full w-full rounded-none\">\n      <AvatarImage\n        src={src}\n        alt=\"Attachment preview\"\n        className=\"aui-attachment-tile-image rounded-none object-cover\"\n      />\n      <AvatarFallback>\n        <FileText className=\"aui-attachment-tile-fallback-icon text-muted-foreground/80 size-6 stroke-[1.5]\" />\n      </AvatarFallback>\n    </Avatar>\n  );\n};\n\nconst AttachmentUI: FC = () => {\n  const aui = useAui();\n  const isComposer = aui.attachment.source !== \"message\";\n\n  const isImage = useAuiState((s) => s.attachment.type === \"image\");\n  const typeLabel = useAuiState((s) => {\n    const type = s.attachment.type;\n    switch (type) {\n      case \"image\":\n        return \"Image\";\n      case \"document\":\n        return \"Document\";\n      case \"file\":\n        return \"File\";\n      default:\n        return type;\n    }\n  });\n\n  const uploadState = useAuiState((s) =>\n    s.attachment.status.type === \"running\"\n      ? \"uploading\"\n      : s.attachment.status.type === \"incomplete\" &&\n          s.attachment.status.reason === \"error\"\n        ? \"error\"\n        : undefined,\n  );\n  const isUploading = uploadState === \"uploading\";\n  const isError = uploadState === \"error\";\n\n  const errorMessage = useAuiState((s) =>\n    s.attachment.status.type === \"incomplete\" &&\n    s.attachment.status.reason === \"error\"\n      ? (s.attachment.status.message ?? \"Upload failed\")\n      : undefined,\n  );\n\n  return (\n    <TooltipProvider>\n      <Tooltip>\n        <AttachmentPrimitive.Root\n          className={cn(\n            \"aui-attachment-root relative\",\n            isComposer &&\n              \"animate-in fade-in-0 zoom-in-95 duration-200 motion-reduce:animate-none\",\n            isImage &&\n              !isComposer &&\n              \"aui-attachment-root-message only:*:first:size-24\",\n          )}\n        >\n          <AttachmentPreviewDialog>\n            <TooltipTrigger asChild>\n              <div\n                className={cn(\n                  \"aui-attachment-tile bg-muted hover:after:bg-foreground/10 focus-visible:ring-ring/50 relative size-14 cursor-pointer overflow-hidden rounded-[calc(var(--composer-radius,1.5rem)-var(--composer-padding,8px))] transition-transform outline-none after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:ring-1 after:ring-black/10 after:transition-colors after:ring-inset focus-visible:ring-1 active:scale-[0.96] motion-reduce:transition-none dark:after:ring-white/10\",\n                  isError &&\n                    \"after:ring-destructive/60 dark:after:ring-destructive/60\",\n                )}\n                role=\"button\"\n                tabIndex={0}\n                onKeyDown={(e) => {\n                  if (e.key === \"Enter\") {\n                    e.preventDefault();\n                    e.currentTarget.click();\n                  } else if (e.key === \" \") {\n                    e.preventDefault();\n                  }\n                }}\n                onKeyUp={(e) => {\n                  if (e.key === \" \") e.currentTarget.click();\n                }}\n                aria-label={`${typeLabel} attachment${\n                  isError ? \", upload failed\" : isUploading ? \", uploading\" : \"\"\n                }`}\n              >\n                <AttachmentThumb />\n                {isUploading && (\n                  <div\n                    aria-hidden=\"true\"\n                    className=\"aui-attachment-tile-uploading bg-background/60 animate-in fade-in-0 absolute inset-0 flex items-center justify-center backdrop-blur-[2px] motion-reduce:animate-none\"\n                  >\n                    <Loader2Icon className=\"text-muted-foreground size-4 animate-spin\" />\n                  </div>\n                )}\n                {isError && (\n                  <div\n                    aria-hidden=\"true\"\n                    className=\"aui-attachment-tile-error bg-background/70 animate-in fade-in-0 absolute inset-0 flex items-center justify-center backdrop-blur-[2px] motion-reduce:animate-none\"\n                  >\n                    <AlertCircleIcon className=\"text-destructive size-4\" />\n                  </div>\n                )}\n              </div>\n            </TooltipTrigger>\n          </AttachmentPreviewDialog>\n          {isComposer && <AttachmentRemove />}\n        </AttachmentPrimitive.Root>\n        <TooltipContent side=\"top\">\n          <AttachmentPrimitive.Name />\n          {errorMessage && (\n            <p className=\"aui-attachment-error-message\">{errorMessage}</p>\n          )}\n        </TooltipContent>\n      </Tooltip>\n    </TooltipProvider>\n  );\n};\n\nconst AttachmentRemove: FC = () => {\n  return (\n    <AttachmentPrimitive.Remove asChild>\n      <TooltipIconButton\n        tooltip=\"Remove file\"\n        className=\"aui-attachment-tile-remove absolute end-1 top-1 size-5 rounded-full bg-black/50! text-white after:absolute after:-inset-1.5 hover:bg-black/70! hover:text-white! active:scale-[0.96] motion-reduce:transition-none\"\n        side=\"top\"\n      >\n        <XIcon className=\"aui-attachment-remove-icon size-3 stroke-[2.5]\" />\n      </TooltipIconButton>\n    </AttachmentPrimitive.Remove>\n  );\n};\n\nexport const UserMessageAttachments: FC = () => {\n  return (\n    <div className=\"aui-user-message-attachments-end col-span-full col-start-1 row-start-1 flex w-full flex-row justify-end gap-2\">\n      <MessagePrimitive.Attachments>\n        {() => <AttachmentUI />}\n      </MessagePrimitive.Attachments>\n    </div>\n  );\n};\n\nexport const ComposerAttachments: FC = () => {\n  return (\n    <div className=\"aui-composer-attachments flex w-full flex-row items-center gap-2 overflow-x-auto empty:hidden\">\n      <ComposerPrimitive.Attachments>\n        {() => <AttachmentUI />}\n      </ComposerPrimitive.Attachments>\n    </div>\n  );\n};\n\nexport const ComposerAddAttachment: FC = () => {\n  return (\n    <ComposerPrimitive.AddAttachment asChild>\n      <TooltipIconButton\n        tooltip=\"Add Attachment\"\n        side=\"bottom\"\n        variant=\"ghost\"\n        size=\"icon\"\n        className=\"aui-composer-add-attachment text-muted-foreground hover:text-foreground hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30 size-7 rounded-full active:scale-[0.96] motion-reduce:transition-none\"\n        aria-label=\"Add Attachment\"\n      >\n        <PlusIcon className=\"aui-attachment-add-icon size-4\" />\n      </TooltipIconButton>\n    </ComposerPrimitive.AddAttachment>\n  );\n};\n"
    },
    {
      "type": "registry:file",
      "path": "components/assistant-ui/elements/tooltip-icon-button.tsx",
      "target": "components/assistant-ui/elements/tooltip-icon-button.tsx",
      "sourcePath": "packages/ui/src/components/react/assistant-ui/elements/tooltip-icon-button.radix.tsx",
      "content": "\"use client\";\n\nimport { type ComponentPropsWithRef, forwardRef } from \"react\";\nimport { Slot } from \"radix-ui\";\n\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nexport type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {\n  tooltip: string;\n  side?: \"top\" | \"bottom\" | \"left\" | \"right\";\n};\n\nexport const TooltipIconButton = forwardRef<\n  HTMLButtonElement,\n  TooltipIconButtonProps\n>(({ children, tooltip, side = \"bottom\", className, ...rest }, ref) => {\n  return (\n    <TooltipProvider delayDuration={0}>\n      <Tooltip>\n        <TooltipTrigger asChild>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            {...rest}\n            className={cn(\n              \"aui-button-icon size-6 p-1 active:scale-90\",\n              className,\n            )}\n            ref={ref}\n          >\n            <Slot.Slottable>{children}</Slot.Slottable>\n            <span className=\"aui-sr-only sr-only\">{tooltip}</span>\n          </Button>\n        </TooltipTrigger>\n        <TooltipContent side={side}>{tooltip}</TooltipContent>\n      </Tooltip>\n    </TooltipProvider>\n  );\n});\n\nTooltipIconButton.displayName = \"TooltipIconButton\";\n"
    },
    {
      "type": "registry:file",
      "path": "hooks/use-attachment-src.ts",
      "target": "hooks/use-attachment-src.ts",
      "sourcePath": "packages/ui/src/hooks/use-attachment-src.ts",
      "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useAuiState } from \"@assistant-ui/react\";\nimport { useShallow } from \"zustand/react/shallow\";\n\nconst useFileSrc = (file: File | undefined) => {\n  const [entry, setEntry] = useState<{ file: File; url: string } | undefined>(\n    undefined,\n  );\n\n  useEffect(() => {\n    if (!file) {\n      setEntry(undefined);\n      return;\n    }\n\n    const objectUrl = URL.createObjectURL(file);\n    setEntry({ file, url: objectUrl });\n\n    return () => {\n      URL.revokeObjectURL(objectUrl);\n    };\n  }, [file]);\n\n  return entry !== undefined && entry.file === file ? entry.url : undefined;\n};\n\nexport const useAttachmentSrc = () => {\n  const { file, src } = useAuiState(\n    useShallow((s): { file?: File; src?: string } => {\n      if (s.attachment.type !== \"image\") return {};\n      if (s.attachment.file) return { file: s.attachment.file };\n      const src = s.attachment.content?.filter((c) => c.type === \"image\")[0]\n        ?.image;\n      if (!src) return {};\n      return { src };\n    }),\n  );\n\n  return useFileSrc(file) ?? src;\n};\n"
    },
    {
      "type": "registry:file",
      "path": "components/assistant-ui/elements/file.tsx",
      "target": "components/assistant-ui/elements/file.tsx",
      "sourcePath": "packages/ui/src/components/react/assistant-ui/elements/file.tsx",
      "content": "\"use client\";\n\nimport { memo, type FC } from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n  FileIcon,\n  FileTextIcon,\n  ImageIcon,\n  MusicIcon,\n  VideoIcon,\n  BracesIcon,\n  DownloadIcon,\n} from \"lucide-react\";\nimport type { FileMessagePartComponent } from \"@assistant-ui/react\";\nimport { cn } from \"@/lib/utils\";\n\nconst fileVariants = cva(\n  \"aui-file-root inline-flex items-center gap-3 rounded-lg transition-colors\",\n  {\n    variants: {\n      variant: {\n        outline: \"border-border hover:bg-muted/50 border\",\n        ghost: \"hover:bg-muted/50\",\n        muted: \"bg-muted/50 hover:bg-muted/70\",\n      },\n      size: {\n        sm: \"px-2.5 py-1.5 text-xs\",\n        default: \"px-3 py-2 text-sm\",\n        lg: \"px-4 py-3 text-base\",\n      },\n    },\n    defaultVariants: {\n      variant: \"outline\",\n      size: \"default\",\n    },\n  },\n);\n\nfunction getMimeTypeIcon(mimeType: string): FC<{ className?: string }> {\n  const type = mimeType.toLowerCase();\n  if (type.startsWith(\"image/\")) {\n    return ImageIcon;\n  }\n  if (type === \"application/pdf\") {\n    return FileTextIcon;\n  }\n  if (type === \"application/json\") {\n    return BracesIcon;\n  }\n  if (type.startsWith(\"text/\")) {\n    return FileTextIcon;\n  }\n  if (type.startsWith(\"audio/\")) {\n    return MusicIcon;\n  }\n  if (type.startsWith(\"video/\")) {\n    return VideoIcon;\n  }\n  return FileIcon;\n}\n\nexport type FileDataKind = \"data-uri\" | \"url\" | \"base64\" | \"id\";\n\nfunction getFileDataKind(\n  data: string,\n  sourceType?: \"url\" | \"id\",\n): FileDataKind {\n  if (sourceType === \"url\" && /^data:/i.test(data)) return \"data-uri\";\n  if (sourceType) return sourceType;\n  if (/^data:/i.test(data)) return \"data-uri\";\n  if (/^https?:\\/\\//i.test(data)) return \"url\";\n  return \"base64\";\n}\n\nfunction getBase64Size(base64: string): number {\n  const commaIndex = base64.indexOf(\",\");\n  const base64Data = commaIndex >= 0 ? base64.slice(commaIndex + 1) : base64;\n  const padding = (base64Data.match(/=/g) || []).length;\n  return Math.floor((base64Data.length * 3) / 4) - padding;\n}\n\nfunction formatFileSize(bytes: number): string {\n  if (bytes < 1024) {\n    return `${bytes} B`;\n  }\n  if (bytes < 1024 * 1024) {\n    return `${(bytes / 1024).toFixed(1)} KB`;\n  }\n  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nexport type FileRootProps = React.ComponentProps<\"div\"> &\n  VariantProps<typeof fileVariants>;\n\nfunction FileRoot({\n  className,\n  variant,\n  size,\n  children,\n  ...props\n}: FileRootProps) {\n  return (\n    <div\n      data-slot=\"file-root\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(fileVariants({ variant, size, className }))}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n}\n\ntype FileIconDisplayProps = React.ComponentProps<\"span\"> & {\n  mimeType?: string;\n};\n\nfunction FileIconDisplay({\n  mimeType,\n  className,\n  children,\n  ...props\n}: FileIconDisplayProps) {\n  const IconComponent = mimeType ? getMimeTypeIcon(mimeType) : FileIcon;\n\n  return (\n    <span\n      data-slot=\"file-icon\"\n      className={cn(\"text-muted-foreground shrink-0\", className)}\n      {...props}\n    >\n      {children ?? <IconComponent className=\"size-5\" />}\n    </span>\n  );\n}\n\nfunction FileName({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"file-name\"\n      className={cn(\"min-w-0 flex-1 truncate font-medium\", className)}\n      {...props}\n    >\n      {children || \"Unnamed file\"}\n    </span>\n  );\n}\n\ntype FileSizeProps = React.ComponentProps<\"span\"> & {\n  bytes: number;\n};\n\nfunction FileSize({ bytes, className, ...props }: FileSizeProps) {\n  return (\n    <span\n      data-slot=\"file-size\"\n      className={cn(\"text-muted-foreground shrink-0\", className)}\n      {...props}\n    >\n      {formatFileSize(bytes)}\n    </span>\n  );\n}\n\ntype FileDownloadProps = Omit<React.ComponentProps<\"a\">, \"href\"> & {\n  data: string;\n  mimeType: string;\n  filename?: string;\n  sourceType?: \"url\" | \"id\";\n};\n\nfunction FileDownload({\n  data,\n  mimeType,\n  filename,\n  sourceType,\n  className,\n  children,\n  ...props\n}: FileDownloadProps) {\n  if (typeof data !== \"string\") return null;\n  const kind = getFileDataKind(data, sourceType);\n  if (kind === \"id\") return null;\n  if (kind === \"url\" && !/^(https?:\\/\\/|blob:)/i.test(data)) return null;\n  const href = kind === \"base64\" ? `data:${mimeType};base64,${data}` : data;\n\n  return (\n    <a\n      data-slot=\"file-download\"\n      href={href}\n      download={filename || \"download\"}\n      {...(kind === \"url\" && { target: \"_blank\", rel: \"noopener noreferrer\" })}\n      className={cn(\n        \"text-muted-foreground hover:bg-accent hover:text-accent-foreground shrink-0 rounded-md p-1 transition-colors\",\n        className,\n      )}\n      {...props}\n    >\n      {children || <DownloadIcon className=\"size-4\" />}\n    </a>\n  );\n}\n\nconst FileImpl: FileMessagePartComponent = ({\n  filename,\n  data,\n  mimeType,\n  sourceType,\n}) => {\n  const kind = getFileDataKind(data, sourceType);\n  const showSize =\n    typeof data === \"string\" && (kind === \"base64\" || kind === \"data-uri\");\n\n  return (\n    <FileRoot>\n      <FileIconDisplay mimeType={mimeType} />\n      <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n        <FileName>{filename}</FileName>\n        {showSize && (\n          <FileSize bytes={getBase64Size(data)} className=\"text-xs\" />\n        )}\n      </div>\n      <FileDownload\n        data={data}\n        mimeType={mimeType}\n        {...(filename !== undefined && { filename })}\n        {...(sourceType !== undefined && { sourceType })}\n      />\n    </FileRoot>\n  );\n};\n\nconst File = memo(FileImpl) as unknown as FileMessagePartComponent & {\n  Root: typeof FileRoot;\n  Icon: typeof FileIconDisplay;\n  Name: typeof FileName;\n  Size: typeof FileSize;\n  Download: typeof FileDownload;\n};\n\nFile.displayName = \"File\";\nFile.Root = FileRoot;\nFile.Icon = FileIconDisplay;\nFile.Name = FileName;\nFile.Size = FileSize;\nFile.Download = FileDownload;\n\nexport {\n  File,\n  FileRoot,\n  FileIconDisplay,\n  FileName,\n  FileSize,\n  FileDownload,\n  fileVariants,\n  getMimeTypeIcon,\n  getFileDataKind,\n  getBase64Size,\n  formatFileSize,\n};\n"
    },
    {
      "type": "registry:file",
      "path": "components/assistant-ui/elements/follow-up-suggestions.aui.tsx",
      "target": "components/assistant-ui/elements/follow-up-suggestions.aui.tsx",
      "sourcePath": "packages/ui/src/components/react/assistant-ui/elements/follow-up-suggestions.aui.tsx",
      "content": "\"use client\";\n\nimport { AuiIf, useAuiState, ThreadPrimitive } from \"@assistant-ui/react\";\nimport { useCallback, useEffect, useRef, useState, type FC } from \"react\";\n\nconst FollowupSuggestionsRow: FC = () => {\n  const suggestions = useAuiState((s) => s.thread.suggestions);\n  const scrollRef = useRef<HTMLDivElement>(null);\n  const rtlRef = useRef<boolean | null>(null);\n  const [fades, setFades] = useState({ left: false, right: false });\n\n  const updateFades = useCallback(() => {\n    const el = scrollRef.current;\n    if (!el) return;\n    const maxScroll = el.scrollWidth - el.clientWidth;\n    // scrollLeft runs 0..-max in RTL; normalize to hidden width per physical edge.\n    const fromStart = Math.abs(el.scrollLeft);\n    // getComputedStyle forces a style recalc per scroll event; direction is stable, read it once.\n    const rtl = (rtlRef.current ??= getComputedStyle(el).direction === \"rtl\");\n    const [left, right] = rtl\n      ? [maxScroll - fromStart, fromStart]\n      : [fromStart, maxScroll - fromStart];\n    setFades((prev) => {\n      const next = { left: left > 1, right: right > 1 };\n      return prev.left === next.left && prev.right === next.right ? prev : next;\n    });\n  }, []);\n\n  useEffect(() => {\n    updateFades();\n    const el = scrollRef.current;\n    if (!el?.firstElementChild) return undefined;\n    const observer = new ResizeObserver(updateFades);\n    observer.observe(el);\n    observer.observe(el.firstElementChild);\n    return () => observer.disconnect();\n  }, [updateFades]);\n\n  const maskImage = `linear-gradient(to right, ${\n    fades.left ? \"transparent, black 2rem\" : \"black\"\n  }, ${fades.right ? \"black calc(100% - 2rem), transparent\" : \"black\"})`;\n\n  return (\n    <div\n      ref={scrollRef}\n      onScroll={updateFades}\n      // overflow-x clips both axes; py-1/-my-1 gives focus rings vertical room without changing outer height.\n      className=\"aui-thread-followup-suggestions -my-1 w-full overflow-x-auto py-1 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n      style={{ maskImage, WebkitMaskImage: maskImage }}\n    >\n      <div className=\"mx-auto flex min-h-8 w-max items-center gap-2 px-0.5\">\n        {suggestions.map((suggestion, idx) => (\n          <ThreadPrimitive.Suggestion\n            key={idx}\n            className=\"aui-thread-followup-suggestion bg-background hover:bg-muted/80 rounded-full border px-3 py-1 text-sm whitespace-nowrap transition-colors ease-in\"\n            prompt={suggestion.prompt}\n            send\n          >\n            {suggestion.title ?? suggestion.prompt}\n            {suggestion.label && (\n              <span className=\"aui-thread-followup-suggestion-label text-muted-foreground ms-1\">\n                {suggestion.label}\n              </span>\n            )}\n          </ThreadPrimitive.Suggestion>\n        ))}\n      </div>\n    </div>\n  );\n};\n\nexport const ThreadFollowupSuggestions: FC = () => (\n  <AuiIf\n    condition={(s) =>\n      !s.thread.isEmpty &&\n      !s.thread.isRunning &&\n      s.thread.suggestions.length > 0\n    }\n  >\n    <FollowupSuggestionsRow />\n  </AuiIf>\n);\n"
    },
    {
      "type": "registry:file",
      "path": "components/assistant-ui/elements/image.tsx",
      "target": "components/assistant-ui/elements/image.tsx",
      "sourcePath": "packages/ui/src/components/react/assistant-ui/elements/image.tsx",
      "content": "\"use client\";\n\nimport {\n  memo,\n  useState,\n  useEffect,\n  useCallback,\n  useRef,\n  type PropsWithChildren,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n  CopyIcon,\n  DownloadIcon,\n  ImageIcon,\n  ImageOffIcon,\n  Loader2Icon,\n  RefreshCwIcon,\n  ShieldAlertIcon,\n  XIcon,\n} from \"lucide-react\";\nimport type {\n  ImageMessagePart,\n  ImageMessagePartComponent,\n} from \"@assistant-ui/react\";\nimport { cn } from \"@/lib/utils\";\n\nconst extensionForMimeType = (mimeType?: string): string => {\n  switch (mimeType) {\n    case \"image/png\":\n      return \"png\";\n    case \"image/jpeg\":\n    case \"image/jpg\":\n      return \"jpg\";\n    case \"image/webp\":\n      return \"webp\";\n    case \"image/gif\":\n      return \"gif\";\n    case \"image/svg+xml\":\n      return \"svg\";\n    default:\n      return \"png\";\n  }\n};\n\nconst dataUriToBlob = (dataUri: string): Blob => {\n  const commaIndex = dataUri.indexOf(\",\");\n  const meta = commaIndex >= 0 ? dataUri.slice(0, commaIndex) : dataUri;\n  const data = commaIndex >= 0 ? dataUri.slice(commaIndex + 1) : \"\";\n  const mime =\n    meta.match(/data:([^;]+)/i)?.[1]?.toLowerCase() ??\n    \"application/octet-stream\";\n  if (!/;base64/i.test(meta)) {\n    const text = data.replace(/(?:%[0-9A-Fa-f]{2})+/g, (seq) => {\n      try {\n        return decodeURIComponent(seq);\n      } catch {\n        return seq;\n      }\n    });\n    return new Blob([text], { type: mime });\n  }\n  const bytes = atob(data);\n  const arr = new Uint8Array(bytes.length);\n  for (let i = 0; i < bytes.length; i++) arr[i] = bytes.charCodeAt(i);\n  return new Blob([arr], { type: mime });\n};\n\nconst mimeFromImage = (image: string): string | undefined =>\n  image.match(/^data:([^;,]+)/i)?.[1]?.toLowerCase();\n\nconst downloadImagePart = (\n  part: Pick<ImageMessagePart, \"image\" | \"filename\">,\n): void => {\n  if (typeof document === \"undefined\") return;\n  const ext = extensionForMimeType(mimeFromImage(part.image));\n  const filename = part.filename ?? `image.${ext}`;\n  const isDataUri = /^data:/i.test(part.image);\n  const objectUrl = isDataUri\n    ? URL.createObjectURL(dataUriToBlob(part.image))\n    : null;\n  const href = objectUrl ?? part.image;\n  const a = document.createElement(\"a\");\n  a.href = href;\n  a.download = filename;\n  a.rel = \"noopener\";\n  document.body.appendChild(a);\n  a.click();\n  document.body.removeChild(a);\n  if (objectUrl) setTimeout(() => URL.revokeObjectURL(objectUrl), 40_000);\n};\n\nconst copyImagePart = async (\n  part: Pick<ImageMessagePart, \"image\">,\n): Promise<void> => {\n  if (\n    typeof navigator === \"undefined\" ||\n    !navigator.clipboard ||\n    typeof ClipboardItem === \"undefined\"\n  ) {\n    throw new Error(\"Clipboard API is not available in this environment.\");\n  }\n  const blob = /^data:/i.test(part.image)\n    ? dataUriToBlob(part.image)\n    : await fetch(part.image).then((r) => r.blob());\n  const mime = mimeFromImage(part.image) ?? blob.type ?? \"image/png\";\n  await navigator.clipboard.write([new ClipboardItem({ [mime]: blob })]);\n};\n\nconst imageVariants = cva(\n  \"aui-image-root relative overflow-hidden rounded-lg\",\n  {\n    variants: {\n      variant: {\n        outline: \"border-border border\",\n        ghost: \"\",\n        muted: \"bg-muted/50\",\n      },\n      size: {\n        sm: \"max-w-64\",\n        default: \"max-w-96\",\n        lg: \"max-w-[512px]\",\n        full: \"w-full\",\n      },\n    },\n    defaultVariants: {\n      variant: \"outline\",\n      size: \"default\",\n    },\n  },\n);\n\nexport type ImageRootProps = React.ComponentProps<\"div\"> &\n  VariantProps<typeof imageVariants>;\n\nfunction ImageRoot({\n  className,\n  variant,\n  size,\n  children,\n  ...props\n}: ImageRootProps) {\n  return (\n    <div\n      data-slot=\"image-root\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(imageVariants({ variant, size, className }))}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n}\n\ntype ImagePreviewProps = Omit<React.ComponentProps<\"img\">, \"children\"> & {\n  containerClassName?: string;\n};\n\nfunction ImagePreview({\n  className,\n  containerClassName,\n  onLoad,\n  onError,\n  alt = \"Image content\",\n  src,\n  ...props\n}: ImagePreviewProps) {\n  const imgRef = useRef<HTMLImageElement>(null);\n  const [loadedSrc, setLoadedSrc] = useState<string | undefined>(undefined);\n  const [errorSrc, setErrorSrc] = useState<string | undefined>(undefined);\n\n  const loaded = loadedSrc === src;\n  const error = errorSrc === src;\n\n  useEffect(() => {\n    const image = imgRef.current;\n    if (typeof src !== \"string\" || !image?.complete) return;\n    if (image.naturalWidth > 0) setLoadedSrc(src);\n    else setErrorSrc(src);\n  }, [src]);\n\n  return (\n    <div\n      data-slot=\"image-preview\"\n      className={cn(\"relative min-h-32\", containerClassName)}\n    >\n      {!loaded && !error && (\n        <div\n          data-slot=\"image-preview-loading\"\n          className=\"bg-muted/50 absolute inset-0 flex items-center justify-center\"\n        >\n          <ImageIcon className=\"text-muted-foreground size-8 animate-pulse\" />\n        </div>\n      )}\n      {error ? (\n        <div\n          data-slot=\"image-preview-error\"\n          className=\"bg-muted/50 flex min-h-32 items-center justify-center p-4\"\n        >\n          <ImageOffIcon className=\"text-muted-foreground size-8\" />\n        </div>\n      ) : (\n        <img\n          ref={imgRef}\n          src={src}\n          alt={alt}\n          className={cn(\n            \"block h-auto w-full object-contain\",\n            !loaded && \"invisible\",\n            className,\n          )}\n          onLoad={(e) => {\n            if (typeof src === \"string\") setLoadedSrc(src);\n            onLoad?.(e);\n          }}\n          onError={(e) => {\n            if (typeof src === \"string\") setErrorSrc(src);\n            onError?.(e);\n          }}\n          {...props}\n        />\n      )}\n    </div>\n  );\n}\n\nfunction ImageFilename({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  if (!children) return null;\n\n  return (\n    <span\n      data-slot=\"image-filename\"\n      className={cn(\n        \"text-muted-foreground block truncate px-2 py-1.5 text-xs\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </span>\n  );\n}\n\ntype ImageZoomProps = PropsWithChildren<{\n  src: string;\n  alt?: string;\n}>;\n\nfunction ImageZoom({ src, alt = \"Image preview\", children }: ImageZoomProps) {\n  const [isMounted, setIsMounted] = useState(false);\n  const [isOpen, setIsOpen] = useState(false);\n  const triggerRef = useRef<HTMLDivElement>(null);\n  const closeRef = useRef<HTMLButtonElement>(null);\n  const overlayRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    setIsMounted(true);\n  }, []);\n\n  const handleOpen = useCallback(() => setIsOpen(true), []);\n  const handleClose = useCallback(() => {\n    setIsOpen(false);\n    triggerRef.current?.focus();\n  }, []);\n\n  useEffect(() => {\n    if (!isOpen) return;\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") {\n        handleClose();\n        return;\n      }\n      if (e.key !== \"Tab\") return;\n      const focusables = overlayRef.current?.querySelectorAll<HTMLElement>(\n        'a[href], button:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n      );\n      const first = focusables?.[0];\n      const last = focusables?.[focusables.length - 1];\n      if (!first || !last) return;\n      if (e.shiftKey && document.activeElement === first) {\n        e.preventDefault();\n        last.focus();\n      } else if (!e.shiftKey && document.activeElement === last) {\n        e.preventDefault();\n        first.focus();\n      }\n    };\n    document.addEventListener(\"keydown\", handleKeyDown);\n    return () => document.removeEventListener(\"keydown\", handleKeyDown);\n  }, [isOpen, handleClose]);\n\n  useEffect(() => {\n    if (!isOpen) return;\n    const originalOverflow = document.body.style.overflow;\n    document.body.style.overflow = \"hidden\";\n    return () => {\n      document.body.style.overflow = originalOverflow;\n    };\n  }, [isOpen]);\n\n  useEffect(() => {\n    if (isOpen) closeRef.current?.focus();\n  }, [isOpen]);\n\n  return (\n    <>\n      <div\n        ref={triggerRef}\n        onClick={handleOpen}\n        onKeyDown={(e) => e.key === \"Enter\" && handleOpen()}\n        role=\"button\"\n        tabIndex={0}\n        className=\"aui-image-zoom-trigger cursor-zoom-in\"\n        aria-label=\"Click to zoom image\"\n      >\n        {children}\n      </div>\n      {isMounted &&\n        isOpen &&\n        createPortal(\n          <div\n            ref={overlayRef}\n            data-slot=\"image-zoom-overlay\"\n            role=\"dialog\"\n            aria-modal=\"true\"\n            className=\"aui-image-zoom-overlay fade-in animate-in fixed inset-0 z-50 flex items-center justify-center bg-black/80 duration-200\"\n            onClick={handleClose}\n            aria-label=\"Zoomed image\"\n          >\n            <img\n              data-slot=\"image-zoom-content\"\n              src={src}\n              alt={alt}\n              className=\"aui-image-zoom-content fade-in zoom-in-95 animate-in max-h-[90vh] max-w-[90vw] cursor-zoom-out object-contain duration-200\"\n              onClick={(e) => {\n                e.stopPropagation();\n                handleClose();\n              }}\n            />\n            <button\n              ref={closeRef}\n              type=\"button\"\n              aria-label=\"Close zoomed image\"\n              onClick={(e) => {\n                e.stopPropagation();\n                handleClose();\n              }}\n              className=\"text-muted-foreground hover:text-foreground bg-background/80 absolute end-4 top-4 cursor-pointer rounded-md p-2\"\n            >\n              <XIcon className=\"size-5\" />\n            </button>\n          </div>,\n          document.body,\n        )}\n    </>\n  );\n}\n\nfunction ImageGenerating({ className }: { className?: string }) {\n  return (\n    <div\n      data-slot=\"image-generating\"\n      className={cn(\n        \"bg-muted/50 flex min-h-32 items-center justify-center p-4\",\n        className,\n      )}\n    >\n      <Loader2Icon className=\"text-muted-foreground size-8 animate-spin\" />\n      <span className=\"sr-only\">Generating image…</span>\n    </div>\n  );\n}\n\nfunction ImageContentFilterError({\n  className,\n  reason,\n}: {\n  className?: string;\n  reason?: string;\n}) {\n  return (\n    <div\n      data-slot=\"image-content-filter-error\"\n      className={cn(\n        \"bg-muted/50 flex min-h-32 flex-col items-center justify-center gap-2 p-4 text-center\",\n        className,\n      )}\n    >\n      <ShieldAlertIcon className=\"text-muted-foreground size-8\" />\n      <p className=\"text-sm font-medium\">Image could not be generated</p>\n      {reason && <p className=\"text-muted-foreground text-xs\">{reason}</p>}\n    </div>\n  );\n}\n\nexport type ImageActionsProps = {\n  part: ImageMessagePart;\n  /**\n   * Wire to your own generation call to show a regenerate button. The button\n   * renders only when this is set and the part carries a `prompt`.\n   */\n  onRegenerate?: () => void | Promise<void>;\n  className?: string;\n};\n\nfunction RegenerateButton({\n  onRegenerate,\n}: {\n  onRegenerate: () => void | Promise<void>;\n}) {\n  const [isRegenerating, setIsRegenerating] = useState(false);\n  return (\n    <button\n      type=\"button\"\n      onClick={async () => {\n        setIsRegenerating(true);\n        try {\n          await onRegenerate();\n        } catch {\n        } finally {\n          setIsRegenerating(false);\n        }\n      }}\n      disabled={isRegenerating}\n      data-slot=\"image-regenerate\"\n      aria-label=\"Regenerate image\"\n      className=\"hover:bg-muted inline-flex size-7 items-center justify-center rounded disabled:opacity-50\"\n    >\n      <RefreshCwIcon\n        className={cn(\"size-4\", isRegenerating && \"animate-spin\")}\n      />\n    </button>\n  );\n}\n\nfunction ImageActions({ part, onRegenerate, className }: ImageActionsProps) {\n  return (\n    <div\n      data-slot=\"image-actions\"\n      className={cn(\"flex items-center gap-1 p-1\", className)}\n    >\n      <button\n        type=\"button\"\n        onClick={() => downloadImagePart(part)}\n        data-slot=\"image-download\"\n        aria-label=\"Download image\"\n        className=\"hover:bg-muted inline-flex size-7 items-center justify-center rounded\"\n      >\n        <DownloadIcon className=\"size-4\" />\n      </button>\n      <button\n        type=\"button\"\n        onClick={() => {\n          copyImagePart(part).catch(() => {});\n        }}\n        data-slot=\"image-copy\"\n        aria-label=\"Copy image\"\n        className=\"hover:bg-muted inline-flex size-7 items-center justify-center rounded\"\n      >\n        <CopyIcon className=\"size-4\" />\n      </button>\n      {onRegenerate && <RegenerateButton onRegenerate={onRegenerate} />}\n    </div>\n  );\n}\n\nconst ImageImpl: ImageMessagePartComponent = (props) => {\n  const { image, filename, status } = props;\n\n  if (status?.type === \"running\") {\n    return (\n      <ImageRoot>\n        <ImageGenerating />\n        <ImageFilename>{filename}</ImageFilename>\n      </ImageRoot>\n    );\n  }\n\n  if (status?.type === \"incomplete\" && status.reason === \"content-filter\") {\n    return (\n      <ImageRoot>\n        <ImageContentFilterError reason=\"The provider blocked this image.\" />\n      </ImageRoot>\n    );\n  }\n\n  return (\n    <ImageRoot>\n      <ImageZoom src={image} alt={filename || \"Image content\"}>\n        <ImagePreview src={image} alt={filename || \"Image content\"} />\n      </ImageZoom>\n      <ImageFilename>{filename}</ImageFilename>\n    </ImageRoot>\n  );\n};\n\nconst Image = memo(ImageImpl) as unknown as ImageMessagePartComponent & {\n  Root: typeof ImageRoot;\n  Preview: typeof ImagePreview;\n  Filename: typeof ImageFilename;\n  Zoom: typeof ImageZoom;\n  Actions: typeof ImageActions;\n  Generating: typeof ImageGenerating;\n  ContentFilterError: typeof ImageContentFilterError;\n};\n\nImage.displayName = \"Image\";\nImage.Root = ImageRoot;\nImage.Preview = ImagePreview;\nImage.Filename = ImageFilename;\nImage.Zoom = ImageZoom;\nImage.Actions = ImageActions;\nImage.Generating = ImageGenerating;\nImage.ContentFilterError = ImageContentFilterError;\n\nexport {\n  Image,\n  ImageRoot,\n  ImagePreview,\n  ImageFilename,\n  ImageZoom,\n  ImageActions,\n  ImageGenerating,\n  ImageContentFilterError,\n  imageVariants,\n};\n"
    },
    {
      "type": "registry:file",
      "path": "components/assistant-ui/elements/markdown-text.tsx",
      "target": "components/assistant-ui/elements/markdown-text.tsx",
      "sourcePath": "packages/ui/src/components/react/assistant-ui/elements/markdown-text.tsx",
      "content": "\"use client\";\n\nimport \"@assistant-ui/react-markdown/styles/dot.css\";\n\nimport {\n  type CodeHeaderProps,\n  MarkdownTextPrimitive,\n  unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,\n  useIsMarkdownCodeBlock,\n} from \"@assistant-ui/react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport { type FC, memo, useMemo, useRef } from \"react\";\nimport type { TextMessagePartProps } from \"@assistant-ui/react\";\nimport { CheckIcon, CopyIcon } from \"lucide-react\";\n\nimport { TooltipIconButton } from \"@/components/assistant-ui/elements/tooltip-icon-button\";\nimport { useCopyToClipboard } from \"@/hooks/use-copy-to-clipboard\";\nimport { cn } from \"@/lib/utils\";\n\ntype MarkdownTextProps = Partial<TextMessagePartProps> & {\n  components?: Parameters<typeof memoizeMarkdownComponents>[0];\n};\n\nconst useShallowStable = <T extends Record<string, unknown> | undefined>(\n  value: T,\n): T => {\n  const ref = useRef(value);\n  if (value !== ref.current) {\n    const prev = ref.current;\n    const stable =\n      value !== undefined &&\n      prev !== undefined &&\n      Object.keys(prev).length === Object.keys(value).length &&\n      Object.keys(value).every((key) => prev[key] === value[key]);\n    if (!stable) ref.current = value;\n  }\n  return ref.current;\n};\n\nconst MarkdownTextImpl: FC<MarkdownTextProps> = ({ components }) => {\n  const stableComponents = useShallowStable(components);\n  const markdownComponents = useMemo(() => {\n    if (!stableComponents) return defaultComponents;\n    return {\n      ...defaultComponents,\n      ...memoizeMarkdownComponents(stableComponents),\n    };\n  }, [stableComponents]);\n\n  return (\n    <MarkdownTextPrimitive\n      remarkPlugins={[remarkGfm]}\n      className=\"aui-md\"\n      components={markdownComponents}\n      defer\n    />\n  );\n};\n\nexport const MarkdownText = memo(MarkdownTextImpl);\n\nconst CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {\n  const { isCopied, copyToClipboard } = useCopyToClipboard();\n  const onCopy = () => {\n    if (!code || isCopied) return;\n    copyToClipboard(code);\n  };\n\n  return (\n    <div className=\"aui-code-header-root border-border/50 bg-muted/50 mt-3 flex items-center justify-between rounded-t-xl border border-b-0 px-3.5 py-1.5 text-xs\">\n      <span className=\"aui-code-header-language text-muted-foreground font-medium lowercase\">\n        {language}\n      </span>\n      <TooltipIconButton tooltip=\"Copy\" onClick={onCopy}>\n        {!isCopied && (\n          <CopyIcon className=\"animate-in zoom-in-75 fade-in duration-150\" />\n        )}\n        {isCopied && (\n          <CheckIcon className=\"animate-in zoom-in-50 fade-in duration-200 ease-out\" />\n        )}\n      </TooltipIconButton>\n    </div>\n  );\n};\n\nconst defaultComponents = memoizeMarkdownComponents({\n  h1: ({ className, ...props }) => (\n    <h1\n      className={cn(\n        \"aui-md-h1 mt-5 mb-2 scroll-m-20 text-xl font-semibold first:mt-0 last:mb-0\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  h2: ({ className, ...props }) => (\n    <h2\n      className={cn(\n        \"aui-md-h2 mt-5 mb-2 scroll-m-20 text-lg font-semibold first:mt-0 last:mb-0\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  h3: ({ className, ...props }) => (\n    <h3\n      className={cn(\n        \"aui-md-h3 mt-4 mb-1.5 scroll-m-20 text-base font-semibold first:mt-0 last:mb-0\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  h4: ({ className, ...props }) => (\n    <h4\n      className={cn(\n        \"aui-md-h4 mt-3.5 mb-1 scroll-m-20 text-base font-medium first:mt-0 last:mb-0\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  h5: ({ className, ...props }) => (\n    <h5\n      className={cn(\n        \"aui-md-h5 mt-3 mb-1 text-sm font-semibold first:mt-0 last:mb-0\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  h6: ({ className, ...props }) => (\n    <h6\n      className={cn(\n        \"aui-md-h6 mt-3 mb-1 text-sm font-medium first:mt-0 last:mb-0\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  p: ({ className, ...props }) => (\n    <p\n      className={cn(\n        \"aui-md-p my-3 leading-relaxed first:mt-0 last:mb-0\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  a: ({ className, ...props }) => (\n    <a\n      className={cn(\n        \"aui-md-a text-primary hover:text-primary/80 underline underline-offset-2\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  blockquote: ({ className, ...props }) => (\n    <blockquote\n      className={cn(\n        \"aui-md-blockquote border-muted-foreground/30 text-muted-foreground my-3 border-s-2 ps-4\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  ul: ({ className, ...props }) => (\n    <ul\n      className={cn(\n        \"aui-md-ul marker:text-muted-foreground my-3 ms-5 list-disc [&>li]:mt-1\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  ol: ({ className, ...props }) => (\n    <ol\n      className={cn(\n        \"aui-md-ol marker:text-muted-foreground my-3 ms-5 list-decimal [&>li]:mt-1\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  hr: ({ className, ...props }) => (\n    <hr\n      className={cn(\"aui-md-hr border-muted-foreground/20 my-3\", className)}\n      {...props}\n    />\n  ),\n  table: ({ className, ...props }) => (\n    <div className=\"aui-md-table-wrapper my-3 overflow-x-auto\">\n      <table\n        className={cn(\n          \"aui-md-table w-full border-separate border-spacing-0\",\n          className,\n        )}\n        {...props}\n      />\n    </div>\n  ),\n  th: ({ className, ...props }) => (\n    <th\n      className={cn(\n        \"aui-md-th bg-muted px-3 py-1.5 text-start font-medium first:rounded-ss-lg last:rounded-se-lg [[align=center]]:text-center [[align=right]]:text-right\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  td: ({ className, ...props }) => (\n    <td\n      className={cn(\n        \"aui-md-td border-muted-foreground/20 border-s border-b px-3 py-1.5 text-start last:border-e [[align=center]]:text-center [[align=right]]:text-right\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  tr: ({ className, ...props }) => (\n    <tr\n      className={cn(\n        \"aui-md-tr m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-es-lg [&:last-child>td:last-child]:rounded-ee-lg\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  li: ({ className, ...props }) => (\n    <li className={cn(\"aui-md-li leading-relaxed\", className)} {...props} />\n  ),\n  strong: ({ className, ...props }) => (\n    <strong\n      className={cn(\"aui-md-strong font-semibold\", className)}\n      {...props}\n    />\n  ),\n  sup: ({ className, ...props }) => (\n    <sup\n      className={cn(\"aui-md-sup [&>a]:text-xs [&>a]:no-underline\", className)}\n      {...props}\n    />\n  ),\n  pre: ({ className, ...props }) => (\n    <pre\n      className={cn(\n        \"aui-md-pre border-border/50 bg-muted/30 overflow-x-auto rounded-t-none rounded-b-xl border border-t-0 p-3.5 text-[13px] leading-relaxed\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  code: function Code({ className, ...props }) {\n    const isCodeBlock = useIsMarkdownCodeBlock();\n    return (\n      <code\n        className={cn(\n          !isCodeBlock &&\n            \"aui-md-inline-code bg-muted rounded-md px-1.5 py-0.5 font-mono text-[0.85em]\",\n          className,\n        )}\n        {...props}\n      />\n    );\n  },\n  CodeHeader,\n});\n"
    },
    {
      "type": "registry:file",
      "path": "hooks/use-copy-to-clipboard.ts",
      "target": "hooks/use-copy-to-clipboard.ts",
      "sourcePath": "packages/ui/src/hooks/use-copy-to-clipboard.ts",
      "content": "\"use client\";\n\nimport { useState } from \"react\";\n\nexport type UseCopyToClipboardOptions = {\n  copiedDuration?: number;\n};\n\nexport const useCopyToClipboard = ({\n  copiedDuration = 3000,\n}: UseCopyToClipboardOptions = {}) => {\n  const [isCopied, setIsCopied] = useState<boolean>(false);\n\n  const copyToClipboard = (value: string) => {\n    if (!value || typeof navigator === \"undefined\" || !navigator.clipboard) {\n      return;\n    }\n\n    navigator.clipboard.writeText(value).then(\n      () => {\n        setIsCopied(true);\n        setTimeout(() => setIsCopied(false), copiedDuration);\n      },\n      () => {},\n    );\n  };\n\n  return { isCopied, copyToClipboard };\n};\n"
    },
    {
      "type": "registry:file",
      "path": "components/assistant-ui/elements/reasoning.aui.tsx",
      "target": "components/assistant-ui/elements/reasoning.aui.tsx",
      "sourcePath": "packages/ui/src/components/react/assistant-ui/elements/reasoning.aui.tsx",
      "content": "\"use client\";\n\nimport { memo, useCallback, useRef } from \"react\";\nimport {\n  useScrollLock,\n  useAuiState,\n  type ReasoningMessagePartComponent,\n  type ReasoningGroupComponent,\n} from \"@assistant-ui/react\";\nimport { MarkdownText } from \"@/components/assistant-ui/elements/markdown-text\";\nimport {\n  ANIMATION_DURATION,\n  ReasoningRoot as ReasoningRootBase,\n  ReasoningTrigger,\n  ReasoningContent,\n  ReasoningText,\n  ReasoningFade,\n  reasoningVariants,\n  type ReasoningRootProps,\n} from \"./reasoning\";\n\nexport type { ReasoningRootProps } from \"./reasoning\";\n\n/** `ReasoningRoot` with the thread viewport scroll locked during disclosure animations. */\nfunction ReasoningRoot({\n  ref,\n  onAnimationStart,\n  ...props\n}: ReasoningRootProps) {\n  const collapsibleRef = useRef<HTMLDivElement | null>(null);\n  const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION);\n\n  const handleAnimationStart = useCallback(() => {\n    lockScroll();\n    onAnimationStart?.();\n  }, [lockScroll, onAnimationStart]);\n\n  const composedRef = useCallback(\n    (node: HTMLDivElement | null) => {\n      collapsibleRef.current = node;\n      if (typeof ref === \"function\") {\n        ref(node);\n      } else if (ref) {\n        ref.current = node;\n      }\n    },\n    [ref],\n  );\n\n  return (\n    <ReasoningRootBase\n      ref={composedRef}\n      onAnimationStart={handleAnimationStart}\n      {...props}\n    />\n  );\n}\n\nconst ReasoningImpl: ReasoningMessagePartComponent = () => <MarkdownText />;\n\nconst ReasoningGroupImpl: ReasoningGroupComponent = ({\n  children,\n  startIndex,\n  endIndex,\n}) => {\n  const isReasoningStreaming = useAuiState((s) => {\n    if (s.message.status?.type !== \"running\") return false;\n    for (let index = startIndex; index <= endIndex; index++) {\n      if (s.message.parts[index]?.status.type === \"running\") return true;\n    }\n    return false;\n  });\n\n  return (\n    <ReasoningRoot streaming={isReasoningStreaming}>\n      <ReasoningTrigger active={isReasoningStreaming} />\n      <ReasoningContent aria-busy={isReasoningStreaming}>\n        <ReasoningText>{children}</ReasoningText>\n      </ReasoningContent>\n    </ReasoningRoot>\n  );\n};\n\nconst Reasoning = memo(\n  ReasoningImpl,\n) as unknown as ReasoningMessagePartComponent & {\n  Root: typeof ReasoningRoot;\n  Trigger: typeof ReasoningTrigger;\n  Content: typeof ReasoningContent;\n  Text: typeof ReasoningText;\n  Fade: typeof ReasoningFade;\n};\n\nReasoning.displayName = \"Reasoning\";\nReasoning.Root = ReasoningRoot;\nReasoning.Trigger = ReasoningTrigger;\nReasoning.Content = ReasoningContent;\nReasoning.Text = ReasoningText;\nReasoning.Fade = ReasoningFade;\n\n/**\n * @deprecated This wrapper targets the legacy `components.ReasoningGroup`\n * prop on `<MessagePrimitive.Parts>`. Use `<MessagePrimitive.GroupedParts>`\n * with a `groupBy` returning `\"group-reasoning\"` and compose `ReasoningRoot`\n * / `ReasoningTrigger` / `ReasoningContent` / `ReasoningText` directly.\n * See `thread.aui.tsx` for an example.\n */\nconst ReasoningGroup = memo(ReasoningGroupImpl);\nReasoningGroup.displayName = \"ReasoningGroup\";\n\nexport {\n  Reasoning,\n  ReasoningGroup,\n  ReasoningRoot,\n  ReasoningTrigger,\n  ReasoningContent,\n  ReasoningText,\n  ReasoningFade,\n  reasoningVariants,\n};\n"
    },
    {
      "type": "registry:file",
      "path": "components/assistant-ui/elements/reasoning.tsx",
      "target": "components/assistant-ui/elements/reasoning.tsx",
      "sourcePath": "packages/ui/src/components/react/assistant-ui/elements/reasoning.tsx",
      "content": "\"use client\";\n\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { BrainIcon, ChevronDownIcon } from \"lucide-react\";\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\";\nimport { cn } from \"@/lib/utils\";\n\nexport const ANIMATION_DURATION = 200;\n\nconst ReasoningPreviewContext = createContext(false);\n\nconst reasoningVariants = cva(\"aui-reasoning-root mb-4 w-full\", {\n  variants: {\n    variant: {\n      outline: \"rounded-lg border px-3 py-2\",\n      ghost: \"\",\n      muted: \"bg-muted/50 rounded-lg px-3 py-2\",\n    },\n  },\n  defaultVariants: {\n    variant: \"outline\",\n  },\n});\n\nexport type ReasoningRootProps = Omit<\n  React.ComponentProps<typeof Collapsible>,\n  \"open\" | \"onOpenChange\"\n> &\n  VariantProps<typeof reasoningVariants> & {\n    open?: boolean;\n    onOpenChange?: (open: boolean) => void;\n    defaultOpen?: boolean;\n    /**\n     * Whether the reasoning is currently streaming. While `true` the\n     * disclosure is held open with a bottom-pinned live preview; when\n     * streaming ends it returns to `defaultOpen`, and the first manual\n     * toggle takes over the open/close state permanently. The live preview\n     * keeps following the newest tokens while the disclosure is open during\n     * streaming, even after a manual toggle, and pauses while the reader is\n     * scrolled up.\n     */\n    streaming?: boolean;\n    /** Called right before the disclosure animates, on toggle and on streaming transitions. */\n    onAnimationStart?: () => void;\n  };\n\nfunction ReasoningRoot({\n  className,\n  variant,\n  open: controlledOpen,\n  onOpenChange: controlledOnOpenChange,\n  defaultOpen = false,\n  streaming,\n  onAnimationStart,\n  children,\n  ...props\n}: ReasoningRootProps) {\n  const initialOpenRef = useRef(defaultOpen);\n  const [userOpen, setUserOpen] = useState<boolean | null>(null);\n\n  const isControlled = controlledOpen !== undefined;\n  const isOpen = isControlled\n    ? controlledOpen\n    : (userOpen ?? (streaming || initialOpenRef.current));\n  const isPreview = streaming === true && isOpen;\n\n  const prevStreamingRef = useRef(streaming);\n  useLayoutEffect(() => {\n    if (prevStreamingRef.current === streaming) return;\n    prevStreamingRef.current = streaming;\n    // A streaming transition only animates the panel when the resting state\n    // is collapsed; with `defaultOpen` the disclosure stays open across it.\n    if (!isControlled && userOpen === null && !initialOpenRef.current) {\n      onAnimationStart?.();\n    }\n  }, [streaming, isControlled, userOpen, onAnimationStart]);\n\n  const handleOpenChange = useCallback(\n    (open: boolean) => {\n      onAnimationStart?.();\n      if (!isControlled) {\n        setUserOpen(open);\n      }\n      controlledOnOpenChange?.(open);\n    },\n    [onAnimationStart, isControlled, controlledOnOpenChange],\n  );\n\n  return (\n    <Collapsible\n      data-slot=\"reasoning-root\"\n      data-variant={variant}\n      open={isOpen}\n      onOpenChange={handleOpenChange}\n      className={cn(\n        \"group/reasoning-root\",\n        reasoningVariants({ variant, className }),\n      )}\n      style={\n        {\n          \"--animation-duration\": `${ANIMATION_DURATION}ms`,\n        } as React.CSSProperties\n      }\n      {...props}\n    >\n      <ReasoningPreviewContext.Provider value={isPreview}>\n        {children}\n      </ReasoningPreviewContext.Provider>\n    </Collapsible>\n  );\n}\n\nfunction ReasoningFade({\n  side = \"bottom\",\n  className,\n  ...props\n}: React.ComponentProps<\"div\"> & { side?: \"top\" | \"bottom\" }) {\n  if (side === \"top\") {\n    return (\n      <div\n        data-slot=\"reasoning-fade\"\n        className={cn(\n          \"aui-reasoning-fade pointer-events-none absolute inset-x-0 top-0 z-10 h-8\",\n          \"bg-[linear-gradient(to_bottom,var(--color-background),transparent)]\",\n          \"group-data-[variant=muted]/reasoning-root:bg-[linear-gradient(to_bottom,color-mix(in_oklab,var(--color-muted)_50%,var(--color-background)),transparent)]\",\n          \"fade-in-0 animate-in\",\n          \"animation-duration-(--animation-duration)\",\n          className,\n        )}\n        {...props}\n      />\n    );\n  }\n\n  return (\n    <div\n      data-slot=\"reasoning-fade\"\n      className={cn(\n        \"aui-reasoning-fade pointer-events-none absolute inset-x-0 bottom-0 z-10 h-8\",\n        \"bg-[linear-gradient(to_top,var(--color-background),transparent)]\",\n        \"group-data-[variant=muted]/reasoning-root:bg-[linear-gradient(to_top,color-mix(in_oklab,var(--color-muted)_50%,var(--color-background)),transparent)]\",\n        \"fade-in-0 animate-in\",\n        \"animation-duration-(--animation-duration)\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction ReasoningTrigger({\n  active,\n  duration,\n  className,\n  ...props\n}: React.ComponentProps<typeof CollapsibleTrigger> & {\n  active?: boolean;\n  duration?: number;\n}) {\n  const durationText = duration ? ` (${duration}s)` : \"\";\n\n  return (\n    <CollapsibleTrigger\n      data-slot=\"reasoning-trigger\"\n      className={cn(\n        \"aui-reasoning-trigger group/trigger text-muted-foreground hover:text-foreground flex max-w-[75%] origin-left items-center gap-2 py-1.5 text-sm transition-[color,scale] active:scale-[0.98]\",\n        className,\n      )}\n      {...props}\n    >\n      <BrainIcon\n        data-slot=\"reasoning-trigger-icon\"\n        className=\"aui-reasoning-trigger-icon size-4 shrink-0\"\n      />\n      <span\n        data-slot=\"reasoning-trigger-label\"\n        className={cn(\n          \"aui-reasoning-trigger-label-wrapper inline-block leading-none tabular-nums\",\n          active && \"shimmer motion-reduce:animate-none\",\n        )}\n      >\n        Reasoning{durationText}\n      </span>\n      <ChevronDownIcon\n        data-slot=\"reasoning-trigger-chevron\"\n        className={cn(\n          \"aui-reasoning-trigger-chevron mt-0.5 size-4 shrink-0\",\n          \"transition-transform duration-(--animation-duration) ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none\",\n          \"-rotate-90\",\n          \"group-data-open/trigger:rotate-0\",\n          \"group-data-panel-open/trigger:rotate-0\",\n        )}\n      />\n    </CollapsibleTrigger>\n  );\n}\n\nfunction ReasoningContent({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof CollapsibleContent>) {\n  const isPreview = useContext(ReasoningPreviewContext);\n\n  return (\n    <CollapsibleContent\n      data-slot=\"reasoning-content\"\n      className={cn(\n        \"aui-reasoning-content text-muted-foreground relative overflow-hidden text-sm outline-none\",\n        \"group/collapsible-content ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:animate-none\",\n        \"data-closed:animate-collapsible-up\",\n        \"data-open:animate-collapsible-down\",\n        \"data-closed:fill-mode-forwards\",\n        \"data-closed:pointer-events-none\",\n        \"[--tw-duration:var(--animation-duration)]\",\n        className,\n      )}\n      {...props}\n    >\n      <ReasoningFade side=\"top\" />\n      {children}\n      {isPreview ? <ReasoningFade /> : null}\n    </CollapsibleContent>\n  );\n}\n\nfunction ReasoningText({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  const isPreview = useContext(ReasoningPreviewContext);\n  const scrollRef = useRef<HTMLDivElement>(null);\n  const contentRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    if (!isPreview) return;\n    const scrollEl = scrollRef.current;\n    const contentEl = contentRef.current;\n    if (!scrollEl || !contentEl) return;\n\n    let pinned = true;\n    let lastScrollTop = scrollEl.scrollTop;\n    let lastScrollHeight = scrollEl.scrollHeight;\n    const isAtBottom = () =>\n      Math.abs(\n        scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight,\n      ) <= 1 || scrollEl.scrollHeight <= scrollEl.clientHeight;\n\n    const pin = () => {\n      if (!pinned) return;\n      scrollEl.scrollTop = scrollEl.scrollHeight;\n    };\n    // A pin's own scroll event can arrive after new content grew the scroll\n    // height and read as \"not at bottom\"; only an upward move at unchanged\n    // scroll height is user intent.\n    const onScroll = () => {\n      if (isAtBottom()) {\n        pinned = true;\n      } else if (\n        scrollEl.scrollTop < lastScrollTop &&\n        scrollEl.scrollHeight === lastScrollHeight\n      ) {\n        pinned = false;\n      }\n      lastScrollTop = scrollEl.scrollTop;\n      lastScrollHeight = scrollEl.scrollHeight;\n    };\n\n    pin();\n    scrollEl.addEventListener(\"scroll\", onScroll);\n    const observer = new ResizeObserver(pin);\n    observer.observe(contentEl);\n    return () => {\n      scrollEl.removeEventListener(\"scroll\", onScroll);\n      observer.disconnect();\n    };\n  }, [isPreview]);\n\n  return (\n    <div\n      ref={scrollRef}\n      data-slot=\"reasoning-text\"\n      className={cn(\n        \"aui-reasoning-text relative z-0 max-h-64 overflow-y-auto ps-6 pt-2 pb-2 leading-relaxed text-pretty\",\n        \"transform-gpu transition-[transform,opacity] ease-[cubic-bezier(0.32,0.72,0,1)]\",\n        \"motion-reduce:animate-none\",\n        \"group-data-open/collapsible-content:animate-in\",\n        \"group-data-closed/collapsible-content:animate-out\",\n        \"group-data-open/collapsible-content:fade-in-0\",\n        \"group-data-closed/collapsible-content:fade-out-0\",\n        \"group-data-open/collapsible-content:slide-in-from-top-4\",\n        \"group-data-closed/collapsible-content:slide-out-to-top-4\",\n        \"group-data-open/collapsible-content:blur-in-[2px]\",\n        \"group-data-closed/collapsible-content:blur-out-[2px]\",\n        \"group-data-open/collapsible-content:animation-duration-(--animation-duration)\",\n        \"group-data-closed/collapsible-content:animation-duration-(--animation-duration)\",\n        className,\n      )}\n      {...props}\n    >\n      <div ref={contentRef} className=\"aui-reasoning-text-content space-y-4\">\n        {children}\n      </div>\n    </div>\n  );\n}\n\nexport {\n  ReasoningRoot,\n  ReasoningTrigger,\n  ReasoningContent,\n  ReasoningText,\n  ReasoningFade,\n  reasoningVariants,\n};\n"
    },
    {
      "type": "registry:file",
      "path": "components/assistant-ui/elements/tool-fallback.aui.tsx",
      "target": "components/assistant-ui/elements/tool-fallback.aui.tsx",
      "sourcePath": "packages/ui/src/components/react/assistant-ui/elements/tool-fallback.aui.tsx",
      "content": "\"use client\";\n\nimport { memo, useCallback, useRef, useState } from \"react\";\nimport {\n  AlertCircleIcon,\n  CheckIcon,\n  ChevronDownIcon,\n  LoaderIcon,\n  XCircleIcon,\n} from \"lucide-react\";\nimport {\n  toolApprovalAcceptsText,\n  useScrollLock,\n  useToolCallElapsed,\n  type ToolApprovalOption,\n  type ToolCallMessagePart,\n  type ToolCallMessagePartProps,\n  type ToolCallMessagePartStatus,\n  type ToolCallMessagePartComponent,\n} from \"@assistant-ui/react\";\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport { Textarea } from \"@/components/ui/textarea\";\n\nconst ANIMATION_DURATION = 200;\n\nconst pressable = \"active:scale-[0.98]\";\n\nexport type ToolFallbackRootProps = Omit<\n  React.ComponentProps<typeof Collapsible>,\n  \"open\" | \"onOpenChange\"\n> & {\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  defaultOpen?: boolean;\n};\n\nfunction ToolFallbackRoot({\n  className,\n  open: controlledOpen,\n  onOpenChange: controlledOnOpenChange,\n  defaultOpen = false,\n  children,\n  ...props\n}: ToolFallbackRootProps) {\n  const collapsibleRef = useRef<HTMLDivElement>(null);\n  const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n  const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION);\n\n  const isControlled = controlledOpen !== undefined;\n  const isOpen = isControlled ? controlledOpen : uncontrolledOpen;\n\n  const handleOpenChange = useCallback(\n    (open: boolean) => {\n      lockScroll();\n      if (!isControlled) {\n        setUncontrolledOpen(open);\n      }\n      controlledOnOpenChange?.(open);\n    },\n    [lockScroll, isControlled, controlledOnOpenChange],\n  );\n\n  return (\n    <Collapsible\n      ref={collapsibleRef}\n      data-slot=\"tool-fallback-root\"\n      open={isOpen}\n      onOpenChange={handleOpenChange}\n      className={cn(\n        \"aui-tool-fallback-root group/tool-fallback-root w-full\",\n        className,\n      )}\n      style={\n        {\n          \"--animation-duration\": `${ANIMATION_DURATION}ms`,\n        } as React.CSSProperties\n      }\n      {...props}\n    >\n      {children}\n    </Collapsible>\n  );\n}\n\ntype ToolStatus = ToolCallMessagePartStatus[\"type\"];\n\nconst statusIconMap: Record<ToolStatus, React.ElementType> = {\n  running: LoaderIcon,\n  complete: CheckIcon,\n  incomplete: XCircleIcon,\n  \"requires-action\": AlertCircleIcon,\n};\n\nconst formatToolDuration = (ms: number) => {\n  if (ms < 1000) return \"<1s\";\n  const seconds = ms / 1000;\n  if (seconds < 10) return `${(Math.floor(seconds * 10) / 10).toFixed(1)}s`;\n  if (seconds < 60) return `${Math.floor(seconds)}s`;\n  return `${Math.floor(seconds / 60)}m ${Math.floor(seconds % 60)}s`;\n};\n\nfunction ToolFallbackDuration({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  const elapsedMs = useToolCallElapsed();\n  if (elapsedMs === undefined) return null;\n\n  return (\n    <span\n      data-slot=\"tool-fallback-duration\"\n      className={cn(\n        \"aui-tool-fallback-duration text-muted-foreground text-xs tabular-nums\",\n        className,\n      )}\n      {...props}\n    >\n      {formatToolDuration(elapsedMs)}\n    </span>\n  );\n}\n\nfunction ToolFallbackTrigger({\n  toolName,\n  status,\n  className,\n  ...props\n}: React.ComponentProps<typeof CollapsibleTrigger> & {\n  toolName: string;\n  status?: ToolCallMessagePartStatus;\n}) {\n  const statusType = status?.type ?? \"complete\";\n  const isRunning = statusType === \"running\";\n  const isCancelled =\n    status?.type === \"incomplete\" && status.reason === \"cancelled\";\n\n  const Icon = statusIconMap[statusType];\n  const label = isCancelled ? \"Cancelled tool\" : \"Used tool\";\n\n  return (\n    <CollapsibleTrigger\n      data-slot=\"tool-fallback-trigger\"\n      className={cn(\n        \"aui-tool-fallback-trigger group/trigger text-muted-foreground hover:text-foreground flex w-fit origin-left items-center gap-2 py-1.5 text-sm transition-[color,scale] active:scale-[0.98]\",\n        className,\n      )}\n      {...props}\n    >\n      <Icon\n        data-slot=\"tool-fallback-trigger-icon\"\n        className={cn(\n          \"aui-tool-fallback-trigger-icon size-4 shrink-0\",\n          isCancelled && \"text-muted-foreground\",\n          isRunning && \"animate-spin [animation-duration:0.6s]\",\n        )}\n      />\n      <span\n        data-slot=\"tool-fallback-trigger-label\"\n        className={cn(\n          \"aui-tool-fallback-trigger-label-wrapper inline-block text-start leading-none\",\n          isCancelled && \"text-muted-foreground line-through\",\n          isRunning && \"shimmer motion-reduce:animate-none\",\n        )}\n      >\n        {label}: <b>{toolName}</b>\n      </span>\n      <ToolFallbackDuration />\n      <ChevronDownIcon\n        data-slot=\"tool-fallback-trigger-chevron\"\n        className={cn(\n          \"aui-tool-fallback-trigger-chevron size-4 shrink-0\",\n          \"transition-transform duration-(--animation-duration) ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none\",\n          \"-rotate-90\",\n          \"group-data-open/trigger:rotate-0\",\n          \"group-data-panel-open/trigger:rotate-0\",\n        )}\n      />\n    </CollapsibleTrigger>\n  );\n}\n\nfunction ToolFallbackContent({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof CollapsibleContent>) {\n  return (\n    <CollapsibleContent\n      data-slot=\"tool-fallback-content\"\n      className={cn(\n        \"aui-tool-fallback-content relative overflow-hidden text-sm outline-none\",\n        \"group/collapsible-content ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:animate-none\",\n        \"data-closed:animate-collapsible-up\",\n        \"data-open:animate-collapsible-down\",\n        \"data-closed:fill-mode-forwards\",\n        \"data-closed:pointer-events-none\",\n        \"[--tw-duration:var(--animation-duration)]\",\n        className,\n      )}\n      {...props}\n    >\n      <div\n        className={cn(\n          \"flex flex-col gap-2 ps-6 pt-1 pb-2 ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:animate-none\",\n          \"group-data-open/collapsible-content:animate-in group-data-open/collapsible-content:fade-in-0 group-data-open/collapsible-content:blur-in-[2px] group-data-open/collapsible-content:slide-in-from-top-1\",\n          \"group-data-closed/collapsible-content:animate-out group-data-closed/collapsible-content:fade-out-0 group-data-closed/collapsible-content:blur-out-[2px] group-data-closed/collapsible-content:slide-out-to-top-1\",\n          \"group-data-closed/collapsible-content:animation-duration-(--animation-duration) group-data-open/collapsible-content:animation-duration-(--animation-duration)\",\n        )}\n      >\n        {children}\n      </div>\n    </CollapsibleContent>\n  );\n}\n\nfunction ToolFallbackArgs({\n  argsText,\n  className,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  argsText?: string;\n}) {\n  if (!argsText) return null;\n\n  return (\n    <div\n      data-slot=\"tool-fallback-args\"\n      className={cn(\"aui-tool-fallback-args\", className)}\n      {...props}\n    >\n      <pre className=\"aui-tool-fallback-args-value bg-muted/50 text-foreground/90 rounded-md p-2.5 text-xs whitespace-pre-wrap\">\n        {argsText}\n      </pre>\n    </div>\n  );\n}\n\nfunction ToolFallbackResult({\n  result,\n  className,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  result?: unknown;\n}) {\n  if (result === undefined) return null;\n\n  return (\n    <div\n      data-slot=\"tool-fallback-result\"\n      className={cn(\"aui-tool-fallback-result\", className)}\n      {...props}\n    >\n      <p className=\"aui-tool-fallback-result-header text-muted-foreground text-xs font-medium\">\n        Result:\n      </p>\n      <pre className=\"aui-tool-fallback-result-content bg-muted/50 text-foreground/90 mt-1 rounded-md p-2.5 text-xs whitespace-pre-wrap\">\n        {typeof result === \"string\" ? result : JSON.stringify(result, null, 2)}\n      </pre>\n    </div>\n  );\n}\n\nfunction ToolFallbackError({\n  status,\n  className,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  status?: ToolCallMessagePartStatus;\n}) {\n  if (status?.type !== \"incomplete\") return null;\n\n  const error = status.error;\n  const errorText = error\n    ? typeof error === \"string\"\n      ? error\n      : JSON.stringify(error)\n    : null;\n\n  if (!errorText) return null;\n\n  const isCancelled = status.reason === \"cancelled\";\n  const headerText = isCancelled ? \"Cancelled reason:\" : \"Error:\";\n\n  return (\n    <div\n      data-slot=\"tool-fallback-error\"\n      className={cn(\"aui-tool-fallback-error\", className)}\n      {...props}\n    >\n      <p className=\"aui-tool-fallback-error-header text-muted-foreground font-semibold\">\n        {headerText}\n      </p>\n      <p className=\"aui-tool-fallback-error-reason text-muted-foreground\">\n        {errorText}\n      </p>\n    </div>\n  );\n}\n\nconst APPROVED_RESULT = \"Approved by user\";\nconst DENIED_RESULT = \"User denied tool execution\";\n\nconst APPROVAL_OPTION_DEFAULT_LABELS: Record<string, string> = {\n  \"allow-once\": \"Allow\",\n  \"allow-always\": \"Always allow\",\n  \"reject-once\": \"Deny\",\n  \"reject-always\": \"Always deny\",\n};\n\nconst isKnownKind = (kind: string) =>\n  Object.hasOwn(APPROVAL_OPTION_DEFAULT_LABELS, kind);\n\nconst isAllowKind = (kind: string) =>\n  kind === \"allow-once\" || kind === \"allow-always\";\n\nconst approvalOptionLabel = (option: ToolApprovalOption) =>\n  option.label ??\n  (isKnownKind(option.kind)\n    ? APPROVAL_OPTION_DEFAULT_LABELS[option.kind]\n    : undefined) ??\n  option.id;\n\n/**\n * A request that declares how it wants to be presented is asking a question,\n * not gating an action, so a refusal is not one of the answers it accepts.\n */\nconst isQuestion = (approval: ToolCallMessagePart[\"approval\"]) =>\n  approval?.display === \"select\" || approval?.display === \"text\";\n\nconst offersInterruptAction = (\n  status: ToolCallMessagePartStatus | undefined,\n  approval: ToolCallMessagePart[\"approval\"],\n  interrupt: ToolCallMessagePart[\"interrupt\"],\n) =>\n  status?.type !== \"requires-action\" ||\n  status.reason !== \"interrupt\" ||\n  approval != null ||\n  interrupt != null;\n\nfunction ToolFallbackApproval({\n  className,\n  addResult,\n  resume,\n  interrupt,\n  approval,\n  respondToApproval,\n  status,\n  ...props\n}: React.ComponentProps<\"div\"> &\n  Partial<\n    Pick<\n      ToolCallMessagePartProps,\n      \"addResult\" | \"resume\" | \"respondToApproval\" | \"status\"\n    >\n  > & {\n    interrupt?: ToolCallMessagePart[\"interrupt\"];\n    approval?: ToolCallMessagePart[\"approval\"];\n  }) {\n  const [submitted, setSubmitted] = useState(false);\n  const [confirmingId, setConfirmingId] = useState<string | null>(null);\n  const [answer, setAnswer] = useState(\"\");\n  const [error, setError] = useState<string | null>(null);\n\n  if (\n    approval != null &&\n    (approval.approved !== undefined || approval.resolution !== undefined)\n  )\n    return null;\n\n  if (!offersInterruptAction(status, approval, interrupt)) return null;\n\n  // A declared option list is a host constraint: the kit never adds an\n  // approval path beyond it, and preserves a refusal path only where the\n  // request is an action the user may refuse.\n  const declaredOptions = respondToApproval ? approval?.options : undefined;\n  const acceptsText =\n    approval != null &&\n    respondToApproval != null &&\n    toolApprovalAcceptsText(approval);\n\n  // A refused response leaves the request open, so the controls come back\n  // rather than staying spent on a decision the runtime never recorded.\n  const submit = (send: () => Promise<void> | void) => {\n    setSubmitted(true);\n    setError(null);\n    void (async () => {\n      try {\n        await send();\n      } catch (sendError) {\n        setSubmitted(false);\n        setError(\n          sendError instanceof Error ? sendError.message : String(sendError),\n        );\n      }\n    })();\n  };\n\n  const respond = (approved: boolean) => {\n    if (submitted) return;\n    if (\n      approval != null &&\n      approval.approved === undefined &&\n      respondToApproval\n    ) {\n      submit(() => respondToApproval({ approved, ...typedAnswer() }));\n    } else if (interrupt) {\n      submit(() => resume?.({ approved }));\n    } else if (\n      status?.type === \"requires-action\" &&\n      status.reason === \"interrupt\"\n    ) {\n      return;\n    } else {\n      submit(() => addResult?.(approved ? APPROVED_RESULT : DENIED_RESULT));\n    }\n  };\n\n  const respondWithOption = (option: ToolApprovalOption) => {\n    if (submitted) return;\n    setConfirmingId(null);\n    // A custom kind has no decision class for the runtime to derive, and\n    // responding without one throws; picking a declared option is an answer,\n    // so it resolves as approved.\n    submit(() =>\n      respondToApproval?.(\n        isKnownKind(option.kind)\n          ? { optionId: option.id, ...typedAnswer() }\n          : { optionId: option.id, approved: true, ...typedAnswer() },\n      ),\n    );\n  };\n\n  const typedAnswer = () => (answer.trim() ? { text: answer } : {});\n\n  const submitAnswer = () => {\n    if (submitted || !answer.trim()) return;\n    submit(() => respondToApproval?.({ text: answer }));\n  };\n\n  const handleOption = (option: ToolApprovalOption) => {\n    if (option.confirm) {\n      setConfirmingId(option.id);\n    } else {\n      respondWithOption(option);\n    }\n  };\n\n  const confirming =\n    confirmingId != null\n      ? declaredOptions?.find((o) => o.id === confirmingId)\n      : undefined;\n\n  const question = isQuestion(approval);\n\n  const promptText = approval?.prompt ? (\n    <p className=\"aui-tool-fallback-approval-prompt text-foreground\">\n      {approval.prompt}\n    </p>\n  ) : null;\n\n  const errorText = error ? (\n    <p\n      role=\"alert\"\n      className=\"aui-tool-fallback-approval-error text-destructive text-xs\"\n    >\n      {error}\n    </p>\n  ) : null;\n\n  const answerField = acceptsText ? (\n    <div className=\"aui-tool-fallback-approval-answer flex flex-col items-start gap-2\">\n      <Textarea\n        value={answer}\n        onChange={(event) => setAnswer(event.target.value)}\n        disabled={submitted}\n        aria-label={question ? (approval?.prompt ?? \"Answer\") : \"Note\"}\n        placeholder={\n          question ? \"Type your answer\" : \"Add a note to your decision\"\n        }\n      />\n      {question && (\n        <Button\n          size=\"sm\"\n          className={pressable}\n          onClick={submitAnswer}\n          disabled={submitted || !answer.trim()}\n        >\n          Send\n        </Button>\n      )}\n    </div>\n  ) : null;\n\n  if (confirming) {\n    const confirmMeta =\n      typeof confirming.confirm === \"object\" ? confirming.confirm : undefined;\n    const confirmDescription =\n      confirmMeta?.description ?? confirming.description;\n    return (\n      <div\n        data-slot=\"tool-fallback-approval-confirm\"\n        className={cn(\n          \"aui-tool-fallback-approval-confirm flex flex-col gap-2 pt-1\",\n          className,\n        )}\n        {...props}\n      >\n        <p className=\"aui-tool-fallback-approval-confirm-title font-semibold\">\n          {confirmMeta?.title ?? `${approvalOptionLabel(confirming)}?`}\n        </p>\n        {confirmDescription && (\n          <p className=\"aui-tool-fallback-approval-confirm-description text-muted-foreground\">\n            {confirmDescription}\n          </p>\n        )}\n        {confirming.grants && confirming.grants.length > 0 && (\n          <ul className=\"aui-tool-fallback-approval-confirm-grants flex flex-col gap-1\">\n            {confirming.grants.map((grant) => (\n              <li key={grant}>\n                <code className=\"aui-tool-fallback-approval-confirm-grant bg-muted rounded px-1.5 py-0.5 text-xs\">\n                  {grant}\n                </code>\n              </li>\n            ))}\n          </ul>\n        )}\n        <div className=\"flex items-center gap-2\">\n          <Button\n            size=\"sm\"\n            className={pressable}\n            onClick={() => respondWithOption(confirming)}\n            disabled={submitted}\n          >\n            Confirm\n          </Button>\n          <Button\n            size=\"sm\"\n            variant=\"outline\"\n            className={pressable}\n            onClick={() => setConfirmingId(null)}\n            disabled={submitted}\n          >\n            Back\n          </Button>\n        </div>\n      </div>\n    );\n  }\n\n  if (declaredOptions && declaredOptions.length > 0) {\n    const allowOptions = declaredOptions.filter((o) => isAllowKind(o.kind));\n    const customOptions = declaredOptions.filter((o) => !isKnownKind(o.kind));\n    const rejectOptions = declaredOptions.filter(\n      (o) => isKnownKind(o.kind) && !isAllowKind(o.kind),\n    );\n    return (\n      <div\n        data-slot=\"tool-fallback-approval\"\n        className={cn(\n          \"aui-tool-fallback-approval flex flex-col gap-2 pt-1\",\n          className,\n        )}\n        {...props}\n      >\n        {promptText}\n        <div className=\"flex flex-wrap items-center gap-2\">\n          {[...allowOptions, ...customOptions, ...rejectOptions].map(\n            (option) => (\n              <Button\n                key={option.id}\n                size=\"sm\"\n                variant={option === allowOptions[0] ? \"default\" : \"outline\"}\n                className={pressable}\n                onClick={() => handleOption(option)}\n                disabled={submitted}\n              >\n                {approvalOptionLabel(option)}\n              </Button>\n            ),\n          )}\n          {rejectOptions.length === 0 && !question && (\n            <Button\n              size=\"sm\"\n              variant=\"outline\"\n              className={pressable}\n              onClick={() => respond(false)}\n              disabled={submitted}\n            >\n              Deny\n            </Button>\n          )}\n        </div>\n        {answerField}\n        {errorText}\n      </div>\n    );\n  }\n\n  // A question carries no decision to fabricate, so it renders only what the\n  // request declared, even when that leaves nothing to act on here.\n  if (question) {\n    return (\n      <div\n        data-slot=\"tool-fallback-approval\"\n        className={cn(\n          \"aui-tool-fallback-approval flex flex-col gap-2 pt-1\",\n          className,\n        )}\n        {...props}\n      >\n        {promptText}\n        {answerField}\n        {errorText}\n      </div>\n    );\n  }\n\n  return (\n    <div\n      data-slot=\"tool-fallback-approval\"\n      className={cn(\n        \"aui-tool-fallback-approval flex flex-col gap-2 pt-1\",\n        className,\n      )}\n      {...props}\n    >\n      {promptText}\n      <div className=\"flex items-center gap-2\">\n        <Button\n          size=\"sm\"\n          className={pressable}\n          onClick={() => respond(true)}\n          disabled={submitted}\n        >\n          Allow\n        </Button>\n        <Button\n          size=\"sm\"\n          variant=\"outline\"\n          className={pressable}\n          onClick={() => respond(false)}\n          disabled={submitted}\n        >\n          Deny\n        </Button>\n      </div>\n      {answerField}\n      {errorText}\n    </div>\n  );\n}\n\nconst ToolFallbackImpl: ToolCallMessagePartComponent = ({\n  toolName,\n  argsText,\n  result,\n  status,\n  addResult,\n  resume,\n  interrupt,\n  approval,\n  respondToApproval,\n}) => {\n  const isCancelled =\n    status?.type === \"incomplete\" && status.reason === \"cancelled\";\n  const isRequiresAction = status?.type === \"requires-action\";\n  const shouldRenderApproval =\n    isRequiresAction && offersInterruptAction(status, approval, interrupt);\n\n  const [open, setOpen] = useState(isRequiresAction);\n  const [prevRequiresAction, setPrevRequiresAction] =\n    useState(isRequiresAction);\n  if (isRequiresAction !== prevRequiresAction) {\n    setPrevRequiresAction(isRequiresAction);\n    if (isRequiresAction) setOpen(true);\n  }\n\n  return (\n    <ToolFallbackRoot open={open} onOpenChange={setOpen}>\n      <ToolFallbackTrigger toolName={toolName} status={status} />\n      <ToolFallbackContent>\n        <ToolFallbackError status={status} />\n        <ToolFallbackArgs\n          argsText={argsText}\n          className={cn(isCancelled && \"opacity-60\")}\n        />\n        {shouldRenderApproval && (\n          <ToolFallbackApproval\n            addResult={addResult}\n            resume={resume}\n            interrupt={interrupt}\n            approval={approval}\n            respondToApproval={respondToApproval}\n            status={status}\n          />\n        )}\n        {!isCancelled && <ToolFallbackResult result={result} />}\n      </ToolFallbackContent>\n    </ToolFallbackRoot>\n  );\n};\n\nconst ToolFallback = memo(\n  ToolFallbackImpl,\n) as unknown as ToolCallMessagePartComponent & {\n  Root: typeof ToolFallbackRoot;\n  Trigger: typeof ToolFallbackTrigger;\n  Content: typeof ToolFallbackContent;\n  Args: typeof ToolFallbackArgs;\n  Result: typeof ToolFallbackResult;\n  Error: typeof ToolFallbackError;\n  Approval: typeof ToolFallbackApproval;\n};\n\nToolFallback.displayName = \"ToolFallback\";\nToolFallback.Root = ToolFallbackRoot;\nToolFallback.Trigger = ToolFallbackTrigger;\nToolFallback.Content = ToolFallbackContent;\nToolFallback.Args = ToolFallbackArgs;\nToolFallback.Result = ToolFallbackResult;\nToolFallback.Error = ToolFallbackError;\nToolFallback.Approval = ToolFallbackApproval;\n\nexport {\n  ToolFallback,\n  ToolFallbackRoot,\n  ToolFallbackTrigger,\n  ToolFallbackContent,\n  ToolFallbackArgs,\n  ToolFallbackResult,\n  ToolFallbackError,\n  ToolFallbackApproval,\n};\n"
    },
    {
      "type": "registry:file",
      "path": "components/assistant-ui/elements/tool-group.aui.tsx",
      "target": "components/assistant-ui/elements/tool-group.aui.tsx",
      "sourcePath": "packages/ui/src/components/react/assistant-ui/elements/tool-group.aui.tsx",
      "content": "\"use client\";\n\nimport {\n  memo,\n  useCallback,\n  useRef,\n  useState,\n  type FC,\n  type PropsWithChildren,\n} from \"react\";\nimport { ChevronDownIcon, LoaderIcon } from \"lucide-react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { useScrollLock } from \"@assistant-ui/react\";\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\";\nimport { cn } from \"@/lib/utils\";\n\nconst ANIMATION_DURATION = 200;\n\nconst toolGroupVariants = cva(\"aui-tool-group-root group/tool-group w-full\", {\n  variants: {\n    variant: {\n      outline: \"rounded-lg border py-3\",\n      ghost: \"\",\n      muted: \"border-muted-foreground/30 bg-muted/30 rounded-lg border py-3\",\n    },\n  },\n  defaultVariants: { variant: \"outline\" },\n});\n\nexport type ToolGroupRootProps = Omit<\n  React.ComponentProps<typeof Collapsible>,\n  \"open\" | \"onOpenChange\"\n> &\n  VariantProps<typeof toolGroupVariants> & {\n    open?: boolean;\n    onOpenChange?: (open: boolean) => void;\n    defaultOpen?: boolean;\n  };\n\nfunction ToolGroupRoot({\n  className,\n  variant,\n  open: controlledOpen,\n  onOpenChange: controlledOnOpenChange,\n  defaultOpen = false,\n  children,\n  ...props\n}: ToolGroupRootProps) {\n  const collapsibleRef = useRef<HTMLDivElement>(null);\n  const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n  const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION);\n\n  const isControlled = controlledOpen !== undefined;\n  const isOpen = isControlled ? controlledOpen : uncontrolledOpen;\n\n  const handleOpenChange = useCallback(\n    (open: boolean) => {\n      lockScroll();\n      if (!isControlled) {\n        setUncontrolledOpen(open);\n      }\n      controlledOnOpenChange?.(open);\n    },\n    [lockScroll, isControlled, controlledOnOpenChange],\n  );\n\n  return (\n    <Collapsible\n      ref={collapsibleRef}\n      data-slot=\"tool-group-root\"\n      data-variant={variant ?? \"outline\"}\n      open={isOpen}\n      onOpenChange={handleOpenChange}\n      className={cn(\n        toolGroupVariants({ variant }),\n        \"group/tool-group-root\",\n        className,\n      )}\n      style={\n        {\n          \"--animation-duration\": `${ANIMATION_DURATION}ms`,\n        } as React.CSSProperties\n      }\n      {...props}\n    >\n      {children}\n    </Collapsible>\n  );\n}\n\nfunction ToolGroupTrigger({\n  count,\n  active = false,\n  className,\n  ...props\n}: React.ComponentProps<typeof CollapsibleTrigger> & {\n  count: number;\n  active?: boolean;\n}) {\n  const label = `${count} tool ${count === 1 ? \"call\" : \"calls\"}`;\n\n  return (\n    <CollapsibleTrigger\n      data-slot=\"tool-group-trigger\"\n      className={cn(\n        \"aui-tool-group-trigger group/trigger flex origin-left items-center gap-2 text-sm transition-[color,scale] active:scale-[0.98]\",\n        \"group-data-[variant=ghost]/tool-group-root:text-muted-foreground group-data-[variant=ghost]/tool-group-root:hover:text-foreground group-data-[variant=ghost]/tool-group-root:py-1.5\",\n        \"group-data-[variant=outline]/tool-group-root:w-full group-data-[variant=outline]/tool-group-root:px-4\",\n        \"group-data-[variant=muted]/tool-group-root:w-full group-data-[variant=muted]/tool-group-root:px-4\",\n        className,\n      )}\n      {...props}\n    >\n      {active && (\n        <LoaderIcon\n          data-slot=\"tool-group-trigger-loader\"\n          className=\"aui-tool-group-trigger-loader size-3 shrink-0 animate-spin [animation-duration:0.6s]\"\n        />\n      )}\n      <span\n        data-slot=\"tool-group-trigger-label\"\n        className={cn(\n          \"aui-tool-group-trigger-label-wrapper inline-block text-start text-xs leading-none font-medium\",\n          \"group-data-[variant=ghost]/tool-group-root:font-normal\",\n          \"group-data-[variant=outline]/tool-group-root:grow\",\n          \"group-data-[variant=muted]/tool-group-root:grow\",\n          active && \"shimmer motion-reduce:animate-none\",\n        )}\n      >\n        {label}\n      </span>\n      <ChevronDownIcon\n        data-slot=\"tool-group-trigger-chevron\"\n        className={cn(\n          \"aui-tool-group-trigger-chevron size-3 shrink-0\",\n          \"transition-transform duration-(--animation-duration) ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none\",\n          \"-rotate-90\",\n          \"group-data-open/trigger:rotate-0\",\n          \"group-data-panel-open/trigger:rotate-0\",\n        )}\n      />\n    </CollapsibleTrigger>\n  );\n}\n\nfunction ToolGroupContent({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof CollapsibleContent>) {\n  return (\n    <CollapsibleContent\n      data-slot=\"tool-group-content\"\n      className={cn(\n        \"aui-tool-group-content relative overflow-hidden text-sm outline-none\",\n        \"group/collapsible-content ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:animate-none\",\n        \"data-closed:animate-collapsible-up\",\n        \"data-open:animate-collapsible-down\",\n        \"data-closed:fill-mode-forwards\",\n        \"data-closed:pointer-events-none\",\n        \"[--tw-duration:var(--animation-duration)]\",\n        className,\n      )}\n      {...props}\n    >\n      <div\n        className={cn(\n          \"mt-2 flex flex-col gap-2\",\n          \"group-data-[variant=ghost]/tool-group-root:mt-1 group-data-[variant=ghost]/tool-group-root:gap-1\",\n          \"group-data-[variant=outline]/tool-group-root:mt-3 group-data-[variant=outline]/tool-group-root:border-t group-data-[variant=outline]/tool-group-root:px-4 group-data-[variant=outline]/tool-group-root:pt-3\",\n          \"group-data-[variant=muted]/tool-group-root:mt-3 group-data-[variant=muted]/tool-group-root:border-t group-data-[variant=muted]/tool-group-root:px-4 group-data-[variant=muted]/tool-group-root:pt-3\",\n          \"[&>*]:animate-in [&>*]:fade-in-0 [&>*]:blur-in-[2px] [&>*]:slide-in-from-top-1 [&>*]:animation-duration-(--animation-duration) [&>*]:ease-[cubic-bezier(0.32,0.72,0,1)]\",\n          \"[&>*]:motion-reduce:animate-none\",\n          \"[&>*:nth-child(2)]:[animation-delay:40ms]\",\n          \"[&>*:nth-child(3)]:[animation-delay:80ms]\",\n          \"[&>*:nth-child(4)]:[animation-delay:120ms]\",\n          \"[&>*:nth-child(n+5)]:[animation-delay:160ms]\",\n        )}\n      >\n        {children}\n      </div>\n    </CollapsibleContent>\n  );\n}\n\ntype ToolGroupComponent = FC<\n  PropsWithChildren<{ startIndex: number; endIndex: number }>\n> & {\n  Root: typeof ToolGroupRoot;\n  Trigger: typeof ToolGroupTrigger;\n  Content: typeof ToolGroupContent;\n};\n\nconst ToolGroupImpl: FC<\n  PropsWithChildren<{ startIndex: number; endIndex: number }>\n> = ({ children, startIndex, endIndex }) => {\n  const toolCount = endIndex - startIndex + 1;\n\n  return (\n    <ToolGroupRoot>\n      <ToolGroupTrigger count={toolCount} />\n      <ToolGroupContent>{children}</ToolGroupContent>\n    </ToolGroupRoot>\n  );\n};\n\n/**\n * @deprecated This wrapper targets the legacy `components.ToolGroup` prop\n * on `<MessagePrimitive.Parts>`. Use `<MessagePrimitive.GroupedParts>` with\n * a `groupBy` returning `\"group-tool\"` and compose `ToolGroupRoot` /\n * `ToolGroupTrigger` / `ToolGroupContent` directly. See `thread.tsx`.\n */\nconst ToolGroup = memo(ToolGroupImpl) as unknown as ToolGroupComponent;\n\nToolGroup.displayName = \"ToolGroup\";\nToolGroup.Root = ToolGroupRoot;\nToolGroup.Trigger = ToolGroupTrigger;\nToolGroup.Content = ToolGroupContent;\n\nexport {\n  ToolGroup,\n  ToolGroupRoot,\n  ToolGroupTrigger,\n  ToolGroupContent,\n  toolGroupVariants,\n};\n"
    },
    {
      "type": "registry:file",
      "path": "components/ui/avatar.tsx",
      "target": "components/ui/avatar.tsx",
      "sourcePath": "packages/ui/src/components/react/ui/radix/avatar.tsx",
      "content": "\"use client\";\n\nimport type * as React from \"react\";\nimport { Avatar as AvatarPrimitive } from \"radix-ui\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Avatar({\n  className,\n  size = \"default\",\n  ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Root> & {\n  size?: \"default\" | \"sm\" | \"lg\";\n}) {\n  return (\n    <AvatarPrimitive.Root\n      data-slot=\"avatar\"\n      data-size={size}\n      className={cn(\n        \"group/avatar after:border-border relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AvatarImage({\n  className,\n  ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Image>) {\n  return (\n    <AvatarPrimitive.Image\n      data-slot=\"avatar-image\"\n      className={cn(\n        \"aspect-square size-full rounded-full object-cover\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {\n  return (\n    <AvatarPrimitive.Fallback\n      data-slot=\"avatar-fallback\"\n      className={cn(\n        \"bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"avatar-badge\"\n      className={cn(\n        \"bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none\",\n        \"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden\",\n        \"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2\",\n        \"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"avatar-group\"\n      className={cn(\n        \"group/avatar-group *:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"avatar-group-count\"\n      className={cn(\n        \"bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarBadge,\n  AvatarGroup,\n  AvatarGroupCount,\n};\n"
    },
    {
      "type": "registry:file",
      "path": "components/ui/button.tsx",
      "target": "components/ui/button.tsx",
      "sourcePath": "packages/ui/src/components/react/ui/radix/button.tsx",
      "content": "import type * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Slot } from \"radix-ui\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n  \"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 inline-flex shrink-0 items-center justify-center gap-2 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 text-white\",\n        outline:\n          \"bg-muted/70 text-foreground hover:bg-muted dark:bg-muted/50 dark:hover:bg-muted border-transparent\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost:\n          \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n        xs: \"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5\",\n        lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n        icon: \"size-9\",\n        \"icon-xs\": \"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3\",\n        \"icon-sm\": \"size-8\",\n        \"icon-lg\": \"size-10\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  },\n);\n\nfunction Button({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> &\n  VariantProps<typeof buttonVariants> & {\n    asChild?: boolean;\n  }) {\n  const Comp = asChild ? Slot.Root : \"button\";\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
    },
    {
      "type": "registry:file",
      "path": "components/ui/collapsible.tsx",
      "target": "components/ui/collapsible.tsx",
      "sourcePath": "packages/ui/src/components/react/ui/radix/collapsible.tsx",
      "content": "\"use client\";\n\nimport { Collapsible as CollapsiblePrimitive } from \"radix-ui\";\n\nfunction Collapsible({\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {\n  return <CollapsiblePrimitive.Root data-slot=\"collapsible\" {...props} />;\n}\n\nfunction CollapsibleTrigger({\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {\n  return (\n    <CollapsiblePrimitive.CollapsibleTrigger\n      data-slot=\"collapsible-trigger\"\n      {...props}\n    />\n  );\n}\n\nfunction CollapsibleContent({\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {\n  return (\n    <CollapsiblePrimitive.CollapsibleContent\n      data-slot=\"collapsible-content\"\n      {...props}\n    />\n  );\n}\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent };\n"
    },
    {
      "type": "registry:file",
      "path": "components/ui/dialog.tsx",
      "target": "components/ui/dialog.tsx",
      "sourcePath": "packages/ui/src/components/react/ui/radix/dialog.tsx",
      "content": "\"use client\";\n\nimport type * as React from \"react\";\nimport { XIcon } from \"lucide-react\";\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\n\nfunction Dialog({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Root>) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Close>) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n  return (\n    <DialogPrimitive.Overlay\n      data-slot=\"dialog-overlay\"\n      className={cn(\n        \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=open]:animate-in fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal data-slot=\"dialog-portal\">\n      <DialogOverlay />\n      <DialogPrimitive.Content\n        data-slot=\"dialog-content\"\n        className={cn(\n          \"data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 bg-popover text-popover-foreground ring-foreground/10 data-[state=closed]:animate-out data-[state=open]:animate-in fixed start-[50%] top-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-xl p-5 text-sm ring-1 duration-100 outline-none sm:max-w-sm rtl:-translate-x-[-50%]\",\n          className,\n        )}\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            className=\"focus-visible:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute end-4 top-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:outline-hidden focus-visible:ring-1 disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Content>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"dialog-header\"\n      className={cn(\"flex flex-col gap-2\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      data-slot=\"dialog-footer\"\n      className={cn(\n        \"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close asChild>\n          <Button variant=\"outline\">Close</Button>\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Title>) {\n  return (\n    <DialogPrimitive.Title\n      data-slot=\"dialog-title\"\n      className={cn(\n        \"cn-font-heading text-base leading-none font-medium\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n  return (\n    <DialogPrimitive.Description\n      data-slot=\"dialog-description\"\n      className={cn(\"text-muted-foreground text-sm\", className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
    },
    {
      "type": "registry:file",
      "path": "components/ui/skeleton.tsx",
      "target": "components/ui/skeleton.tsx",
      "sourcePath": "packages/ui/src/components/react/ui/radix/skeleton.tsx",
      "content": "import { cn } from \"@/lib/utils\";\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"skeleton\"\n      className={cn(\"bg-muted animate-pulse rounded-md\", className)}\n      {...props}\n    />\n  );\n}\n\nexport { Skeleton };\n"
    },
    {
      "type": "registry:file",
      "path": "components/ui/textarea.tsx",
      "target": "components/ui/textarea.tsx",
      "sourcePath": "packages/ui/src/components/react/ui/radix/textarea.tsx",
      "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<\"textarea\">) {\n  return (\n    <textarea\n      data-slot=\"textarea\"\n      className={cn(\n        \"placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 disabled:bg-input/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 bg-muted/60 focus-visible:bg-background field-sizing-content min-h-16 w-full min-w-0 rounded-lg border border-transparent px-3 py-2 text-base transition-colors outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-1 md:text-sm\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
    },
    {
      "type": "registry:file",
      "path": "components/ui/tooltip.tsx",
      "target": "components/ui/tooltip.tsx",
      "sourcePath": "packages/ui/src/components/react/ui/radix/tooltip.tsx",
      "content": "\"use client\";\n\nimport type * as React from \"react\";\nimport { Tooltip as TooltipPrimitive } from \"radix-ui\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction TooltipProvider({\n  delayDuration = 0,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delayDuration={delayDuration}\n      {...props}\n    />\n  );\n}\n\nfunction Tooltip({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Root>) {\n  return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />;\n}\n\nfunction TooltipTrigger({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  sideOffset = 0,\n  children,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Content>) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Content\n        data-slot=\"tooltip-content\"\n        sideOffset={sideOffset}\n        className={cn(\n          \"fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 animate-in bg-foreground text-background data-[state=closed]:animate-out z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-[var(--radius-surface,var(--radius-lg))] px-3 py-1.5 text-xs has-data-[slot=kbd]:pr-1.5 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm\",\n          className,\n        )}\n        {...props}\n      >\n        {children}\n        <TooltipPrimitive.Arrow\n          data-slot=\"tooltip-arrow\"\n          className=\"bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]\"\n        />\n      </TooltipPrimitive.Content>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
    }
  ]
}