{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chat-message",
  "type": "registry:component",
  "title": "Chat Message",
  "description": "A chat message component compatible with the Vercel AI SDK including markdown support, copying, etc.",
  "dependencies": [
    "@radix-ui/react-slot",
    "class-variance-authority",
    "lucide-react",
    "react-markdown",
    "remark-gfm",
    "react-syntax-highlighter",
    "ai"
  ],
  "registryDependencies": [
    "accordion"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/chat-message/chat-message.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { VariantProps, cva } from \"class-variance-authority\";\nimport Markdown from \"@/registry/new-york/blocks/markdown/markdown\";\nimport React from \"react\";\nimport { ToolUIPart, UIMessage } from \"ai\";\nimport ChatReasoning from \"@/registry/new-york/blocks/chat-reasoning/chat-reasoning\";\nimport ChatTool from \"@/registry/new-york/blocks/chat-tool/chat-tool\";\n\nconst userMessageVariants = cva(\"flex flex-col gap-2\", {\n  variants: {\n    variant: {\n      raised:\n        \"bg-gradient-to-b from-primary to-primary/80 border border-primary text-primary-foreground shadow-lg shadow-shade max-w-2xs ml-auto rounded-xl px-4 py-2 font-medium\",\n      title: \"font-semibold tracking-tight text-3xl mt-4 border-b border-primary/30 pb-1\",\n      default: \"bg-primary/20 max-w-2xs ml-auto rounded-xl px-4 py-2\",\n    },\n  },\n});\nconst assistantMessageVariants = cva(\"flex flex-col gap-2\", {\n  variants: {\n    variant: {\n      raised:\n        \"bg-gradient-to-b from-muted to-muted/50 rounded-xl px-4 py-2 border shadow-md shadow-shade mr-8\",\n      paragraph: \"\",\n      default: \"bg-muted rounded-xl px-4 py-2 mr-8\",\n    },\n  },\n});\n\n// Helper function to render a single message part\nconst renderMessagePart = (part: any, key: string | number) => {\n  if (part.type.includes(\"tool\")) {\n    return (\n      <ChatTool\n        toolMessagePart={part as ToolUIPart}\n        className=\"my-1 border-none px-0 py-0 shadow-none text-muted-foreground\"\n      />\n    );\n  } else if (part.type === \"text\") {\n    return <Markdown key={key}>{part.text}</Markdown>;\n  } else if (part.type === \"reasoning\") {\n    return (\n      <Markdown key={key} className=\"text-sm text-muted-foreground\">\n        {part.text}\n      </Markdown>\n    );\n  }\n  return <div key={key}>{part.type}</div>;\n};\n\nexport default function ChatMessage({\n  message,\n  className,\n  userMessageVariant = \"default\",\n  assistantMessageVariant = \"default\",\n}: {\n  message: UIMessage;\n  className?: string;\n  userMessageVariant?: VariantProps<typeof userMessageVariants>[\"variant\"];\n  assistantMessageVariant?: VariantProps<typeof assistantMessageVariants>[\"variant\"];\n}) {\n  if (message.role === \"user\") {\n    return (\n      <div\n        className={cn(userMessageVariants({ variant: userMessageVariant }), className)}\n        role=\"user\"\n      >\n        {message.parts.map((part) => part.type === \"text\" && part.text).join(\"\")}\n      </div>\n    );\n  }\n\n  const firstTextIndex = message.parts.findIndex((part) => part.type === \"text\");\n  const hasTextPart = firstTextIndex !== -1;\n\n  const shouldShowAccordion = firstTextIndex !== 0;\n  const accordionDefaultValue = !hasTextPart ? \"reasoning\" : null;\n  const partsInAccordion = shouldShowAccordion ? message.parts.slice(0, firstTextIndex) : [];\n  const partsAfter = hasTextPart ? message.parts.slice(firstTextIndex) : [];\n\n  return (\n    <div className={cn(\"flex items-start gap-3\", className)}>\n      <div\n        className={cn(\n          \"flex flex-col gap-1 relative\",\n          assistantMessageVariants({ variant: assistantMessageVariant })\n        )}\n      >\n        {shouldShowAccordion && (\n          <ChatReasoning\n            renderMessagePart={renderMessagePart}\n            partsInAccordion={partsInAccordion}\n            defaultValue={accordionDefaultValue ?? undefined}\n          />\n        )}\n        {partsAfter.map((part, index) => renderMessagePart(part, firstTextIndex + index))}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "registry/new-york/blocks/button/button.tsx",
      "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n  \"inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-xl text-md font-medium disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive duration-200 hover:duration-75 transition-all\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"border border-primary bg-primary/70 bg-linear-to-t from-primary to-primary/30 px-4 py-2 font-medium text-primary-foreground shadow-md inset-shadow-[0px_1px_0px_1px] inset-shadow-white/30 transition-all duration-75 hover:bg-primary/60 active:translate-y-px active:inset-shadow-black/10 dark:bg-primary dark:from-primary dark:to-primary/80 dark:hover:bg-primary/90\",\n        fancy:\n          \"border border-primary bg-primary/30 bg-radial-[at_50%_25%] from-primary/40 to-primary text-primary-foreground shadow-md inset-shadow-sm inset-shadow-white/50 hover:bg-white hover:shadow-lg hover:inset-shadow-white active:bg-primary/30 active:shadow-none active:inset-shadow-primary/80  hover:-translate-y-px active:translate-y-px\",\n        destructive:\n          \"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60\",\n        outline:\n          \"border shadow-lg hover:shadow-none shadow-shade bg-transparent text-primary hover:border-transparent hover:bg-primary/10 active:bg-primary/30\",\n        secondary:\n          \"border border-transparent bg-primary/10 text-primary hover:border-primary/50 hover:bg-transparent  active:border-transparent active:border-primary active:ring-1 active:scale-95\",\n        ghost:\n          \"bg-transparent text-primary hover:border-transparent hover:bg-primary/10 active:bg-primary/30\",\n        link: \"text-foreground underline decoration-primary underline-offset-2 hover:underline-offset-2 active:decoration-4 hover:opacity-70 hover:decoration-2 active:text-foreground\",\n      },\n      size: {\n        default: \"px-4 py-2\",\n        xs: \"px-2 py-1.5 rounded-lg [&_svg:not([class*='size-'])]:size-3 text-xs\",\n        sm: \"px-3 py-1.5 text-sm rounded-lg\",\n        lg: \"px-5 py-2 text-lg\",\n        xl: \"px-6 py-3 [&_svg:not([class*='size-'])]:size-5 text-xl\",\n        icon: \"size-8 rounded-lg [&_svg:not([class*='size-'])]:size-4\",\n        iconSm: \"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3\",\n        iconLg: \"size-10 rounded-lg [&_svg:not([class*='size-'])]:size-5\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant,\n  size,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> &\n  VariantProps<typeof buttonVariants> & {\n    asChild?: boolean;\n  }) {\n  const Comp = asChild ? Slot : \"button\";\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n",
      "type": "registry:component",
      "target": "components/ui/button.tsx"
    },
    {
      "path": "registry/new-york/blocks/chat-reasoning/chat-reasoning.tsx",
      "content": "import {\n  Accordion,\n  AccordionContent,\n  AccordionItem,\n  AccordionTrigger,\n} from \"@/components/ui/accordion\";\nimport { cn } from \"@/lib/utils\";\nimport { UIDataTypes, UIMessagePart, UITools } from \"ai\";\nimport React from \"react\";\n\nexport default function ChatReasoning({\n  partsInAccordion,\n  defaultValue,\n  renderMessagePart,\n  className,\n}: {\n  partsInAccordion: UIMessagePart<UIDataTypes, UITools>[];\n  defaultValue?: string;\n  renderMessagePart: (\n    part: UIMessagePart<UIDataTypes, UITools>,\n    key: string | number\n  ) => React.ReactNode;\n  className?: string;\n}) {\n  const [value, setValue] = React.useState<string | undefined>(defaultValue);\n\n  React.useEffect(() => {\n    setValue(defaultValue);\n  }, [defaultValue]);\n\n  return (\n    <Accordion\n      type=\"single\"\n      collapsible\n      value={value}\n      onValueChange={setValue}\n      className={cn(\"w-full\", className)}\n    >\n      <AccordionItem value=\"reasoning\" className=\"w-full\">\n        <AccordionTrigger className=\"text-md text-muted-foreground hover:no-underline hover:opacity-70 py-2 w-full\">\n          {defaultValue === \"reasoning\" ? \"Reasoning...\" : `Done reasoning.`}\n        </AccordionTrigger>\n        <AccordionContent className=\"p-0 -mt-1\">\n          <div className=\"flex flex-col gap-0\">\n            {partsInAccordion.map(\n              (part, index) =>\n                part.type !== \"step-start\" && (\n                  <div key={index} className=\"flex gap-2 pl-2\">\n                    <div className=\"flex flex-col items-center gap-1 pt-2 -mb-1\">\n                      <div className=\"w-2 h-2 bg-muted-foreground/50 rounded-full\" />\n                      <div\n                        className={cn(\n                          \"w-0.5 min-h-0 flex-1 bg-border rounded-full\",\n                          index === partsInAccordion.length - 1 &&\n                            \"bg-gradient-to-b from-border to-transparent\"\n                        )}\n                      />\n                    </div>\n                    <div className=\"flex-1\">{renderMessagePart(part, `accordion-${index}`)}</div>\n                  </div>\n                )\n            )}\n          </div>\n        </AccordionContent>\n      </AccordionItem>\n    </Accordion>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/chat/chat-reasoning.tsx"
    },
    {
      "path": "registry/new-york/blocks/markdown/markdown.tsx",
      "content": "import ReactMarkdown, { Components } from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport { cn } from \"@/lib/utils\";\nimport { CodeBlock } from \"@/registry/new-york/blocks/code-block/code-block\";\nimport { Button } from \"@/registry/new-york/blocks/button/button\";\n\nconst components: Components = {\n  // Text components\n  p: ({ children }) => <p className=\"text-base mb-3 last:mb-0\">{children}</p>,\n  h1: ({ children }) => (\n    <h1 className=\"text-4xl font-semibold tracking-tight mb-4 mt-2\">{children}</h1>\n  ),\n  h2: ({ children }) => (\n    <h2 className=\"text-3xl font-semibold tracking-tight mt-4 mb-2 border-b border-border pb-2\">\n      {children}\n    </h2>\n  ),\n  h3: ({ children }) => (\n    <h3 className=\"text-2xl font-semibold tracking-tight mt-4 mb-2\">{children}</h3>\n  ),\n  h4: ({ children }) => (\n    <h4 className=\"text-xl font-semibold tracking-tight mt-3 mb-2\">{children}</h4>\n  ),\n  h5: ({ children }) => (\n    <h5 className=\"text-lg font-semibold tracking-tight mt-2 mb-1\">{children}</h5>\n  ),\n  h6: ({ children }) => (\n    <h6 className=\"text-base font-semibold tracking-tight mt-2 mb-1\">{children}</h6>\n  ),\n\n  // Lists\n  ul: ({ children }) => (\n    <ul className=\"text-base my-2 pl-6 list-disc marker:text-primary/70\">{children}</ul>\n  ),\n  ol: ({ children }) => <ol className=\"text-base my-2 pl-6 list-decimal\">{children}</ol>,\n  li: ({ children }) => <li className=\"my-0.5\">{children}</li>,\n\n  // Inline formatting\n  strong: ({ children }) => <strong className=\"font-bold\">{children}</strong>,\n  em: ({ children }) => <em className=\"italic\">{children}</em>,\n  del: ({ children }) => <del className=\"line-through\">{children}</del>,\n\n  // Block elements\n  blockquote: ({ children }) => (\n    <blockquote className=\"pl-4 border-l-4 border-primary/20 my-2 italic text-primary/70\">\n      {children}\n    </blockquote>\n  ),\n  hr: () => <hr className=\"my-4 border-t border-border\" />,\n\n  // Tables\n  table: ({ children }) => (\n    <div className=\"overflow-x-auto my-4\">\n      <table className=\"min-w-full divide-y divide-border border border-border\">{children}</table>\n    </div>\n  ),\n  thead: ({ children }) => <thead className=\"bg-muted/50\">{children}</thead>,\n  tbody: ({ children }) => <tbody className=\"divide-y divide-border\">{children}</tbody>,\n  tr: ({ children }) => <tr>{children}</tr>,\n  th: ({ children }) => (\n    <th className=\"px-3 py-2 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n      {children}\n    </th>\n  ),\n  td: ({ children }) => <td className=\"px-3 py-2 whitespace-nowrap text-sm\">{children}</td>,\n\n  // Links and images\n  a: ({ children, href, title }) => (\n    <Button\n      variant=\"link\"\n      size=\"sm\"\n      className=\"text-primary hover:underline p-0 m-0\"\n      title={title}\n      onClick={() => {\n        if (href) {\n          window.open(href, \"_blank\", \"noopener noreferrer\");\n        }\n      }}\n      disabled={!href}\n      asChild\n    >\n      {children}\n    </Button>\n  ),\n  img: ({ src, alt, title }) => (\n    <div className=\"flex flex-col items-center w-fit\">\n      <img\n        src={src || \"\"}\n        alt={alt || \"\"}\n        title={title || alt || \"\"}\n        className=\"max-w-sm h-auto my-2 rounded-xl\"\n      />\n      <p className=\"text-sm text-center text-muted-foreground\">{alt}</p>\n    </div>\n  ),\n\n  // Code blocks and inline code\n  code: ({ className, children }) => {\n    const match = /language-(\\w+)/.exec(className || \"\");\n    const isInline = !match;\n    return !isInline && match ? (\n      <CodeBlock code={String(children).replace(/\\n$/, \"\")} language={match[1]} />\n    ) : (\n      <code className=\"bg-muted px-1.5 py-0.5 rounded-md text-sm text-primary font-mono\">\n        {children}\n      </code>\n    );\n  },\n  pre: ({ children }) => <>{children}</>,\n};\n\nexport default function Markdown({\n  children,\n  className,\n}: {\n  children: string;\n  className?: string;\n}) {\n  return (\n    <div className={cn(\"w-full\", className)}>\n      <ReactMarkdown remarkPlugins={[remarkGfm]} components={components}>\n        {children}\n      </ReactMarkdown>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/ui/markdown.tsx"
    },
    {
      "path": "registry/new-york/blocks/code-block/code-block.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport React, { useEffect, useState } from \"react\";\nimport { Button } from \"@/registry/new-york/blocks/button/button\";\nimport { Check, Copy } from \"lucide-react\";\nimport { Prism as SyntaxHighlighter } from \"react-syntax-highlighter\";\nimport { oneLight, a11yDark as oneDark } from \"react-syntax-highlighter/dist/esm/styles/prism\";\nimport { cva } from \"class-variance-authority\";\nimport { useTheme } from \"next-themes\";\n\nconst customSyntaxTheme = {\n  ...oneLight,\n  'pre[class*=\"language-\"]': {\n    ...oneLight['pre[class*=\"language-\"]'],\n    background: \"hsl(var(--muted))\",\n    borderRadius: \"0.5rem\",\n    fontFamily: \"Geist Mono, monospace\",\n    textShadow: \"none\",\n    padding: 0,\n    margin: 0,\n  },\n  'code[class*=\"language-\"]': {\n    ...oneLight['code[class*=\"language-\"]'],\n    background: \"none\",\n    fontFamily: \"Geist Mono, monospace\",\n    textShadow: \"none\",\n    fontSize: \"13px\",\n    padding: 0,\n  },\n};\n\nconst customDarkSyntaxTheme = {\n  ...oneDark,\n  'pre[class*=\"language-\"]': {\n    ...oneDark['pre[class*=\"language-\"]'],\n    background: \"hsl(var(--muted))\",\n    borderRadius: \"0.5rem\",\n    fontFamily: \"Geist Mono, monospace\",\n    textShadow: \"none\",\n    padding: 0,\n    margin: 0,\n  },\n  'code[class*=\"language-\"]': {\n    ...oneDark['code[class*=\"language-\"]'],\n    background: \"none\",\n    fontFamily: \"Geist Mono, monospace\",\n    textShadow: \"none\",\n    fontSize: \"13px\",\n    padding: 0,\n  },\n};\n\nconst codeBlockVariants = cva(\"text-card-foreground rounded-xl p-4 flex flex-col gap-2\", {\n  variants: {\n    variant: {\n      flat: \"bg-muted\",\n      default: \"border border-border bg-card\",\n    },\n  },\n  defaultVariants: {\n    variant: \"default\",\n  },\n});\n\nexport type CodeBlockProps = {\n  code: string;\n  language?: string;\n  theme?: string;\n  className?: string;\n  title?: string;\n  children?: React.ReactNode;\n  variant?: \"default\" | \"flat\";\n} & React.HTMLProps<HTMLDivElement>;\n\nexport function CodeBlock({\n  code,\n  language = \"tsx\",\n  theme = \"github-light\",\n  className,\n  title,\n  children,\n  variant = \"default\",\n  ...props\n}: CodeBlockProps) {\n  const { theme: themeName } = useTheme();\n  const codeClassNames = cn(\"w-full overflow-x-auto text-[13px] font-mono\");\n  const [copied, setCopied] = useState(false);\n  return (\n    <div className={cn(\"relative group\", codeBlockVariants({ variant }), className)} {...props}>\n      <Button\n        onClick={() => {\n          navigator.clipboard.writeText(String(code));\n          setCopied(true);\n          setTimeout(() => {\n            setCopied(false);\n          }, 1000);\n        }}\n        variant=\"ghost\"\n        size=\"icon\"\n        className=\"absolute right-2 top-2 focus-visible:ring-0 text-muted-foreground hover:bg-muted active:bg-muted\"\n        aria-label={copied ? \"Copied\" : \"Copy to clipboard\"}\n      >\n        <span className=\"sr-only\">{copied ? \"Copied\" : \"Copy\"}</span>\n        <Copy\n          className={`size-4 transition-all duration-300 ${copied ? \"scale-0\" : \"scale-100\"}`}\n        />\n        <Check\n          className={`absolute inset-0 m-auto size-4 transition-all duration-300 text-success ${\n            copied ? \"scale-100\" : \"scale-0\"\n          }`}\n        />\n      </Button>\n      {title && <h3 className=\"text-sm text-muted-foreground tracking-tight font-mono\">{title}</h3>}\n      <SyntaxHighlighter\n        language={language}\n        style={themeName === \"dark\" ? customDarkSyntaxTheme : customSyntaxTheme}\n        PreTag=\"div\"\n        className={codeClassNames}\n      >\n        {code}\n      </SyntaxHighlighter>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/ui/code-block.tsx"
    }
  ]
}