{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "thread",
  "type": "registry:component",
  "title": "Thread",
  "description": "Chat container with message list, composer, auto scroll, and accessibility built in.",
  "dependencies": [
    "@assistant-ui/react@^0.15.23",
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "skeleton",
    "https://r.assistant-ui.com/attachment.json",
    "https://r.assistant-ui.com/file.json",
    "https://r.assistant-ui.com/follow-up-suggestions.json",
    "https://r.assistant-ui.com/image.json",
    "https://r.assistant-ui.com/markdown-text.json",
    "https://r.assistant-ui.com/reasoning.json",
    "https://r.assistant-ui.com/tooltip-icon-button.json",
    "https://r.assistant-ui.com/tool-fallback.json",
    "https://r.assistant-ui.com/tool-group.json"
  ],
  "files": [
    {
      "type": "registry:component",
      "path": "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 TextMessagePartComponent,\n  type ToolCallMessagePartComponent,\n  useAuiState,\n} from \"@assistant-ui/react\";\nimport {\n  ArrowDownIcon,\n  ArrowUpIcon,\n  AudioLinesIcon,\n  CheckIcon,\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  CopyIcon,\n  DownloadIcon,\n  MicIcon,\n  MoreHorizontalIcon,\n  PencilIcon,\n  PhoneIcon,\n  RefreshCwIcon,\n  SquareIcon,\n  ThumbsDownIcon,\n  ThumbsUpIcon,\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`. When `TaskGroup` is set, tool calls that carry a nested\n * conversation and have no registered UI render through it instead of the\n * tool group; without it they render like any other tool call.\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  TaskGroup?: ComponentType<{ group: ThreadGroupPart }> | undefined;\n};\n\nconst messageGroupBy = groupPartByType({\n  reasoning: [\"group-chainOfThought\", \"group-reasoning\"],\n  \"tool-call\": [\"group-chainOfThought\", \"group-tool\"],\n  \"standalone-tool-call\": [],\n});\n\ntype ThreadGroupKey =\n  | \"group-chainOfThought\"\n  | \"group-reasoning\"\n  | \"group-tool\"\n  | \"group-task\";\n\nconst TASK_GROUP_PATH: readonly ThreadGroupKey[] = [\n  \"group-chainOfThought\",\n  \"group-task\",\n];\n\nconst taskAwareGroupBy = (\n  part: Parameters<typeof messageGroupBy>[0],\n  context?: Parameters<typeof messageGroupBy>[1],\n): readonly ThreadGroupKey[] => {\n  const path = messageGroupBy(part, context);\n  return part.type === \"tool-call\" &&\n    part.messages !== undefined &&\n    path.length > 0 &&\n    !context?.toolUIs?.[part.toolName]?.length\n    ? TASK_GROUP_PATH\n    : path;\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]:\n          \"color-mix(in oklab, var(--color-muted) 30%, transparent)\",\n        [\"--composer-radius\" as string]: \"1rem\",\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  const isSpoken = useAuiState((s) => s.message.metadata.modality === \"voice\");\n\n  if (isEditing) return <EditComposer />;\n  if (isSpoken) return <SpokenMessage />;\n  if (role === \"user\") return <UserMessage />;\n  return <AssistantMessageComponent />;\n};\n\ntype VoiceRunPosition = \"single\" | \"start\" | \"middle\" | \"end\";\n\nconst useVoiceRunPosition = (): VoiceRunPosition =>\n  useAuiState((s) => {\n    const before =\n      s.thread.messages[s.message.index - 1]?.metadata.modality === \"voice\";\n    const after =\n      s.thread.messages[s.message.index + 1]?.metadata.modality === \"voice\";\n    if (before) return after ? \"middle\" : \"end\";\n    return after ? \"start\" : \"single\";\n  });\n\nconst SpokenText: TextMessagePartComponent = ({ text }) => (\n  <p className=\"aui-spoken-message-text m-0\">{text}</p>\n);\n\nconst SpokenMessage: FC = () => {\n  const role = useAuiState((s) => s.message.role);\n  const position = useVoiceRunPosition();\n  const isSpeaking = useAuiState(\n    (s) =>\n      s.message.role === \"assistant\" && s.message.status?.type === \"running\",\n  );\n  const opensExchange = position === \"start\" || position === \"single\";\n\n  return (\n    <MessagePrimitive.Root\n      data-slot=\"aui_spoken-message-root\"\n      data-role={role}\n      data-voice-run={position}\n      className={cn(\n        \"aui-spoken-message bg-muted/40 mx-2 px-3 py-1.5 [contain-intrinsic-size:auto_48px] [content-visibility:auto]\",\n        position === \"single\" && \"rounded-xl py-2\",\n        position === \"start\" && \"rounded-t-xl pt-2\",\n        position === \"middle\" && \"-mt-6\",\n        position === \"end\" && \"-mt-6 rounded-b-xl pb-2\",\n      )}\n    >\n      {opensExchange && (\n        <div\n          data-slot=\"aui_spoken-exchange-header\"\n          className=\"text-muted-foreground mb-1.5 flex items-center gap-1.5 text-xs\"\n        >\n          <PhoneIcon className=\"size-3\" aria-hidden />\n          <span>Voice conversation</span>\n        </div>\n      )}\n      <div\n        data-slot=\"aui_spoken-message-content\"\n        className=\"text-foreground flex items-start gap-2 text-sm leading-relaxed\"\n      >\n        <span className=\"text-muted-foreground mt-1 shrink-0\" aria-hidden>\n          {role === \"user\" ? (\n            <MicIcon className=\"size-3.5\" />\n          ) : (\n            <AudioLinesIcon className=\"size-3.5\" />\n          )}\n        </span>\n        <span className=\"sr-only\">\n          {role === \"user\" ? \"You said\" : \"Assistant said\"}\n        </span>\n        <div className=\"min-w-0 flex-1 wrap-break-word\">\n          <MessagePrimitive.Parts components={{ Text: SpokenText }} />\n          {isSpeaking && (\n            <span\n              data-slot=\"aui_spoken-message-indicator\"\n              role=\"status\"\n              className=\"text-muted-foreground ms-1 animate-pulse font-sans\"\n              aria-label=\"Assistant is speaking\"\n            >\n              ●\n            </span>\n          )}\n        </div>\n        <SpokenActionBar />\n      </div>\n    </MessagePrimitive.Root>\n  );\n};\n\nconst SpokenActionBar: FC = () => {\n  return (\n    <ActionBarPrimitive.Root\n      hideWhenRunning\n      autohide=\"always\"\n      className=\"aui-spoken-action-bar text-muted-foreground flex shrink-0 gap-1\"\n    >\n      <ActionBarPrimitive.Copy asChild>\n        <TooltipIconButton tooltip=\"Copy\" className=\"size-6\">\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.Root>\n  );\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 px-2\">\n      <p 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      </p>\n    </div>\n  );\n};\n\nconst ThreadSuggestions: FC = () => {\n  return (\n    <div className=\"aui-thread-welcome-suggestions flex w-full flex-col\">\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          type=\"button\"\n          className=\"aui-thread-welcome-suggestion group hover:bg-foreground/[0.03] focus-visible:ring-ring/50 flex w-full items-baseline gap-2.5 rounded-md px-2 py-2 text-start text-sm transition-colors outline-none focus-visible:ring-1 motion-reduce:transition-none\"\n        >\n          <span\n            aria-hidden\n            className=\"text-muted-foreground/60 group-hover:text-foreground font-mono text-xs transition-colors motion-reduce:transition-none\"\n          >\n            {\">\"}\n          </span>\n          <span className=\"min-w-0 flex-1 truncate\">\n            <SuggestionPrimitive.Title className=\"aui-thread-welcome-suggestion-text-1 text-foreground\" />{\" \"}\n            <SuggestionPrimitive.Description className=\"aui-thread-welcome-suggestion-text-2 text-muted-foreground empty:hidden\" />\n          </span>\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-foreground/10 focus-within:border-foreground/25 data-[dragging=true]:border-ring 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  // The stop control only cancels the send while no run it could stop is going.\n  const isSending = useAuiState(\n    (s) =>\n      s.composer.submission !== undefined &&\n      !(s.thread.isRunning && s.thread.capabilities.cancel),\n  );\n\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\n          condition={(s) =>\n            !s.composer.canCancel ||\n            (s.thread.voice !== undefined &&\n              s.composer.submission === undefined)\n          }\n        >\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\n          condition={(s) =>\n            s.composer.canCancel &&\n            (s.thread.voice === undefined ||\n              s.composer.submission !== undefined)\n          }\n        >\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={isSending ? \"Cancel sending\" : \"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    TaskGroup: TaskGroupComponent,\n  } = useContext(ThreadComponentsContext);\n  const groupBy = TaskGroupComponent ? taskAwareGroupBy : messageGroupBy;\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 groupBy={groupBy}>\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-task\":\n                return TaskGroupComponent ? (\n                  <TaskGroupComponent group={part} />\n                ) : null;\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      <AuiIf condition={(s) => s.thread.capabilities.feedback}>\n        <ActionBarPrimitive.FeedbackPositive asChild>\n          <TooltipIconButton\n            tooltip=\"Helpful\"\n            className=\"data-[submitted=true]:bg-accent data-[submitted=true]:text-accent-foreground\"\n          >\n            <ThumbsUpIcon />\n          </TooltipIconButton>\n        </ActionBarPrimitive.FeedbackPositive>\n        <ActionBarPrimitive.FeedbackNegative asChild>\n          <TooltipIconButton\n            tooltip=\"Not helpful\"\n            className=\"data-[submitted=true]:bg-accent data-[submitted=true]:text-accent-foreground\"\n          >\n            <ThumbsDownIcon />\n          </TooltipIconButton>\n        </ActionBarPrimitive.FeedbackNegative>\n      </AuiIf>\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-(--composer-radius) 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 -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-foreground/10 focus-within:border-foreground/25 ms-auto flex w-full max-w-[85%] cursor-text flex-col rounded-(--composer-radius) border bg-(--composer-bg) transition-[border-color]\">\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 variant=\"ghost\" size=\"sm\" className=\"h-8 px-3\">\n              Cancel\n            </Button>\n          </ComposerPrimitive.Cancel>\n          <ComposerPrimitive.Send asChild>\n            <Button size=\"sm\" className=\"h-8 px-3\">\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"
    }
  ]
}