{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"message","type":"registry:component","title":"Message","description":"AI-powered message component.","files":[{"path":"registry/default/ai-elements/message.tsx","type":"registry:component","content":"\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  ButtonGroup,\n  ButtonGroupText,\n} from \"@/components/ui/button-group\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport type { FileUIPart, UIMessage } from \"ai\";\nimport {\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  PaperclipIcon,\n  XIcon,\n} from \"lucide-react\";\nimport type { ComponentProps, HTMLAttributes, ReactElement } from \"react\";\nimport { createContext, memo, useContext, useEffect, useState } from \"react\";\nimport { Streamdown } from \"streamdown\";\n\nexport type MessageProps = HTMLAttributes<HTMLDivElement> & {\n  from: UIMessage[\"role\"];\n};\n\nexport const Message = ({ className, from, ...props }: MessageProps) => (\n  <div\n    className={cn(\n      \"group flex w-full max-w-[95%] flex-col gap-2\",\n      from === \"user\" ? \"is-user ml-auto justify-end\" : \"is-assistant\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type MessageContentProps = HTMLAttributes<HTMLDivElement>;\n\nexport const MessageContent = ({\n  children,\n  className,\n  ...props\n}: MessageContentProps) => (\n  <div\n    className={cn(\n      \"is-user:dark flex w-fit max-w-full min-w-0 flex-col gap-2 overflow-hidden text-sm\",\n      \"group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground\",\n      \"group-[.is-assistant]:text-foreground\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n  </div>\n);\n\nexport type MessageActionsProps = ComponentProps<\"div\">;\n\nexport const MessageActions = ({\n  className,\n  children,\n  ...props\n}: MessageActionsProps) => (\n  <div className={cn(\"flex items-center gap-1\", className)} {...props}>\n    {children}\n  </div>\n);\n\nexport type MessageActionProps = ComponentProps<typeof Button> & {\n  tooltip?: string;\n  label?: string;\n};\n\nexport const MessageAction = ({\n  tooltip,\n  children,\n  label,\n  variant = \"ghost\",\n  size = \"icon-sm\",\n  ...props\n}: MessageActionProps) => {\n  const button = (\n    <Button size={size} type=\"button\" variant={variant} {...props}>\n      {children}\n      <span className=\"sr-only\">{label || tooltip}</span>\n    </Button>\n  );\n\n  if (tooltip) {\n    return (\n      <TooltipProvider>\n        <Tooltip>\n          <TooltipTrigger asChild>{button}</TooltipTrigger>\n          <TooltipContent>\n            <p>{tooltip}</p>\n          </TooltipContent>\n        </Tooltip>\n      </TooltipProvider>\n    );\n  }\n\n  return button;\n};\n\ntype MessageBranchContextType = {\n  currentBranch: number;\n  totalBranches: number;\n  goToPrevious: () => void;\n  goToNext: () => void;\n  branches: ReactElement[];\n  setBranches: (branches: ReactElement[]) => void;\n};\n\nconst MessageBranchContext = createContext<MessageBranchContextType | null>(\n  null\n);\n\nconst useMessageBranch = () => {\n  const context = useContext(MessageBranchContext);\n\n  if (!context) {\n    throw new Error(\n      \"MessageBranch components must be used within MessageBranch\"\n    );\n  }\n\n  return context;\n};\n\nexport type MessageBranchProps = HTMLAttributes<HTMLDivElement> & {\n  defaultBranch?: number;\n  onBranchChange?: (branchIndex: number) => void;\n};\n\nexport const MessageBranch = ({\n  defaultBranch = 0,\n  onBranchChange,\n  className,\n  ...props\n}: MessageBranchProps) => {\n  const [currentBranch, setCurrentBranch] = useState(defaultBranch);\n  const [branches, setBranches] = useState<ReactElement[]>([]);\n\n  const handleBranchChange = (newBranch: number) => {\n    setCurrentBranch(newBranch);\n    onBranchChange?.(newBranch);\n  };\n\n  const goToPrevious = () => {\n    const newBranch =\n      currentBranch > 0 ? currentBranch - 1 : branches.length - 1;\n    handleBranchChange(newBranch);\n  };\n\n  const goToNext = () => {\n    const newBranch =\n      currentBranch < branches.length - 1 ? currentBranch + 1 : 0;\n    handleBranchChange(newBranch);\n  };\n\n  const contextValue: MessageBranchContextType = {\n    currentBranch,\n    totalBranches: branches.length,\n    goToPrevious,\n    goToNext,\n    branches,\n    setBranches,\n  };\n\n  return (\n    <MessageBranchContext.Provider value={contextValue}>\n      <div\n        className={cn(\"grid w-full gap-2 [&>div]:pb-0\", className)}\n        {...props}\n      />\n    </MessageBranchContext.Provider>\n  );\n};\n\nexport type MessageBranchContentProps = HTMLAttributes<HTMLDivElement>;\n\nexport const MessageBranchContent = ({\n  children,\n  ...props\n}: MessageBranchContentProps) => {\n  const { currentBranch, setBranches, branches } = useMessageBranch();\n  const childrenArray = Array.isArray(children) ? children : [children];\n\n  // Use useEffect to update branches when they change\n  useEffect(() => {\n    if (branches.length !== childrenArray.length) {\n      setBranches(childrenArray);\n    }\n  }, [childrenArray, branches, setBranches]);\n\n  return childrenArray.map((branch, index) => (\n    <div\n      className={cn(\n        \"grid gap-2 overflow-hidden [&>div]:pb-0\",\n        index === currentBranch ? \"block\" : \"hidden\"\n      )}\n      key={branch.key}\n      {...props}\n    >\n      {branch}\n    </div>\n  ));\n};\n\nexport type MessageBranchSelectorProps = HTMLAttributes<HTMLDivElement> & {\n  from: UIMessage[\"role\"];\n};\n\nexport const MessageBranchSelector = ({\n  className,\n  from,\n  ...props\n}: MessageBranchSelectorProps) => {\n  const { totalBranches } = useMessageBranch();\n\n  // Don't render if there's only one branch\n  if (totalBranches <= 1) {\n    return null;\n  }\n\n  return (\n    <ButtonGroup\n      className=\"[&>*:not(:first-child)]:rounded-l-md [&>*:not(:last-child)]:rounded-r-md\"\n      orientation=\"horizontal\"\n      {...props}\n    />\n  );\n};\n\nexport type MessageBranchPreviousProps = ComponentProps<typeof Button>;\n\nexport const MessageBranchPrevious = ({\n  children,\n  ...props\n}: MessageBranchPreviousProps) => {\n  const { goToPrevious, totalBranches } = useMessageBranch();\n\n  return (\n    <Button\n      aria-label=\"Previous branch\"\n      disabled={totalBranches <= 1}\n      onClick={goToPrevious}\n      size=\"icon-sm\"\n      type=\"button\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children ?? <ChevronLeftIcon size={14} />}\n    </Button>\n  );\n};\n\nexport type MessageBranchNextProps = ComponentProps<typeof Button>;\n\nexport const MessageBranchNext = ({\n  children,\n  className,\n  ...props\n}: MessageBranchNextProps) => {\n  const { goToNext, totalBranches } = useMessageBranch();\n\n  return (\n    <Button\n      aria-label=\"Next branch\"\n      disabled={totalBranches <= 1}\n      onClick={goToNext}\n      size=\"icon-sm\"\n      type=\"button\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children ?? <ChevronRightIcon size={14} />}\n    </Button>\n  );\n};\n\nexport type MessageBranchPageProps = HTMLAttributes<HTMLSpanElement>;\n\nexport const MessageBranchPage = ({\n  className,\n  ...props\n}: MessageBranchPageProps) => {\n  const { currentBranch, totalBranches } = useMessageBranch();\n\n  return (\n    <ButtonGroupText\n      className={cn(\n        \"border-none bg-transparent text-muted-foreground shadow-none\",\n        className\n      )}\n      {...props}\n    >\n      {currentBranch + 1} of {totalBranches}\n    </ButtonGroupText>\n  );\n};\n\nexport type MessageResponseProps = ComponentProps<typeof Streamdown>;\n\nexport const MessageResponse = memo(\n  ({ className, ...props }: MessageResponseProps) => (\n    <Streamdown\n      className={cn(\n        \"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0\",\n        className\n      )}\n      {...props}\n    />\n  ),\n  (prevProps, nextProps) => prevProps.children === nextProps.children\n);\n\nMessageResponse.displayName = \"MessageResponse\";\n\nexport type MessageAttachmentProps = HTMLAttributes<HTMLDivElement> & {\n  data: FileUIPart;\n  className?: string;\n  onRemove?: () => void;\n};\n\nexport function MessageAttachment({\n  data,\n  className,\n  onRemove,\n  ...props\n}: MessageAttachmentProps) {\n  const filename = data.filename || \"\";\n  const mediaType =\n    data.mediaType?.startsWith(\"image/\") && data.url ? \"image\" : \"file\";\n  const isImage = mediaType === \"image\";\n  const attachmentLabel = filename || (isImage ? \"Image\" : \"Attachment\");\n\n  return (\n    <div\n      className={cn(\n        \"group relative size-24 overflow-hidden rounded-lg\",\n        className\n      )}\n      {...props}\n    >\n      {isImage ? (\n        <>\n          <img\n            alt={filename || \"attachment\"}\n            className=\"size-full object-cover\"\n            height={100}\n            src={data.url}\n            width={100}\n          />\n          {onRemove && (\n            <Button\n              aria-label=\"Remove attachment\"\n              className=\"absolute top-2 right-2 size-6 rounded-full bg-background/80 p-0 opacity-0 backdrop-blur-sm transition-opacity hover:bg-background group-hover:opacity-100 [&>svg]:size-3\"\n              onClick={(e) => {\n                e.stopPropagation();\n                onRemove();\n              }}\n              type=\"button\"\n              variant=\"ghost\"\n            >\n              <XIcon />\n              <span className=\"sr-only\">Remove</span>\n            </Button>\n          )}\n        </>\n      ) : (\n        <>\n          <Tooltip>\n            <TooltipTrigger asChild>\n              <div className=\"flex size-full shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground\">\n                <PaperclipIcon className=\"size-4\" />\n              </div>\n            </TooltipTrigger>\n            <TooltipContent>\n              <p>{attachmentLabel}</p>\n            </TooltipContent>\n          </Tooltip>\n          {onRemove && (\n            <Button\n              aria-label=\"Remove attachment\"\n              className=\"size-6 shrink-0 rounded-full p-0 opacity-0 transition-opacity hover:bg-accent group-hover:opacity-100 [&>svg]:size-3\"\n              onClick={(e) => {\n                e.stopPropagation();\n                onRemove();\n              }}\n              type=\"button\"\n              variant=\"ghost\"\n            >\n              <XIcon />\n              <span className=\"sr-only\">Remove</span>\n            </Button>\n          )}\n        </>\n      )}\n    </div>\n  );\n}\n\nexport type MessageAttachmentsProps = ComponentProps<\"div\">;\n\nexport function MessageAttachments({\n  children,\n  className,\n  ...props\n}: MessageAttachmentsProps) {\n  if (!children) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\n        \"ml-auto flex w-fit flex-wrap items-start gap-2\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n}\n\nexport type MessageToolbarProps = ComponentProps<\"div\">;\n\nexport const MessageToolbar = ({\n  className,\n  children,\n  ...props\n}: MessageToolbarProps) => (\n  <div\n    className={cn(\n      \"mt-4 flex w-full items-center justify-between gap-4\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n  </div>\n);\n","target":"components/ai-elements/message.tsx"}],"dependencies":["ai","lucide-react","streamdown"],"devDependencies":[],"registryDependencies":["button","button-group","tooltip"]}