{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "prompt-kit",
  "homepage": "https://prompt-kit.com",
  "items": [
    {
      "name": "prompt-input",
      "type": "registry:ui",
      "title": "Prompt Input",
      "description": "An input field designed for chat interfaces, allowing users to enter and submit text prompts to an AI model",
      "dependencies": [],
      "devDependencies": [],
      "registryDependencies": [
        "textarea",
        "tooltip"
      ],
      "files": [
        {
          "path": "components/prompt-kit/prompt-input.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { Textarea } from \"@/components/ui/textarea\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\nimport { cn } from \"@/lib/utils\"\nimport React, {\n  createContext,\n  useContext,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from \"react\"\n\ntype PromptInputContextType = {\n  isLoading: boolean\n  value: string\n  setValue: (value: string) => void\n  maxHeight: number | string\n  onSubmit?: () => void\n  disabled?: boolean\n  textareaRef: React.RefObject<HTMLTextAreaElement | null>\n}\n\nconst PromptInputContext = createContext<PromptInputContextType>({\n  isLoading: false,\n  value: \"\",\n  setValue: () => {},\n  maxHeight: 240,\n  onSubmit: undefined,\n  disabled: false,\n  textareaRef: React.createRef<HTMLTextAreaElement>(),\n})\n\nfunction usePromptInput() {\n  return useContext(PromptInputContext)\n}\n\nexport type PromptInputProps = {\n  isLoading?: boolean\n  value?: string\n  onValueChange?: (value: string) => void\n  maxHeight?: number | string\n  onSubmit?: () => void\n  children: React.ReactNode\n  className?: string\n  disabled?: boolean\n} & React.ComponentProps<\"div\">\n\nfunction PromptInput({\n  className,\n  isLoading = false,\n  maxHeight = 240,\n  value,\n  onValueChange,\n  onSubmit,\n  children,\n  disabled = false,\n  onClick,\n  ...props\n}: PromptInputProps) {\n  const [internalValue, setInternalValue] = useState(value || \"\")\n  const textareaRef = useRef<HTMLTextAreaElement>(null)\n\n  const handleChange = (newValue: string) => {\n    setInternalValue(newValue)\n    onValueChange?.(newValue)\n  }\n\n  const handleClick: React.MouseEventHandler<HTMLDivElement> = (e) => {\n    if (!disabled) textareaRef.current?.focus()\n    onClick?.(e)\n  }\n\n  return (\n    <TooltipProvider>\n      <PromptInputContext.Provider\n        value={{\n          isLoading,\n          value: value ?? internalValue,\n          setValue: onValueChange ?? handleChange,\n          maxHeight,\n          onSubmit,\n          disabled,\n          textareaRef,\n        }}\n      >\n        <div\n          onClick={handleClick}\n          className={cn(\n            \"border-input bg-background cursor-text rounded-3xl border p-2 shadow-xs\",\n            disabled && \"cursor-not-allowed opacity-60\",\n            className\n          )}\n          {...props}\n        >\n          {children}\n        </div>\n      </PromptInputContext.Provider>\n    </TooltipProvider>\n  )\n}\n\nexport type PromptInputTextareaProps = {\n  disableAutosize?: boolean\n} & React.ComponentProps<typeof Textarea>\n\nfunction PromptInputTextarea({\n  className,\n  onKeyDown,\n  disableAutosize = false,\n  ...props\n}: PromptInputTextareaProps) {\n  const { value, setValue, maxHeight, onSubmit, disabled, textareaRef } =\n    usePromptInput()\n\n  const adjustHeight = (el: HTMLTextAreaElement | null) => {\n    if (!el || disableAutosize) return\n\n    el.style.height = \"auto\"\n\n    if (typeof maxHeight === \"number\") {\n      el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`\n    } else {\n      el.style.height = `min(${el.scrollHeight}px, ${maxHeight})`\n    }\n  }\n\n  const handleRef = (el: HTMLTextAreaElement | null) => {\n    textareaRef.current = el\n    adjustHeight(el)\n  }\n\n  useLayoutEffect(() => {\n    if (!textareaRef.current || disableAutosize) return\n\n    const el = textareaRef.current\n    el.style.height = \"auto\"\n\n    if (typeof maxHeight === \"number\") {\n      el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`\n    } else {\n      el.style.height = `min(${el.scrollHeight}px, ${maxHeight})`\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [value, maxHeight, disableAutosize])\n\n  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n    adjustHeight(e.target)\n    setValue(e.target.value)\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n    if (e.key === \"Enter\" && !e.shiftKey) {\n      e.preventDefault()\n      onSubmit?.()\n    }\n    onKeyDown?.(e)\n  }\n\n  return (\n    <Textarea\n      ref={handleRef}\n      value={value}\n      onChange={handleChange}\n      onKeyDown={handleKeyDown}\n      className={cn(\n        \"text-primary min-h-[44px] w-full resize-none border-none bg-transparent shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0\",\n        className\n      )}\n      rows={1}\n      disabled={disabled}\n      {...props}\n    />\n  )\n}\n\nexport type PromptInputActionsProps = React.HTMLAttributes<HTMLDivElement>\n\nfunction PromptInputActions({\n  children,\n  className,\n  ...props\n}: PromptInputActionsProps) {\n  return (\n    <div className={cn(\"flex items-center gap-2\", className)} {...props}>\n      {children}\n    </div>\n  )\n}\n\nexport type PromptInputActionProps = {\n  className?: string\n  tooltip: React.ReactNode\n  children: React.ReactNode\n  side?: \"top\" | \"bottom\" | \"left\" | \"right\"\n} & React.ComponentProps<typeof Tooltip>\n\nfunction PromptInputAction({\n  tooltip,\n  children,\n  className,\n  side = \"top\",\n  ...props\n}: PromptInputActionProps) {\n  const { disabled } = usePromptInput()\n\n  return (\n    <Tooltip {...props}>\n      <TooltipTrigger\n        asChild\n        disabled={disabled}\n        onClick={(event) => event.stopPropagation()}\n      >\n        {children}\n      </TooltipTrigger>\n      <TooltipContent side={side} className={className}>\n        {tooltip}\n      </TooltipContent>\n    </Tooltip>\n  )\n}\n\nexport {\n  PromptInput,\n  PromptInputTextarea,\n  PromptInputActions,\n  PromptInputAction,\n}\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "code-block",
      "type": "registry:ui",
      "title": "Code Block",
      "description": "A component for displaying code snippets with syntax highlighting and customizable styling",
      "dependencies": [
        "shiki"
      ],
      "devDependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "components/prompt-kit/code-block.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport React, { useEffect, useState } from \"react\"\nimport { codeToHtml } from \"shiki\"\n\nexport type CodeBlockProps = {\n  children?: React.ReactNode\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nfunction CodeBlock({ children, className, ...props }: CodeBlockProps) {\n  return (\n    <div\n      className={cn(\n        \"not-prose flex w-full flex-col overflow-clip border\",\n        \"border-border bg-card text-card-foreground rounded-xl\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\nexport type CodeBlockCodeProps = {\n  code: string\n  language?: string\n  theme?: string\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nfunction CodeBlockCode({\n  code,\n  language = \"tsx\",\n  theme = \"github-light\",\n  className,\n  ...props\n}: CodeBlockCodeProps) {\n  const [highlightedHtml, setHighlightedHtml] = useState<string | null>(null)\n\n  useEffect(() => {\n    async function highlight() {\n      if (!code) {\n        setHighlightedHtml(\"<pre><code></code></pre>\")\n        return\n      }\n\n      const html = await codeToHtml(code, { lang: language, theme })\n      setHighlightedHtml(html)\n    }\n    highlight()\n  }, [code, language, theme])\n\n  const classNames = cn(\n    \"w-full overflow-x-auto text-[13px] [&>pre]:px-4 [&>pre]:py-4\",\n    className\n  )\n\n  // SSR fallback: render plain code if not hydrated yet\n  return highlightedHtml ? (\n    <div\n      className={classNames}\n      dangerouslySetInnerHTML={{ __html: highlightedHtml }}\n      {...props}\n    />\n  ) : (\n    <div className={classNames} {...props}>\n      <pre>\n        <code>{code}</code>\n      </pre>\n    </div>\n  )\n}\n\nexport type CodeBlockGroupProps = React.HTMLAttributes<HTMLDivElement>\n\nfunction CodeBlockGroup({\n  children,\n  className,\n  ...props\n}: CodeBlockGroupProps) {\n  return (\n    <div\n      className={cn(\"flex items-center justify-between\", className)}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\nexport { CodeBlockGroup, CodeBlockCode, CodeBlock }\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "markdown",
      "type": "registry:ui",
      "title": "Markdown",
      "description": "A component for rendering Markdown content with support for code blocks, GFM, and custom styling",
      "dependencies": [
        "react-markdown",
        "remark-gfm",
        "shiki",
        "marked",
        "remark-breaks"
      ],
      "devDependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "components/prompt-kit/markdown.tsx",
          "type": "registry:component",
          "content": "import { cn } from \"@/lib/utils\"\nimport { marked } from \"marked\"\nimport { memo, useId, useMemo } from \"react\"\nimport ReactMarkdown, { Components } from \"react-markdown\"\nimport remarkBreaks from \"remark-breaks\"\nimport remarkGfm from \"remark-gfm\"\nimport { CodeBlock, CodeBlockCode } from \"./code-block\"\n\nexport type MarkdownProps = {\n  children: string\n  id?: string\n  className?: string\n  components?: Partial<Components>\n}\n\nfunction parseMarkdownIntoBlocks(markdown: string): string[] {\n  const tokens = marked.lexer(markdown)\n  return tokens.map((token) => token.raw)\n}\n\nfunction extractLanguage(className?: string): string {\n  if (!className) return \"plaintext\"\n  const match = className.match(/language-(\\w+)/)\n  return match ? match[1] : \"plaintext\"\n}\n\nconst INITIAL_COMPONENTS: Partial<Components> = {\n  code: function CodeComponent({ className, children, ...props }) {\n    const isInline =\n      !props.node?.position?.start.line ||\n      props.node?.position?.start.line === props.node?.position?.end.line\n\n    if (isInline) {\n      return (\n        <span\n          className={cn(\n            \"bg-primary-foreground rounded-sm px-1 font-mono text-sm\",\n            className\n          )}\n          {...props}\n        >\n          {children}\n        </span>\n      )\n    }\n\n    const language = extractLanguage(className)\n\n    return (\n      <CodeBlock className={className}>\n        <CodeBlockCode code={children as string} language={language} />\n      </CodeBlock>\n    )\n  },\n  pre: function PreComponent({ children }) {\n    return <>{children}</>\n  },\n}\n\nconst MemoizedMarkdownBlock = memo(\n  function MarkdownBlock({\n    content,\n    components = INITIAL_COMPONENTS,\n  }: {\n    content: string\n    components?: Partial<Components>\n  }) {\n    return (\n      <ReactMarkdown\n        remarkPlugins={[remarkGfm, remarkBreaks]}\n        components={components}\n      >\n        {content}\n      </ReactMarkdown>\n    )\n  },\n  function propsAreEqual(prevProps, nextProps) {\n    return prevProps.content === nextProps.content\n  }\n)\n\nMemoizedMarkdownBlock.displayName = \"MemoizedMarkdownBlock\"\n\nfunction MarkdownComponent({\n  children,\n  id,\n  className,\n  components = INITIAL_COMPONENTS,\n}: MarkdownProps) {\n  const generatedId = useId()\n  const blockId = id ?? generatedId\n  const blocks = useMemo(() => parseMarkdownIntoBlocks(children), [children])\n\n  return (\n    <div className={className}>\n      {blocks.map((block, index) => (\n        <MemoizedMarkdownBlock\n          key={`${blockId}-block-${index}`}\n          content={block}\n          components={components}\n        />\n      ))}\n    </div>\n  )\n}\n\nconst Markdown = memo(MarkdownComponent)\nMarkdown.displayName = \"Markdown\"\n\nexport { Markdown }\n"
        },
        {
          "path": "components/prompt-kit/code-block.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport React, { useEffect, useState } from \"react\"\nimport { codeToHtml } from \"shiki\"\n\nexport type CodeBlockProps = {\n  children?: React.ReactNode\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nfunction CodeBlock({ children, className, ...props }: CodeBlockProps) {\n  return (\n    <div\n      className={cn(\n        \"not-prose flex w-full flex-col overflow-clip border\",\n        \"border-border bg-card text-card-foreground rounded-xl\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\nexport type CodeBlockCodeProps = {\n  code: string\n  language?: string\n  theme?: string\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nfunction CodeBlockCode({\n  code,\n  language = \"tsx\",\n  theme = \"github-light\",\n  className,\n  ...props\n}: CodeBlockCodeProps) {\n  const [highlightedHtml, setHighlightedHtml] = useState<string | null>(null)\n\n  useEffect(() => {\n    async function highlight() {\n      if (!code) {\n        setHighlightedHtml(\"<pre><code></code></pre>\")\n        return\n      }\n\n      const html = await codeToHtml(code, { lang: language, theme })\n      setHighlightedHtml(html)\n    }\n    highlight()\n  }, [code, language, theme])\n\n  const classNames = cn(\n    \"w-full overflow-x-auto text-[13px] [&>pre]:px-4 [&>pre]:py-4\",\n    className\n  )\n\n  // SSR fallback: render plain code if not hydrated yet\n  return highlightedHtml ? (\n    <div\n      className={classNames}\n      dangerouslySetInnerHTML={{ __html: highlightedHtml }}\n      {...props}\n    />\n  ) : (\n    <div className={classNames} {...props}>\n      <pre>\n        <code>{code}</code>\n      </pre>\n    </div>\n  )\n}\n\nexport type CodeBlockGroupProps = React.HTMLAttributes<HTMLDivElement>\n\nfunction CodeBlockGroup({\n  children,\n  className,\n  ...props\n}: CodeBlockGroupProps) {\n  return (\n    <div\n      className={cn(\"flex items-center justify-between\", className)}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\nexport { CodeBlockGroup, CodeBlockCode, CodeBlock }\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "message",
      "type": "registry:ui",
      "title": "Message",
      "description": "A component for displaying chat messages with support for avatars, markdown content, and interactive actions",
      "dependencies": [
        "react-markdown",
        "remark-gfm",
        "shiki",
        "marked",
        "remark-breaks"
      ],
      "devDependencies": [],
      "registryDependencies": [
        "avatar",
        "tooltip"
      ],
      "files": [
        {
          "path": "components/prompt-kit/message.tsx",
          "type": "registry:component",
          "content": "import { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\nimport { cn } from \"@/lib/utils\"\nimport { Markdown } from \"./markdown\"\n\nexport type MessageProps = {\n  children: React.ReactNode\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nconst Message = ({ children, className, ...props }: MessageProps) => (\n  <div className={cn(\"flex gap-3\", className)} {...props}>\n    {children}\n  </div>\n)\n\nexport type MessageAvatarProps = {\n  src: string\n  alt: string\n  fallback?: string\n  delayMs?: number\n  className?: string\n}\n\nconst MessageAvatar = ({\n  src,\n  alt,\n  fallback,\n  delayMs,\n  className,\n}: MessageAvatarProps) => {\n  return (\n    <Avatar className={cn(\"h-8 w-8 shrink-0\", className)}>\n      <AvatarImage src={src} alt={alt} />\n      {fallback && (\n        <AvatarFallback delayMs={delayMs}>{fallback}</AvatarFallback>\n      )}\n    </Avatar>\n  )\n}\n\nexport type MessageContentProps = {\n  children: React.ReactNode\n  markdown?: boolean\n  className?: string\n} & React.ComponentProps<typeof Markdown> &\n  React.HTMLProps<HTMLDivElement>\n\nconst MessageContent = ({\n  children,\n  markdown = false,\n  className,\n  ...props\n}: MessageContentProps) => {\n  const classNames = cn(\n    \"rounded-lg p-2 text-foreground bg-secondary prose break-words whitespace-normal\",\n    className\n  )\n\n  return markdown ? (\n    <Markdown className={classNames} {...props}>\n      {children as string}\n    </Markdown>\n  ) : (\n    <div className={classNames} {...props}>\n      {children}\n    </div>\n  )\n}\n\nexport type MessageActionsProps = {\n  children: React.ReactNode\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nconst MessageActions = ({\n  children,\n  className,\n  ...props\n}: MessageActionsProps) => (\n  <div\n    className={cn(\"text-muted-foreground flex items-center gap-2\", className)}\n    {...props}\n  >\n    {children}\n  </div>\n)\n\nexport type MessageActionProps = {\n  className?: string\n  tooltip: React.ReactNode\n  children: React.ReactNode\n  side?: \"top\" | \"bottom\" | \"left\" | \"right\"\n} & React.ComponentProps<typeof Tooltip>\n\nconst MessageAction = ({\n  tooltip,\n  children,\n  className,\n  side = \"top\",\n  ...props\n}: MessageActionProps) => {\n  return (\n    <TooltipProvider>\n      <Tooltip {...props}>\n        <TooltipTrigger asChild>{children}</TooltipTrigger>\n        <TooltipContent side={side} className={className}>\n          {tooltip}\n        </TooltipContent>\n      </Tooltip>\n    </TooltipProvider>\n  )\n}\n\nexport { Message, MessageAvatar, MessageContent, MessageActions, MessageAction }\n"
        },
        {
          "path": "components/prompt-kit/markdown.tsx",
          "type": "registry:component",
          "content": "import { cn } from \"@/lib/utils\"\nimport { marked } from \"marked\"\nimport { memo, useId, useMemo } from \"react\"\nimport ReactMarkdown, { Components } from \"react-markdown\"\nimport remarkBreaks from \"remark-breaks\"\nimport remarkGfm from \"remark-gfm\"\nimport { CodeBlock, CodeBlockCode } from \"./code-block\"\n\nexport type MarkdownProps = {\n  children: string\n  id?: string\n  className?: string\n  components?: Partial<Components>\n}\n\nfunction parseMarkdownIntoBlocks(markdown: string): string[] {\n  const tokens = marked.lexer(markdown)\n  return tokens.map((token) => token.raw)\n}\n\nfunction extractLanguage(className?: string): string {\n  if (!className) return \"plaintext\"\n  const match = className.match(/language-(\\w+)/)\n  return match ? match[1] : \"plaintext\"\n}\n\nconst INITIAL_COMPONENTS: Partial<Components> = {\n  code: function CodeComponent({ className, children, ...props }) {\n    const isInline =\n      !props.node?.position?.start.line ||\n      props.node?.position?.start.line === props.node?.position?.end.line\n\n    if (isInline) {\n      return (\n        <span\n          className={cn(\n            \"bg-primary-foreground rounded-sm px-1 font-mono text-sm\",\n            className\n          )}\n          {...props}\n        >\n          {children}\n        </span>\n      )\n    }\n\n    const language = extractLanguage(className)\n\n    return (\n      <CodeBlock className={className}>\n        <CodeBlockCode code={children as string} language={language} />\n      </CodeBlock>\n    )\n  },\n  pre: function PreComponent({ children }) {\n    return <>{children}</>\n  },\n}\n\nconst MemoizedMarkdownBlock = memo(\n  function MarkdownBlock({\n    content,\n    components = INITIAL_COMPONENTS,\n  }: {\n    content: string\n    components?: Partial<Components>\n  }) {\n    return (\n      <ReactMarkdown\n        remarkPlugins={[remarkGfm, remarkBreaks]}\n        components={components}\n      >\n        {content}\n      </ReactMarkdown>\n    )\n  },\n  function propsAreEqual(prevProps, nextProps) {\n    return prevProps.content === nextProps.content\n  }\n)\n\nMemoizedMarkdownBlock.displayName = \"MemoizedMarkdownBlock\"\n\nfunction MarkdownComponent({\n  children,\n  id,\n  className,\n  components = INITIAL_COMPONENTS,\n}: MarkdownProps) {\n  const generatedId = useId()\n  const blockId = id ?? generatedId\n  const blocks = useMemo(() => parseMarkdownIntoBlocks(children), [children])\n\n  return (\n    <div className={className}>\n      {blocks.map((block, index) => (\n        <MemoizedMarkdownBlock\n          key={`${blockId}-block-${index}`}\n          content={block}\n          components={components}\n        />\n      ))}\n    </div>\n  )\n}\n\nconst Markdown = memo(MarkdownComponent)\nMarkdown.displayName = \"Markdown\"\n\nexport { Markdown }\n"
        },
        {
          "path": "components/prompt-kit/code-block.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport React, { useEffect, useState } from \"react\"\nimport { codeToHtml } from \"shiki\"\n\nexport type CodeBlockProps = {\n  children?: React.ReactNode\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nfunction CodeBlock({ children, className, ...props }: CodeBlockProps) {\n  return (\n    <div\n      className={cn(\n        \"not-prose flex w-full flex-col overflow-clip border\",\n        \"border-border bg-card text-card-foreground rounded-xl\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\nexport type CodeBlockCodeProps = {\n  code: string\n  language?: string\n  theme?: string\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nfunction CodeBlockCode({\n  code,\n  language = \"tsx\",\n  theme = \"github-light\",\n  className,\n  ...props\n}: CodeBlockCodeProps) {\n  const [highlightedHtml, setHighlightedHtml] = useState<string | null>(null)\n\n  useEffect(() => {\n    async function highlight() {\n      if (!code) {\n        setHighlightedHtml(\"<pre><code></code></pre>\")\n        return\n      }\n\n      const html = await codeToHtml(code, { lang: language, theme })\n      setHighlightedHtml(html)\n    }\n    highlight()\n  }, [code, language, theme])\n\n  const classNames = cn(\n    \"w-full overflow-x-auto text-[13px] [&>pre]:px-4 [&>pre]:py-4\",\n    className\n  )\n\n  // SSR fallback: render plain code if not hydrated yet\n  return highlightedHtml ? (\n    <div\n      className={classNames}\n      dangerouslySetInnerHTML={{ __html: highlightedHtml }}\n      {...props}\n    />\n  ) : (\n    <div className={classNames} {...props}>\n      <pre>\n        <code>{code}</code>\n      </pre>\n    </div>\n  )\n}\n\nexport type CodeBlockGroupProps = React.HTMLAttributes<HTMLDivElement>\n\nfunction CodeBlockGroup({\n  children,\n  className,\n  ...props\n}: CodeBlockGroupProps) {\n  return (\n    <div\n      className={cn(\"flex items-center justify-between\", className)}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\nexport { CodeBlockGroup, CodeBlockCode, CodeBlock }\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "chat-container",
      "type": "registry:ui",
      "title": "Chat Container",
      "description": "A component for creating chat interfaces with intelligent auto-scrolling behavior, designed to provide a smooth and responsive user experience",
      "dependencies": [
        "use-stick-to-bottom"
      ],
      "devDependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "components/prompt-kit/chat-container.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport { StickToBottom } from \"use-stick-to-bottom\"\n\nexport type ChatContainerRootProps = {\n  children: React.ReactNode\n  className?: string\n} & React.HTMLAttributes<HTMLDivElement>\n\nexport type ChatContainerContentProps = {\n  children: React.ReactNode\n  className?: string\n} & React.HTMLAttributes<HTMLDivElement>\n\nexport type ChatContainerScrollAnchorProps = {\n  className?: string\n  ref?: React.RefObject<HTMLDivElement>\n} & React.HTMLAttributes<HTMLDivElement>\n\nfunction ChatContainerRoot({\n  children,\n  className,\n  ...props\n}: ChatContainerRootProps) {\n  return (\n    <StickToBottom\n      className={cn(\"flex overflow-y-auto\", className)}\n      resize=\"smooth\"\n      initial=\"instant\"\n      role=\"log\"\n      {...props}\n    >\n      {children}\n    </StickToBottom>\n  )\n}\n\nfunction ChatContainerContent({\n  children,\n  className,\n  ...props\n}: ChatContainerContentProps) {\n  return (\n    <StickToBottom.Content\n      className={cn(\"flex w-full flex-col\", className)}\n      {...props}\n    >\n      {children}\n    </StickToBottom.Content>\n  )\n}\n\nfunction ChatContainerScrollAnchor({\n  className,\n  ...props\n}: ChatContainerScrollAnchorProps) {\n  return (\n    <div\n      className={cn(\"h-px w-full shrink-0 scroll-mt-4\", className)}\n      aria-hidden=\"true\"\n      {...props}\n    />\n  )\n}\n\nexport { ChatContainerRoot, ChatContainerContent, ChatContainerScrollAnchor }\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "scroll-button",
      "type": "registry:ui",
      "title": "Scroll Button",
      "description": "A floating button component that appears when users scroll up in a container, allowing them to quickly return to the bottom of the content",
      "dependencies": [
        "class-variance-authority",
        "lucide-react"
      ],
      "devDependencies": [],
      "registryDependencies": [
        "button"
      ],
      "files": [
        {
          "path": "components/prompt-kit/scroll-button.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { Button, buttonVariants } from \"@/components/ui/button\"\nimport { cn } from \"@/lib/utils\"\nimport { type VariantProps } from \"class-variance-authority\"\nimport { ChevronDown } from \"lucide-react\"\nimport { useStickToBottomContext } from \"use-stick-to-bottom\"\n\nexport type ScrollButtonProps = {\n  className?: string\n  variant?: VariantProps<typeof buttonVariants>[\"variant\"]\n  size?: VariantProps<typeof buttonVariants>[\"size\"]\n} & React.ButtonHTMLAttributes<HTMLButtonElement>\n\nfunction ScrollButton({\n  className,\n  variant = \"outline\",\n  size = \"sm\",\n  ...props\n}: ScrollButtonProps) {\n  const { isAtBottom, scrollToBottom } = useStickToBottomContext()\n\n  return (\n    <Button\n      variant={variant}\n      size={size}\n      className={cn(\n        \"h-10 w-10 rounded-full transition-all duration-150 ease-out\",\n        !isAtBottom\n          ? \"translate-y-0 scale-100 opacity-100\"\n          : \"pointer-events-none translate-y-4 scale-95 opacity-0\",\n        className\n      )}\n      onClick={() => scrollToBottom()}\n      {...props}\n    >\n      <ChevronDown className=\"h-5 w-5\" />\n    </Button>\n  )\n}\n\nexport { ScrollButton }\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "loader",
      "type": "registry:ui",
      "title": "Loader",
      "description": "A component for displaying a loading indicator with multiple variants and customizable styling",
      "dependencies": [],
      "devDependencies": [],
      "registryDependencies": [
        "button"
      ],
      "tailwind": {
        "config": {
          "theme": {
            "keyframes": {
              "typing": {
                "0%, 100%": {
                  "transform": "translateY(0)",
                  "opacity": "0.5"
                },
                "50%": {
                  "transform": "translateY(-2px)",
                  "opacity": "1"
                }
              },
              "loading-dots": {
                "0%, 100%": {
                  "opacity": "0"
                },
                "50%": {
                  "opacity": "1"
                }
              },
              "wave": {
                "0%, 100%": {
                  "transform": "scaleY(1)"
                },
                "50%": {
                  "transform": "scaleY(0.6)"
                }
              },
              "blink": {
                "0%, 100%": {
                  "opacity": "1"
                },
                "50%": {
                  "opacity": "0"
                }
              }
            },
            "text-blink": {
              "0%, 100%": {
                "color": "var(--primary)"
              },
              "50%": {
                "color": "var(--muted-foreground)"
              }
            },
            "bounce-dots": {
              "0%, 100%": {
                "transform": "scale(0.8)",
                "opacity": "0.5"
              },
              "50%": {
                "transform": "scale(1.2)",
                "opacity": "1"
              }
            },
            "thin-pulse": {
              "0%, 100%": {
                "transform": "scale(0.95)",
                "opacity": "0.8"
              },
              "50%": {
                "transform": "scale(1.05)",
                "opacity": "0.4"
              }
            },
            "pulse-dot": {
              "0%, 100%": {
                "transform": "scale(1)",
                "opacity": "0.8"
              },
              "50%": {
                "transform": "scale(1.5)",
                "opacity": "1"
              }
            },
            "shimmer-text": {
              "0%": {
                "backgroundPosition": "150% center"
              },
              "100%": {
                "backgroundPosition": "-150% center"
              }
            },
            "wave-bars": {
              "0%, 100%": {
                "transform": "scaleY(1)",
                "opacity": "0.5"
              },
              "50%": {
                "transform": "scaleY(0.6)",
                "opacity": "1"
              }
            },
            "shimmer": {
              "0%": {
                "backgroundPosition": "200% 50%"
              },
              "100%": {
                "backgroundPosition": "-200% 50%"
              }
            },
            "spinner-fade": {
              "0%": {
                "opacity": "0"
              },
              "100%": {
                "opacity": "1"
              }
            }
          }
        }
      },
      "files": [
        {
          "path": "components/prompt-kit/loader.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport React from \"react\"\n\nexport interface LoaderProps {\n  variant?:\n    | \"circular\"\n    | \"classic\"\n    | \"pulse\"\n    | \"pulse-dot\"\n    | \"dots\"\n    | \"typing\"\n    | \"wave\"\n    | \"bars\"\n    | \"terminal\"\n    | \"text-blink\"\n    | \"text-shimmer\"\n    | \"loading-dots\"\n  size?: \"sm\" | \"md\" | \"lg\"\n  text?: string\n  className?: string\n}\n\nexport function CircularLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const sizeClasses = {\n    sm: \"size-4\",\n    md: \"size-5\",\n    lg: \"size-6\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"border-primary animate-spin rounded-full border-2 border-t-transparent\",\n        sizeClasses[size],\n        className\n      )}\n    >\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function ClassicLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const sizeClasses = {\n    sm: \"size-4\",\n    md: \"size-5\",\n    lg: \"size-6\",\n  }\n\n  const barSizes = {\n    sm: { height: \"6px\", width: \"1.5px\" },\n    md: { height: \"8px\", width: \"2px\" },\n    lg: { height: \"10px\", width: \"2.5px\" },\n  }\n\n  return (\n    <div className={cn(\"relative\", sizeClasses[size], className)}>\n      <div className=\"absolute h-full w-full\">\n        {[...Array(12)].map((_, i) => (\n          <div\n            key={i}\n            className=\"bg-primary absolute animate-[spinner-fade_1.2s_linear_infinite] rounded-full\"\n            style={{\n              top: \"0\",\n              left: \"50%\",\n              marginLeft:\n                size === \"sm\" ? \"-0.75px\" : size === \"lg\" ? \"-1.25px\" : \"-1px\",\n              transformOrigin: `${size === \"sm\" ? \"0.75px\" : size === \"lg\" ? \"1.25px\" : \"1px\"} ${size === \"sm\" ? \"10px\" : size === \"lg\" ? \"14px\" : \"12px\"}`,\n              transform: `rotate(${i * 30}deg)`,\n              opacity: 0,\n              animationDelay: `${i * 0.1}s`,\n              height: barSizes[size].height,\n              width: barSizes[size].width,\n            }}\n          />\n        ))}\n      </div>\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function PulseLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const sizeClasses = {\n    sm: \"size-4\",\n    md: \"size-5\",\n    lg: \"size-6\",\n  }\n\n  return (\n    <div className={cn(\"relative\", sizeClasses[size], className)}>\n      <div className=\"border-primary absolute inset-0 animate-[thin-pulse_1.5s_ease-in-out_infinite] rounded-full border-2\" />\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function PulseDotLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const sizeClasses = {\n    sm: \"size-1\",\n    md: \"size-2\",\n    lg: \"size-3\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"bg-primary animate-[pulse-dot_1.2s_ease-in-out_infinite] rounded-full\",\n        sizeClasses[size],\n        className\n      )}\n    >\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function DotsLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const dotSizes = {\n    sm: \"h-1.5 w-1.5\",\n    md: \"h-2 w-2\",\n    lg: \"h-2.5 w-2.5\",\n  }\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center space-x-1\",\n        containerSizes[size],\n        className\n      )}\n    >\n      {[...Array(3)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\n            \"bg-primary animate-[bounce-dots_1.4s_ease-in-out_infinite] rounded-full\",\n            dotSizes[size]\n          )}\n          style={{\n            animationDelay: `${i * 160}ms`,\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function TypingLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const dotSizes = {\n    sm: \"h-1 w-1\",\n    md: \"h-1.5 w-1.5\",\n    lg: \"h-2 w-2\",\n  }\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center space-x-1\",\n        containerSizes[size],\n        className\n      )}\n    >\n      {[...Array(3)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\n            \"bg-primary animate-[typing_1s_infinite] rounded-full\",\n            dotSizes[size]\n          )}\n          style={{\n            animationDelay: `${i * 250}ms`,\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function WaveLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const barWidths = {\n    sm: \"w-0.5\",\n    md: \"w-0.5\",\n    lg: \"w-1\",\n  }\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  }\n\n  const heights = {\n    sm: [\"6px\", \"9px\", \"12px\", \"9px\", \"6px\"],\n    md: [\"8px\", \"12px\", \"16px\", \"12px\", \"8px\"],\n    lg: [\"10px\", \"15px\", \"20px\", \"15px\", \"10px\"],\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center gap-0.5\",\n        containerSizes[size],\n        className\n      )}\n    >\n      {[...Array(5)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\n            \"bg-primary animate-[wave_1s_ease-in-out_infinite] rounded-full\",\n            barWidths[size]\n          )}\n          style={{\n            animationDelay: `${i * 100}ms`,\n            height: heights[size][i],\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function BarsLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const barWidths = {\n    sm: \"w-1\",\n    md: \"w-1.5\",\n    lg: \"w-2\",\n  }\n\n  const containerSizes = {\n    sm: \"h-4 gap-1\",\n    md: \"h-5 gap-1.5\",\n    lg: \"h-6 gap-2\",\n  }\n\n  return (\n    <div className={cn(\"flex\", containerSizes[size], className)}>\n      {[...Array(3)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\n            \"bg-primary h-full animate-[wave-bars_1.2s_ease-in-out_infinite]\",\n            barWidths[size]\n          )}\n          style={{\n            animationDelay: `${i * 0.2}s`,\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function TerminalLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const cursorSizes = {\n    sm: \"h-3 w-1.5\",\n    md: \"h-4 w-2\",\n    lg: \"h-5 w-2.5\",\n  }\n\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  }\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center space-x-1\",\n        containerSizes[size],\n        className\n      )}\n    >\n      <span className={cn(\"text-primary font-mono\", textSizes[size])}>\n        {\">\"}\n      </span>\n      <div\n        className={cn(\n          \"bg-primary animate-[blink_1s_step-end_infinite]\",\n          cursorSizes[size]\n        )}\n      />\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function TextBlinkLoader({\n  text = \"Thinking\",\n  className,\n  size = \"md\",\n}: {\n  text?: string\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"animate-[text-blink_2s_ease-in-out_infinite] font-medium\",\n        textSizes[size],\n        className\n      )}\n    >\n      {text}\n    </div>\n  )\n}\n\nexport function TextShimmerLoader({\n  text = \"Thinking\",\n  className,\n  size = \"md\",\n}: {\n  text?: string\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"bg-[linear-gradient(to_right,var(--muted-foreground)_40%,var(--foreground)_60%,var(--muted-foreground)_80%)]\",\n        \"bg-size-[200%_auto] bg-clip-text font-medium text-transparent\",\n        \"animate-[shimmer_4s_infinite_linear]\",\n        textSizes[size],\n        className\n      )}\n    >\n      {text}\n    </div>\n  )\n}\n\nexport function TextDotsLoader({\n  className,\n  text = \"Thinking\",\n  size = \"md\",\n}: {\n  className?: string\n  text?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  }\n\n  return (\n    <div\n      className={cn(\"inline-flex items-center\", className)}\n    >\n      <span className={cn(\"text-primary font-medium\", textSizes[size])}>\n        {text}\n      </span>\n      <span className=\"inline-flex\">\n        <span className=\"text-primary animate-[loading-dots_1.4s_infinite_0.2s]\">\n          .\n        </span>\n        <span className=\"text-primary animate-[loading-dots_1.4s_infinite_0.4s]\">\n          .\n        </span>\n        <span className=\"text-primary animate-[loading-dots_1.4s_infinite_0.6s]\">\n          .\n        </span>\n      </span>\n    </div>\n  )\n}\n\nfunction Loader({\n  variant = \"circular\",\n  size = \"md\",\n  text,\n  className,\n}: LoaderProps) {\n  switch (variant) {\n    case \"circular\":\n      return <CircularLoader size={size} className={className} />\n    case \"classic\":\n      return <ClassicLoader size={size} className={className} />\n    case \"pulse\":\n      return <PulseLoader size={size} className={className} />\n    case \"pulse-dot\":\n      return <PulseDotLoader size={size} className={className} />\n    case \"dots\":\n      return <DotsLoader size={size} className={className} />\n    case \"typing\":\n      return <TypingLoader size={size} className={className} />\n    case \"wave\":\n      return <WaveLoader size={size} className={className} />\n    case \"bars\":\n      return <BarsLoader size={size} className={className} />\n    case \"terminal\":\n      return <TerminalLoader size={size} className={className} />\n    case \"text-blink\":\n      return <TextBlinkLoader text={text} size={size} className={className} />\n    case \"text-shimmer\":\n      return <TextShimmerLoader text={text} size={size} className={className} />\n    case \"loading-dots\":\n      return <TextDotsLoader text={text} size={size} className={className} />\n    default:\n      return <CircularLoader size={size} className={className} />\n  }\n}\n\nexport { Loader }\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "prompt-suggestion",
      "type": "registry:ui",
      "title": "Prompt Suggestion",
      "description": "A component for implementing interactive prompt suggestions in AI interfaces. The PromptSuggestion component offers two distinct modes: Normal Mode and Highlight Mode.",
      "dependencies": [
        "class-variance-authority",
        "lucide-react"
      ],
      "devDependencies": [],
      "registryDependencies": [
        "button"
      ],
      "files": [
        {
          "path": "components/prompt-kit/prompt-suggestion.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { Button, buttonVariants } from \"@/components/ui/button\"\nimport { cn } from \"@/lib/utils\"\nimport { VariantProps } from \"class-variance-authority\"\n\nexport type PromptSuggestionProps = {\n  children: React.ReactNode\n  variant?: VariantProps<typeof buttonVariants>[\"variant\"]\n  size?: VariantProps<typeof buttonVariants>[\"size\"]\n  className?: string\n  highlight?: string\n} & React.ButtonHTMLAttributes<HTMLButtonElement>\n\nfunction PromptSuggestion({\n  children,\n  variant,\n  size,\n  className,\n  highlight,\n  ...props\n}: PromptSuggestionProps) {\n  const isHighlightMode = highlight !== undefined && highlight.trim() !== \"\"\n  const content = typeof children === \"string\" ? children : \"\"\n\n  if (!isHighlightMode) {\n    return (\n      <Button\n        variant={variant || \"outline\"}\n        size={size || \"lg\"}\n        className={cn(\"rounded-full\", className)}\n        {...props}\n      >\n        {children}\n      </Button>\n    )\n  }\n\n  if (!content) {\n    return (\n      <Button\n        variant={variant || \"ghost\"}\n        size={size || \"sm\"}\n        className={cn(\n          \"w-full cursor-pointer justify-start rounded-xl py-2\",\n          \"hover:bg-accent\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </Button>\n    )\n  }\n\n  const trimmedHighlight = highlight.trim()\n  const contentLower = content.toLowerCase()\n  const highlightLower = trimmedHighlight.toLowerCase()\n  const shouldHighlight = contentLower.includes(highlightLower)\n\n  return (\n    <Button\n      variant={variant || \"ghost\"}\n      size={size || \"sm\"}\n      className={cn(\n        \"w-full cursor-pointer justify-start gap-0 rounded-xl py-2\",\n        \"hover:bg-accent\",\n        className\n      )}\n      {...props}\n    >\n      {shouldHighlight ? (\n        (() => {\n          const index = contentLower.indexOf(highlightLower)\n          if (index === -1)\n            return (\n              <span className=\"text-muted-foreground whitespace-pre-wrap\">\n                {content}\n              </span>\n            )\n\n          const actualHighlightedText = content.substring(\n            index,\n            index + highlightLower.length\n          )\n\n          const before = content.substring(0, index)\n          const after = content.substring(index + actualHighlightedText.length)\n\n          return (\n            <>\n              {before && (\n                <span className=\"text-muted-foreground whitespace-pre-wrap\">\n                  {before}\n                </span>\n              )}\n              <span className=\"text-primary font-medium whitespace-pre-wrap\">\n                {actualHighlightedText}\n              </span>\n              {after && (\n                <span className=\"text-muted-foreground whitespace-pre-wrap\">\n                  {after}\n                </span>\n              )}\n            </>\n          )\n        })()\n      ) : (\n        <span className=\"text-muted-foreground whitespace-pre-wrap\">\n          {content}\n        </span>\n      )}\n    </Button>\n  )\n}\n\nexport { PromptSuggestion }\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "response-stream",
      "type": "registry:ui",
      "title": "Response Stream",
      "description": "A component to simulate streaming text on the client side, perfect for fake responses, or any controlled progressive text display.",
      "dependencies": [],
      "devDependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "components/prompt-kit/response-stream.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport React, { useCallback, useEffect, useRef, useState } from \"react\"\n\nexport type Mode = \"typewriter\" | \"fade\"\n\nexport type UseTextStreamOptions = {\n  textStream: string | AsyncIterable<string>\n  speed?: number\n  mode?: Mode\n  onComplete?: () => void\n  fadeDuration?: number\n  segmentDelay?: number\n  characterChunkSize?: number\n  onError?: (error: unknown) => void\n}\n\nexport type UseTextStreamResult = {\n  displayedText: string\n  isComplete: boolean\n  segments: { text: string; index: number }[]\n  getFadeDuration: () => number\n  getSegmentDelay: () => number\n  reset: () => void\n  startStreaming: () => void\n  pause: () => void\n  resume: () => void\n}\n\nfunction useTextStream({\n  textStream,\n  speed = 20,\n  mode = \"typewriter\",\n  onComplete,\n  fadeDuration,\n  segmentDelay,\n  characterChunkSize,\n  onError,\n}: UseTextStreamOptions): UseTextStreamResult {\n  const [displayedText, setDisplayedText] = useState(\"\")\n  const [isComplete, setIsComplete] = useState(false)\n  const [segments, setSegments] = useState<{ text: string; index: number }[]>(\n    []\n  )\n\n  const speedRef = useRef(speed)\n  const modeRef = useRef(mode)\n  const currentIndexRef = useRef(0)\n  const animationRef = useRef<number | null>(null)\n  const fadeDurationRef = useRef(fadeDuration)\n  const segmentDelayRef = useRef(segmentDelay)\n  const characterChunkSizeRef = useRef(characterChunkSize)\n  const streamRef = useRef<AbortController | null>(null)\n  const completedRef = useRef(false)\n  const onCompleteRef = useRef(onComplete)\n\n  useEffect(() => {\n    speedRef.current = speed\n    modeRef.current = mode\n    fadeDurationRef.current = fadeDuration\n    segmentDelayRef.current = segmentDelay\n    characterChunkSizeRef.current = characterChunkSize\n  }, [speed, mode, fadeDuration, segmentDelay, characterChunkSize])\n\n  useEffect(() => {\n    onCompleteRef.current = onComplete\n  }, [onComplete])\n\n  const getChunkSize = useCallback(() => {\n    if (typeof characterChunkSizeRef.current === \"number\") {\n      return Math.max(1, characterChunkSizeRef.current)\n    }\n\n    const normalizedSpeed = Math.min(100, Math.max(1, speedRef.current))\n\n    if (modeRef.current === \"typewriter\") {\n      if (normalizedSpeed < 25) return 1\n      return Math.max(1, Math.round((normalizedSpeed - 25) / 10))\n    } else if (modeRef.current === \"fade\") {\n      return 1\n    }\n\n    return 1\n  }, [])\n\n  const getProcessingDelay = useCallback(() => {\n    if (typeof segmentDelayRef.current === \"number\") {\n      return Math.max(0, segmentDelayRef.current)\n    }\n\n    const normalizedSpeed = Math.min(100, Math.max(1, speedRef.current))\n    return Math.max(1, Math.round(100 / Math.sqrt(normalizedSpeed)))\n  }, [])\n\n  const getFadeDuration = useCallback(() => {\n    if (typeof fadeDurationRef.current === \"number\")\n      return Math.max(10, fadeDurationRef.current)\n\n    const normalizedSpeed = Math.min(100, Math.max(1, speedRef.current))\n    return Math.round(1000 / Math.sqrt(normalizedSpeed))\n  }, [])\n\n  const getSegmentDelay = useCallback(() => {\n    if (typeof segmentDelayRef.current === \"number\")\n      return Math.max(0, segmentDelayRef.current)\n\n    const normalizedSpeed = Math.min(100, Math.max(1, speedRef.current))\n    return Math.max(1, Math.round(100 / Math.sqrt(normalizedSpeed)))\n  }, [])\n\n  const updateSegments = useCallback((text: string) => {\n    if (modeRef.current === \"fade\") {\n      try {\n        const segmenter = new Intl.Segmenter(navigator.language, {\n          granularity: \"word\",\n        })\n        const segmentIterator = segmenter.segment(text)\n        const newSegments = Array.from(segmentIterator).map(\n          (segment, index) => ({\n            text: segment.segment,\n            index,\n          })\n        )\n        setSegments(newSegments)\n      } catch (error) {\n        const newSegments = text\n          .split(/(\\s+)/)\n          .filter(Boolean)\n          .map((word, index) => ({\n            text: word,\n            index,\n          }))\n        setSegments(newSegments)\n        onError?.(error)\n      }\n    }\n  }, [])\n\n  const markComplete = useCallback(() => {\n    if (!completedRef.current) {\n      completedRef.current = true\n      setIsComplete(true)\n      onCompleteRef.current?.()\n    }\n  }, [])\n\n  const reset = useCallback(() => {\n    currentIndexRef.current = 0\n    setDisplayedText(\"\")\n    setSegments([])\n    setIsComplete(false)\n    completedRef.current = false\n\n    if (animationRef.current) {\n      cancelAnimationFrame(animationRef.current)\n      animationRef.current = null\n    }\n  }, [])\n\n  const processStringTypewriter = useCallback(\n    (text: string) => {\n      let lastFrameTime = 0\n\n      const streamContent = (timestamp: number) => {\n        const delay = getProcessingDelay()\n        if (delay > 0 && timestamp - lastFrameTime < delay) {\n          animationRef.current = requestAnimationFrame(streamContent)\n          return\n        }\n        lastFrameTime = timestamp\n\n        if (currentIndexRef.current >= text.length) {\n          markComplete()\n          return\n        }\n\n        const chunkSize = getChunkSize()\n        const endIndex = Math.min(\n          currentIndexRef.current + chunkSize,\n          text.length\n        )\n        const newDisplayedText = text.slice(0, endIndex)\n\n        setDisplayedText(newDisplayedText)\n        if (modeRef.current === \"fade\") {\n          updateSegments(newDisplayedText)\n        }\n\n        currentIndexRef.current = endIndex\n\n        if (endIndex < text.length) {\n          animationRef.current = requestAnimationFrame(streamContent)\n        } else {\n          markComplete()\n        }\n      }\n\n      animationRef.current = requestAnimationFrame(streamContent)\n    },\n    [getProcessingDelay, getChunkSize, updateSegments, markComplete]\n  )\n\n  const processAsyncIterable = useCallback(\n    async (stream: AsyncIterable<string>) => {\n      const controller = new AbortController()\n      streamRef.current = controller\n\n      let displayed = \"\"\n\n      try {\n        for await (const chunk of stream) {\n          if (controller.signal.aborted) return\n\n          displayed += chunk\n          setDisplayedText(displayed)\n          updateSegments(displayed)\n        }\n\n        markComplete()\n      } catch (error) {\n        console.error(\"Error processing text stream:\", error)\n        markComplete()\n        onError?.(error)\n      }\n    },\n    [updateSegments, markComplete, onError]\n  )\n\n  const startStreaming = useCallback(() => {\n    reset()\n\n    if (typeof textStream === \"string\") {\n      processStringTypewriter(textStream)\n    } else if (textStream) {\n      processAsyncIterable(textStream)\n    }\n  }, [textStream, reset, processStringTypewriter, processAsyncIterable])\n\n  const pause = useCallback(() => {\n    if (animationRef.current) {\n      cancelAnimationFrame(animationRef.current)\n      animationRef.current = null\n    }\n  }, [])\n\n  const resume = useCallback(() => {\n    if (typeof textStream === \"string\" && !isComplete) {\n      processStringTypewriter(textStream)\n    }\n  }, [textStream, isComplete, processStringTypewriter])\n\n  useEffect(() => {\n    startStreaming()\n\n    return () => {\n      if (animationRef.current) {\n        cancelAnimationFrame(animationRef.current)\n      }\n      if (streamRef.current) {\n        streamRef.current.abort()\n      }\n    }\n  }, [textStream, startStreaming])\n\n  return {\n    displayedText,\n    isComplete,\n    segments,\n    getFadeDuration,\n    getSegmentDelay,\n    reset,\n    startStreaming,\n    pause,\n    resume,\n  }\n}\n\nexport type ResponseStreamProps = {\n  textStream: string | AsyncIterable<string>\n  mode?: Mode\n  speed?: number // 1-100, where 1 is slowest and 100 is fastest\n  className?: string\n  onComplete?: () => void\n  as?: keyof React.JSX.IntrinsicElements // Element type to render\n  fadeDuration?: number // Custom fade duration in ms (overrides speed)\n  segmentDelay?: number // Custom delay between segments in ms (overrides speed)\n  characterChunkSize?: number // Custom characters per frame for typewriter mode (overrides speed)\n}\n\nfunction ResponseStream({\n  textStream,\n  mode = \"typewriter\",\n  speed = 20,\n  className = \"\",\n  onComplete,\n  as = \"div\",\n  fadeDuration,\n  segmentDelay,\n  characterChunkSize,\n}: ResponseStreamProps) {\n  const animationEndRef = useRef<(() => void) | null>(null)\n\n  const {\n    displayedText,\n    isComplete,\n    segments,\n    getFadeDuration,\n    getSegmentDelay,\n  } = useTextStream({\n    textStream,\n    speed,\n    mode,\n    onComplete,\n    fadeDuration,\n    segmentDelay,\n    characterChunkSize,\n  })\n\n  useEffect(() => {\n    animationEndRef.current = onComplete ?? null\n  }, [onComplete])\n\n  const handleLastSegmentAnimationEnd = useCallback(() => {\n    if (animationEndRef.current && isComplete) {\n      animationEndRef.current()\n    }\n  }, [isComplete])\n\n  // fadeStyle is the style for the fade animation\n  const fadeStyle = `\n    @keyframes fadeIn {\n      from { opacity: 0; }\n      to { opacity: 1; }\n    }\n    \n    .fade-segment {\n      display: inline-block;\n      opacity: 0;\n      animation: fadeIn ${getFadeDuration()}ms ease-out forwards;\n    }\n\n    .fade-segment-space {\n      white-space: pre;\n    }\n  `\n\n  const renderContent = () => {\n    switch (mode) {\n      case \"typewriter\":\n        return <>{displayedText}</>\n\n      case \"fade\":\n        return (\n          <>\n            <style>{fadeStyle}</style>\n            <div className=\"relative\">\n              {segments.map((segment, idx) => {\n                const isWhitespace = /^\\s+$/.test(segment.text)\n                const isLastSegment = idx === segments.length - 1\n\n                return (\n                  <span\n                    key={`${segment.text}-${idx}`}\n                    className={cn(\n                      \"fade-segment\",\n                      isWhitespace && \"fade-segment-space\"\n                    )}\n                    style={{\n                      animationDelay: `${idx * getSegmentDelay()}ms`,\n                    }}\n                    onAnimationEnd={\n                      isLastSegment ? handleLastSegmentAnimationEnd : undefined\n                    }\n                  >\n                    {segment.text}\n                  </span>\n                )\n              })}\n            </div>\n          </>\n        )\n\n      default:\n        return <>{displayedText}</>\n    }\n  }\n\n  const Container = as as keyof React.JSX.IntrinsicElements\n\n  return <Container className={className}>{renderContent()}</Container>\n}\n\nexport { useTextStream, ResponseStream }\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "reasoning",
      "type": "registry:ui",
      "title": "Reasoning",
      "description": "A collapsible component for showing AI reasoning, explanations, or logic. You can control it manually or let it auto-close when the stream ends. Markdown is supported.",
      "dependencies": [
        "lucide-react"
      ],
      "devDependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "components/prompt-kit/reasoning.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport { ChevronDownIcon } from \"lucide-react\"\nimport React, {\n  createContext,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\"\nimport { Markdown } from \"./markdown\"\n\ntype ReasoningContextType = {\n  isOpen: boolean\n  onOpenChange: (open: boolean) => void\n}\n\nconst ReasoningContext = createContext<ReasoningContextType | undefined>(\n  undefined\n)\n\nfunction useReasoningContext() {\n  const context = useContext(ReasoningContext)\n  if (!context) {\n    throw new Error(\n      \"useReasoningContext must be used within a Reasoning provider\"\n    )\n  }\n  return context\n}\n\nexport type ReasoningProps = {\n  children: React.ReactNode\n  className?: string\n  open?: boolean\n  onOpenChange?: (open: boolean) => void\n  isStreaming?: boolean\n}\nfunction Reasoning({\n  children,\n  className,\n  open,\n  onOpenChange,\n  isStreaming,\n}: ReasoningProps) {\n  const [internalOpen, setInternalOpen] = useState(false)\n  const [wasAutoOpened, setWasAutoOpened] = useState(false)\n\n  const isControlled = open !== undefined\n  const isOpen = isControlled ? open : internalOpen\n\n  const handleOpenChange = (newOpen: boolean) => {\n    if (!isControlled) {\n      setInternalOpen(newOpen)\n    }\n    onOpenChange?.(newOpen)\n  }\n\n  useEffect(() => {\n    if (isStreaming && !wasAutoOpened) {\n      if (!isControlled) setInternalOpen(true)\n      setWasAutoOpened(true)\n    }\n\n    if (!isStreaming && wasAutoOpened) {\n      if (!isControlled) setInternalOpen(false)\n      setWasAutoOpened(false)\n    }\n  }, [isStreaming, wasAutoOpened, isControlled])\n\n  return (\n    <ReasoningContext.Provider\n      value={{\n        isOpen,\n        onOpenChange: handleOpenChange,\n      }}\n    >\n      <div className={className}>{children}</div>\n    </ReasoningContext.Provider>\n  )\n}\n\nexport type ReasoningTriggerProps = {\n  children: React.ReactNode\n  className?: string\n} & React.HTMLAttributes<HTMLButtonElement>\n\nfunction ReasoningTrigger({\n  children,\n  className,\n  ...props\n}: ReasoningTriggerProps) {\n  const { isOpen, onOpenChange } = useReasoningContext()\n\n  return (\n    <button\n      className={cn(\"flex cursor-pointer items-center gap-2\", className)}\n      onClick={() => onOpenChange(!isOpen)}\n      {...props}\n    >\n      <span className=\"text-primary\">{children}</span>\n      <div\n        className={cn(\n          \"transform transition-transform\",\n          isOpen ? \"rotate-180\" : \"\"\n        )}\n      >\n        <ChevronDownIcon className=\"size-4\" />\n      </div>\n    </button>\n  )\n}\n\nexport type ReasoningContentProps = {\n  children: React.ReactNode\n  className?: string\n  markdown?: boolean\n  contentClassName?: string\n} & React.HTMLAttributes<HTMLDivElement>\n\nfunction ReasoningContent({\n  children,\n  className,\n  contentClassName,\n  markdown = false,\n  ...props\n}: ReasoningContentProps) {\n  const contentRef = useRef<HTMLDivElement>(null)\n  const innerRef = useRef<HTMLDivElement>(null)\n  const { isOpen } = useReasoningContext()\n\n  useEffect(() => {\n    if (!contentRef.current || !innerRef.current) return\n\n    const observer = new ResizeObserver(() => {\n      if (contentRef.current && innerRef.current && isOpen) {\n        contentRef.current.style.maxHeight = `${innerRef.current.scrollHeight}px`\n      }\n    })\n\n    observer.observe(innerRef.current)\n\n    if (isOpen) {\n      contentRef.current.style.maxHeight = `${innerRef.current.scrollHeight}px`\n    }\n\n    return () => observer.disconnect()\n  }, [isOpen])\n\n  const content = markdown ? (\n    <Markdown>{children as string}</Markdown>\n  ) : (\n    children\n  )\n\n  return (\n    <div\n      ref={contentRef}\n      className={cn(\n        \"overflow-hidden transition-[max-height] duration-150 ease-out\",\n        className\n      )}\n      style={{\n        maxHeight: isOpen ? contentRef.current?.scrollHeight : \"0px\",\n      }}\n      {...props}\n    >\n      <div\n        ref={innerRef}\n        className={cn(\n          \"text-muted-foreground prose prose-sm dark:prose-invert\",\n          contentClassName\n        )}\n      >\n        {content}\n      </div>\n    </div>\n  )\n}\n\nexport { Reasoning, ReasoningTrigger, ReasoningContent }\n"
        },
        {
          "path": "components/prompt-kit/markdown.tsx",
          "type": "registry:component",
          "content": "import { cn } from \"@/lib/utils\"\nimport { marked } from \"marked\"\nimport { memo, useId, useMemo } from \"react\"\nimport ReactMarkdown, { Components } from \"react-markdown\"\nimport remarkBreaks from \"remark-breaks\"\nimport remarkGfm from \"remark-gfm\"\nimport { CodeBlock, CodeBlockCode } from \"./code-block\"\n\nexport type MarkdownProps = {\n  children: string\n  id?: string\n  className?: string\n  components?: Partial<Components>\n}\n\nfunction parseMarkdownIntoBlocks(markdown: string): string[] {\n  const tokens = marked.lexer(markdown)\n  return tokens.map((token) => token.raw)\n}\n\nfunction extractLanguage(className?: string): string {\n  if (!className) return \"plaintext\"\n  const match = className.match(/language-(\\w+)/)\n  return match ? match[1] : \"plaintext\"\n}\n\nconst INITIAL_COMPONENTS: Partial<Components> = {\n  code: function CodeComponent({ className, children, ...props }) {\n    const isInline =\n      !props.node?.position?.start.line ||\n      props.node?.position?.start.line === props.node?.position?.end.line\n\n    if (isInline) {\n      return (\n        <span\n          className={cn(\n            \"bg-primary-foreground rounded-sm px-1 font-mono text-sm\",\n            className\n          )}\n          {...props}\n        >\n          {children}\n        </span>\n      )\n    }\n\n    const language = extractLanguage(className)\n\n    return (\n      <CodeBlock className={className}>\n        <CodeBlockCode code={children as string} language={language} />\n      </CodeBlock>\n    )\n  },\n  pre: function PreComponent({ children }) {\n    return <>{children}</>\n  },\n}\n\nconst MemoizedMarkdownBlock = memo(\n  function MarkdownBlock({\n    content,\n    components = INITIAL_COMPONENTS,\n  }: {\n    content: string\n    components?: Partial<Components>\n  }) {\n    return (\n      <ReactMarkdown\n        remarkPlugins={[remarkGfm, remarkBreaks]}\n        components={components}\n      >\n        {content}\n      </ReactMarkdown>\n    )\n  },\n  function propsAreEqual(prevProps, nextProps) {\n    return prevProps.content === nextProps.content\n  }\n)\n\nMemoizedMarkdownBlock.displayName = \"MemoizedMarkdownBlock\"\n\nfunction MarkdownComponent({\n  children,\n  id,\n  className,\n  components = INITIAL_COMPONENTS,\n}: MarkdownProps) {\n  const generatedId = useId()\n  const blockId = id ?? generatedId\n  const blocks = useMemo(() => parseMarkdownIntoBlocks(children), [children])\n\n  return (\n    <div className={className}>\n      {blocks.map((block, index) => (\n        <MemoizedMarkdownBlock\n          key={`${blockId}-block-${index}`}\n          content={block}\n          components={components}\n        />\n      ))}\n    </div>\n  )\n}\n\nconst Markdown = memo(MarkdownComponent)\nMarkdown.displayName = \"Markdown\"\n\nexport { Markdown }\n"
        },
        {
          "path": "components/prompt-kit/response-stream.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport React, { useCallback, useEffect, useRef, useState } from \"react\"\n\nexport type Mode = \"typewriter\" | \"fade\"\n\nexport type UseTextStreamOptions = {\n  textStream: string | AsyncIterable<string>\n  speed?: number\n  mode?: Mode\n  onComplete?: () => void\n  fadeDuration?: number\n  segmentDelay?: number\n  characterChunkSize?: number\n  onError?: (error: unknown) => void\n}\n\nexport type UseTextStreamResult = {\n  displayedText: string\n  isComplete: boolean\n  segments: { text: string; index: number }[]\n  getFadeDuration: () => number\n  getSegmentDelay: () => number\n  reset: () => void\n  startStreaming: () => void\n  pause: () => void\n  resume: () => void\n}\n\nfunction useTextStream({\n  textStream,\n  speed = 20,\n  mode = \"typewriter\",\n  onComplete,\n  fadeDuration,\n  segmentDelay,\n  characterChunkSize,\n  onError,\n}: UseTextStreamOptions): UseTextStreamResult {\n  const [displayedText, setDisplayedText] = useState(\"\")\n  const [isComplete, setIsComplete] = useState(false)\n  const [segments, setSegments] = useState<{ text: string; index: number }[]>(\n    []\n  )\n\n  const speedRef = useRef(speed)\n  const modeRef = useRef(mode)\n  const currentIndexRef = useRef(0)\n  const animationRef = useRef<number | null>(null)\n  const fadeDurationRef = useRef(fadeDuration)\n  const segmentDelayRef = useRef(segmentDelay)\n  const characterChunkSizeRef = useRef(characterChunkSize)\n  const streamRef = useRef<AbortController | null>(null)\n  const completedRef = useRef(false)\n  const onCompleteRef = useRef(onComplete)\n\n  useEffect(() => {\n    speedRef.current = speed\n    modeRef.current = mode\n    fadeDurationRef.current = fadeDuration\n    segmentDelayRef.current = segmentDelay\n    characterChunkSizeRef.current = characterChunkSize\n  }, [speed, mode, fadeDuration, segmentDelay, characterChunkSize])\n\n  useEffect(() => {\n    onCompleteRef.current = onComplete\n  }, [onComplete])\n\n  const getChunkSize = useCallback(() => {\n    if (typeof characterChunkSizeRef.current === \"number\") {\n      return Math.max(1, characterChunkSizeRef.current)\n    }\n\n    const normalizedSpeed = Math.min(100, Math.max(1, speedRef.current))\n\n    if (modeRef.current === \"typewriter\") {\n      if (normalizedSpeed < 25) return 1\n      return Math.max(1, Math.round((normalizedSpeed - 25) / 10))\n    } else if (modeRef.current === \"fade\") {\n      return 1\n    }\n\n    return 1\n  }, [])\n\n  const getProcessingDelay = useCallback(() => {\n    if (typeof segmentDelayRef.current === \"number\") {\n      return Math.max(0, segmentDelayRef.current)\n    }\n\n    const normalizedSpeed = Math.min(100, Math.max(1, speedRef.current))\n    return Math.max(1, Math.round(100 / Math.sqrt(normalizedSpeed)))\n  }, [])\n\n  const getFadeDuration = useCallback(() => {\n    if (typeof fadeDurationRef.current === \"number\")\n      return Math.max(10, fadeDurationRef.current)\n\n    const normalizedSpeed = Math.min(100, Math.max(1, speedRef.current))\n    return Math.round(1000 / Math.sqrt(normalizedSpeed))\n  }, [])\n\n  const getSegmentDelay = useCallback(() => {\n    if (typeof segmentDelayRef.current === \"number\")\n      return Math.max(0, segmentDelayRef.current)\n\n    const normalizedSpeed = Math.min(100, Math.max(1, speedRef.current))\n    return Math.max(1, Math.round(100 / Math.sqrt(normalizedSpeed)))\n  }, [])\n\n  const updateSegments = useCallback((text: string) => {\n    if (modeRef.current === \"fade\") {\n      try {\n        const segmenter = new Intl.Segmenter(navigator.language, {\n          granularity: \"word\",\n        })\n        const segmentIterator = segmenter.segment(text)\n        const newSegments = Array.from(segmentIterator).map(\n          (segment, index) => ({\n            text: segment.segment,\n            index,\n          })\n        )\n        setSegments(newSegments)\n      } catch (error) {\n        const newSegments = text\n          .split(/(\\s+)/)\n          .filter(Boolean)\n          .map((word, index) => ({\n            text: word,\n            index,\n          }))\n        setSegments(newSegments)\n        onError?.(error)\n      }\n    }\n  }, [])\n\n  const markComplete = useCallback(() => {\n    if (!completedRef.current) {\n      completedRef.current = true\n      setIsComplete(true)\n      onCompleteRef.current?.()\n    }\n  }, [])\n\n  const reset = useCallback(() => {\n    currentIndexRef.current = 0\n    setDisplayedText(\"\")\n    setSegments([])\n    setIsComplete(false)\n    completedRef.current = false\n\n    if (animationRef.current) {\n      cancelAnimationFrame(animationRef.current)\n      animationRef.current = null\n    }\n  }, [])\n\n  const processStringTypewriter = useCallback(\n    (text: string) => {\n      let lastFrameTime = 0\n\n      const streamContent = (timestamp: number) => {\n        const delay = getProcessingDelay()\n        if (delay > 0 && timestamp - lastFrameTime < delay) {\n          animationRef.current = requestAnimationFrame(streamContent)\n          return\n        }\n        lastFrameTime = timestamp\n\n        if (currentIndexRef.current >= text.length) {\n          markComplete()\n          return\n        }\n\n        const chunkSize = getChunkSize()\n        const endIndex = Math.min(\n          currentIndexRef.current + chunkSize,\n          text.length\n        )\n        const newDisplayedText = text.slice(0, endIndex)\n\n        setDisplayedText(newDisplayedText)\n        if (modeRef.current === \"fade\") {\n          updateSegments(newDisplayedText)\n        }\n\n        currentIndexRef.current = endIndex\n\n        if (endIndex < text.length) {\n          animationRef.current = requestAnimationFrame(streamContent)\n        } else {\n          markComplete()\n        }\n      }\n\n      animationRef.current = requestAnimationFrame(streamContent)\n    },\n    [getProcessingDelay, getChunkSize, updateSegments, markComplete]\n  )\n\n  const processAsyncIterable = useCallback(\n    async (stream: AsyncIterable<string>) => {\n      const controller = new AbortController()\n      streamRef.current = controller\n\n      let displayed = \"\"\n\n      try {\n        for await (const chunk of stream) {\n          if (controller.signal.aborted) return\n\n          displayed += chunk\n          setDisplayedText(displayed)\n          updateSegments(displayed)\n        }\n\n        markComplete()\n      } catch (error) {\n        console.error(\"Error processing text stream:\", error)\n        markComplete()\n        onError?.(error)\n      }\n    },\n    [updateSegments, markComplete, onError]\n  )\n\n  const startStreaming = useCallback(() => {\n    reset()\n\n    if (typeof textStream === \"string\") {\n      processStringTypewriter(textStream)\n    } else if (textStream) {\n      processAsyncIterable(textStream)\n    }\n  }, [textStream, reset, processStringTypewriter, processAsyncIterable])\n\n  const pause = useCallback(() => {\n    if (animationRef.current) {\n      cancelAnimationFrame(animationRef.current)\n      animationRef.current = null\n    }\n  }, [])\n\n  const resume = useCallback(() => {\n    if (typeof textStream === \"string\" && !isComplete) {\n      processStringTypewriter(textStream)\n    }\n  }, [textStream, isComplete, processStringTypewriter])\n\n  useEffect(() => {\n    startStreaming()\n\n    return () => {\n      if (animationRef.current) {\n        cancelAnimationFrame(animationRef.current)\n      }\n      if (streamRef.current) {\n        streamRef.current.abort()\n      }\n    }\n  }, [textStream, startStreaming])\n\n  return {\n    displayedText,\n    isComplete,\n    segments,\n    getFadeDuration,\n    getSegmentDelay,\n    reset,\n    startStreaming,\n    pause,\n    resume,\n  }\n}\n\nexport type ResponseStreamProps = {\n  textStream: string | AsyncIterable<string>\n  mode?: Mode\n  speed?: number // 1-100, where 1 is slowest and 100 is fastest\n  className?: string\n  onComplete?: () => void\n  as?: keyof React.JSX.IntrinsicElements // Element type to render\n  fadeDuration?: number // Custom fade duration in ms (overrides speed)\n  segmentDelay?: number // Custom delay between segments in ms (overrides speed)\n  characterChunkSize?: number // Custom characters per frame for typewriter mode (overrides speed)\n}\n\nfunction ResponseStream({\n  textStream,\n  mode = \"typewriter\",\n  speed = 20,\n  className = \"\",\n  onComplete,\n  as = \"div\",\n  fadeDuration,\n  segmentDelay,\n  characterChunkSize,\n}: ResponseStreamProps) {\n  const animationEndRef = useRef<(() => void) | null>(null)\n\n  const {\n    displayedText,\n    isComplete,\n    segments,\n    getFadeDuration,\n    getSegmentDelay,\n  } = useTextStream({\n    textStream,\n    speed,\n    mode,\n    onComplete,\n    fadeDuration,\n    segmentDelay,\n    characterChunkSize,\n  })\n\n  useEffect(() => {\n    animationEndRef.current = onComplete ?? null\n  }, [onComplete])\n\n  const handleLastSegmentAnimationEnd = useCallback(() => {\n    if (animationEndRef.current && isComplete) {\n      animationEndRef.current()\n    }\n  }, [isComplete])\n\n  // fadeStyle is the style for the fade animation\n  const fadeStyle = `\n    @keyframes fadeIn {\n      from { opacity: 0; }\n      to { opacity: 1; }\n    }\n    \n    .fade-segment {\n      display: inline-block;\n      opacity: 0;\n      animation: fadeIn ${getFadeDuration()}ms ease-out forwards;\n    }\n\n    .fade-segment-space {\n      white-space: pre;\n    }\n  `\n\n  const renderContent = () => {\n    switch (mode) {\n      case \"typewriter\":\n        return <>{displayedText}</>\n\n      case \"fade\":\n        return (\n          <>\n            <style>{fadeStyle}</style>\n            <div className=\"relative\">\n              {segments.map((segment, idx) => {\n                const isWhitespace = /^\\s+$/.test(segment.text)\n                const isLastSegment = idx === segments.length - 1\n\n                return (\n                  <span\n                    key={`${segment.text}-${idx}`}\n                    className={cn(\n                      \"fade-segment\",\n                      isWhitespace && \"fade-segment-space\"\n                    )}\n                    style={{\n                      animationDelay: `${idx * getSegmentDelay()}ms`,\n                    }}\n                    onAnimationEnd={\n                      isLastSegment ? handleLastSegmentAnimationEnd : undefined\n                    }\n                  >\n                    {segment.text}\n                  </span>\n                )\n              })}\n            </div>\n          </>\n        )\n\n      default:\n        return <>{displayedText}</>\n    }\n  }\n\n  const Container = as as keyof React.JSX.IntrinsicElements\n\n  return <Container className={className}>{renderContent()}</Container>\n}\n\nexport { useTextStream, ResponseStream }\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "file-upload",
      "type": "registry:ui",
      "title": "File Upload",
      "description": "A component for creating drag-and-drop file upload interfaces with support for single or multiple files, custom triggers, and visual feedback during file dragging operations.",
      "dependencies": [],
      "devDependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "components/prompt-kit/file-upload.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  Children,\n  cloneElement,\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\"\nimport { createPortal } from \"react-dom\"\n\ntype FileUploadContextValue = {\n  isDragging: boolean\n  inputRef: React.RefObject<HTMLInputElement | null>\n  multiple?: boolean\n  disabled?: boolean\n}\n\nconst FileUploadContext = createContext<FileUploadContextValue | null>(null)\n\nexport type FileUploadProps = {\n  onFilesAdded: (files: File[]) => void\n  children: React.ReactNode\n  multiple?: boolean\n  accept?: string\n  disabled?: boolean\n}\n\nfunction FileUpload({\n  onFilesAdded,\n  children,\n  multiple = true,\n  accept,\n  disabled = false,\n}: FileUploadProps) {\n  const inputRef = useRef<HTMLInputElement>(null)\n  const [isDragging, setIsDragging] = useState(false)\n  const dragCounter = useRef(0)\n\n  const handleFiles = useCallback(\n    (files: FileList) => {\n      const newFiles = Array.from(files)\n      if (multiple) {\n        onFilesAdded(newFiles)\n      } else {\n        onFilesAdded(newFiles.slice(0, 1))\n      }\n    },\n    [multiple, onFilesAdded]\n  )\n\n  useEffect(() => {\n    const handleDrag = (e: DragEvent) => {\n      e.preventDefault()\n      e.stopPropagation()\n    }\n\n    const handleDragIn = (e: DragEvent) => {\n      handleDrag(e)\n      dragCounter.current++\n      if (e.dataTransfer?.items.length) setIsDragging(true)\n    }\n\n    const handleDragOut = (e: DragEvent) => {\n      handleDrag(e)\n      dragCounter.current--\n      if (dragCounter.current === 0) setIsDragging(false)\n    }\n\n    const handleDrop = (e: DragEvent) => {\n      handleDrag(e)\n      setIsDragging(false)\n      dragCounter.current = 0\n      if (e.dataTransfer?.files.length) {\n        handleFiles(e.dataTransfer.files)\n      }\n    }\n\n    window.addEventListener(\"dragenter\", handleDragIn)\n    window.addEventListener(\"dragleave\", handleDragOut)\n    window.addEventListener(\"dragover\", handleDrag)\n    window.addEventListener(\"drop\", handleDrop)\n\n    return () => {\n      window.removeEventListener(\"dragenter\", handleDragIn)\n      window.removeEventListener(\"dragleave\", handleDragOut)\n      window.removeEventListener(\"dragover\", handleDrag)\n      window.removeEventListener(\"drop\", handleDrop)\n    }\n  }, [handleFiles, onFilesAdded, multiple])\n\n  const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {\n    if (e.target.files?.length) {\n      handleFiles(e.target.files)\n      e.target.value = \"\"\n    }\n  }\n\n  return (\n    <FileUploadContext.Provider\n      value={{ isDragging, inputRef, multiple, disabled }}\n    >\n      <input\n        type=\"file\"\n        ref={inputRef}\n        onChange={handleFileSelect}\n        className=\"hidden\"\n        multiple={multiple}\n        accept={accept}\n        aria-hidden\n        disabled={disabled}\n      />\n      {children}\n    </FileUploadContext.Provider>\n  )\n}\n\nexport type FileUploadTriggerProps =\n  React.ComponentPropsWithoutRef<\"button\"> & {\n    asChild?: boolean\n  }\n\nfunction FileUploadTrigger({\n  asChild = false,\n  className,\n  children,\n  ...props\n}: FileUploadTriggerProps) {\n  const context = useContext(FileUploadContext)\n  const handleClick = () => context?.inputRef.current?.click()\n\n  if (asChild) {\n    const child = Children.only(children) as React.ReactElement<\n      React.HTMLAttributes<HTMLElement>\n    >\n    return cloneElement(child, {\n      ...props,\n      role: \"button\",\n      className: cn(className, child.props.className),\n      onClick: (e: React.MouseEvent) => {\n        e.stopPropagation()\n        handleClick()\n        child.props.onClick?.(e as React.MouseEvent<HTMLElement>)\n      },\n    })\n  }\n\n  return (\n    <button\n      type=\"button\"\n      className={className}\n      onClick={handleClick}\n      {...props}\n    >\n      {children}\n    </button>\n  )\n}\n\ntype FileUploadContentProps = React.HTMLAttributes<HTMLDivElement>\n\nfunction FileUploadContent({ className, ...props }: FileUploadContentProps) {\n  const context = useContext(FileUploadContext)\n  const [mounted, setMounted] = useState(false)\n\n  useEffect(() => {\n    setMounted(true)\n    return () => setMounted(false)\n  }, [])\n\n  if (!context?.isDragging || !mounted || context?.disabled) {\n    return null\n  }\n\n  const content = (\n    <div\n      className={cn(\n        \"bg-background/80 fixed inset-0 z-50 flex items-center justify-center backdrop-blur-sm\",\n        \"animate-in fade-in-0 slide-in-from-bottom-10 zoom-in-90 duration-150\",\n        className\n      )}\n      {...props}\n    />\n  )\n\n  return createPortal(content, document.body)\n}\n\nexport { FileUpload, FileUploadTrigger, FileUploadContent }\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "jsx-preview",
      "type": "registry:ui",
      "title": "Jsx Preview",
      "description": "A component for rendering JSX strings as React components, with support for streaming content and automatic tag completion.",
      "dependencies": [
        "react-jsx-parser"
      ],
      "devDependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "components/prompt-kit/jsx-preview.tsx",
          "type": "registry:component",
          "content": "import * as React from \"react\"\nimport JsxParser from \"react-jsx-parser\"\nimport type { TProps as JsxParserProps } from \"react-jsx-parser\"\n\nfunction matchJsxTag(code: string) {\n  if (code.trim() === \"\") {\n    return null\n  }\n\n  const tagRegex = /<\\/?([a-zA-Z][a-zA-Z0-9]*)\\s*([^>]*?)(\\/)?>/\n  const match = code.match(tagRegex)\n\n  if (!match || typeof match.index === \"undefined\") {\n    return null\n  }\n\n  const [fullMatch, tagName, attributes, selfClosing] = match\n\n  const type = selfClosing\n    ? \"self-closing\"\n    : fullMatch.startsWith(\"</\")\n      ? \"closing\"\n      : \"opening\"\n\n  return {\n    tag: fullMatch,\n    tagName,\n    type,\n    attributes: attributes.trim(),\n    startIndex: match.index,\n    endIndex: match.index + fullMatch.length,\n  }\n}\n\nfunction completeJsxTag(code: string) {\n  const stack: string[] = []\n  let result = \"\"\n  let currentPosition = 0\n\n  while (currentPosition < code.length) {\n    const match = matchJsxTag(code.slice(currentPosition))\n    if (!match) break\n    const { tagName, type, endIndex } = match\n\n    if (type === \"opening\") {\n      stack.push(tagName)\n    } else if (type === \"closing\") {\n      stack.pop()\n    }\n\n    result += code.slice(currentPosition, currentPosition + endIndex)\n    currentPosition += endIndex\n  }\n\n  return (\n    result +\n    stack\n      .reverse()\n      .map((tag) => `</${tag}>`)\n      .join(\"\")\n  )\n}\n\nexport type JSXPreviewProps = {\n  jsx: string\n  isStreaming?: boolean\n} & JsxParserProps\n\nfunction JSXPreview({ jsx, isStreaming = false, ...props }: JSXPreviewProps) {\n  const processedJsx = React.useMemo(\n    () => (isStreaming ? completeJsxTag(jsx) : jsx),\n    [jsx, isStreaming]\n  )\n\n  // Cast JsxParser to any to work around the type incompatibility\n  const Parser = JsxParser as unknown as React.ComponentType<JsxParserProps>\n\n  return <Parser jsx={processedJsx} {...props} />\n}\n\nexport { JSXPreview }\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "tool",
      "type": "registry:ui",
      "title": "Tool",
      "description": "Displays tool call details including input, output, status, and errors. Ideal for visualizing AI tool usage in chat UIs.",
      "dependencies": [
        "lucide-react"
      ],
      "devDependencies": [],
      "registryDependencies": [
        "collapsible",
        "button"
      ],
      "files": [
        {
          "path": "components/prompt-kit/tool.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\"\nimport { cn } from \"@/lib/utils\"\nimport {\n  CheckCircle,\n  ChevronDown,\n  Loader2,\n  Settings,\n  XCircle,\n} from \"lucide-react\"\nimport { useState } from \"react\"\n\nexport type ToolPart = {\n  type: string\n  state:\n    | \"input-streaming\"\n    | \"input-available\"\n    | \"output-available\"\n    | \"output-error\"\n  input?: Record<string, unknown>\n  output?: Record<string, unknown>\n  toolCallId?: string\n  errorText?: string\n}\n\nexport type ToolProps = {\n  toolPart: ToolPart\n  defaultOpen?: boolean\n  className?: string\n}\n\nconst Tool = ({ toolPart, defaultOpen = false, className }: ToolProps) => {\n  const [isOpen, setIsOpen] = useState(defaultOpen)\n\n  const { state, input, output, toolCallId } = toolPart\n\n  const getStateIcon = () => {\n    switch (state) {\n      case \"input-streaming\":\n        return <Loader2 className=\"h-4 w-4 animate-spin text-blue-500\" />\n      case \"input-available\":\n        return <Settings className=\"h-4 w-4 text-orange-500\" />\n      case \"output-available\":\n        return <CheckCircle className=\"h-4 w-4 text-green-500\" />\n      case \"output-error\":\n        return <XCircle className=\"h-4 w-4 text-red-500\" />\n      default:\n        return <Settings className=\"text-muted-foreground h-4 w-4\" />\n    }\n  }\n\n  const getStateBadge = () => {\n    const baseClasses = \"px-2 py-1 rounded-full text-xs font-medium\"\n    switch (state) {\n      case \"input-streaming\":\n        return (\n          <span\n            className={cn(\n              baseClasses,\n              \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\"\n            )}\n          >\n            Processing\n          </span>\n        )\n      case \"input-available\":\n        return (\n          <span\n            className={cn(\n              baseClasses,\n              \"bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400\"\n            )}\n          >\n            Ready\n          </span>\n        )\n      case \"output-available\":\n        return (\n          <span\n            className={cn(\n              baseClasses,\n              \"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400\"\n            )}\n          >\n            Completed\n          </span>\n        )\n      case \"output-error\":\n        return (\n          <span\n            className={cn(\n              baseClasses,\n              \"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400\"\n            )}\n          >\n            Error\n          </span>\n        )\n      default:\n        return (\n          <span\n            className={cn(\n              baseClasses,\n              \"bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400\"\n            )}\n          >\n            Pending\n          </span>\n        )\n    }\n  }\n\n  const formatValue = (value: unknown): string => {\n    if (value === null) return \"null\"\n    if (value === undefined) return \"undefined\"\n    if (typeof value === \"string\") return value\n    if (typeof value === \"object\") {\n      return JSON.stringify(value, null, 2)\n    }\n    return String(value)\n  }\n\n  return (\n    <div\n      className={cn(\n        \"border-border mt-3 overflow-hidden rounded-lg border\",\n        className\n      )}\n    >\n      <Collapsible open={isOpen} onOpenChange={setIsOpen}>\n        <CollapsibleTrigger asChild>\n          <Button\n            variant=\"ghost\"\n            className=\"bg-background h-auto w-full justify-between rounded-b-none px-3 py-2 font-normal\"\n          >\n            <div className=\"flex items-center gap-2\">\n              {getStateIcon()}\n              <span className=\"font-mono text-sm font-medium\">\n                {toolPart.type}\n              </span>\n              {getStateBadge()}\n            </div>\n            <ChevronDown className={cn(\"h-4 w-4\", isOpen && \"rotate-180\")} />\n          </Button>\n        </CollapsibleTrigger>\n        <CollapsibleContent\n          className={cn(\n            \"border-border border-t\",\n            \"data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down overflow-hidden\"\n          )}\n        >\n          <div className=\"bg-background space-y-3 p-3\">\n            {input && Object.keys(input).length > 0 && (\n              <div>\n                <h4 className=\"text-muted-foreground mb-2 text-sm font-medium\">\n                  Input\n                </h4>\n                <div className=\"bg-background rounded border p-2 font-mono text-sm\">\n                  {Object.entries(input).map(([key, value]) => (\n                    <div key={key} className=\"mb-1\">\n                      <span className=\"text-muted-foreground\">{key}:</span>{\" \"}\n                      <span>{formatValue(value)}</span>\n                    </div>\n                  ))}\n                </div>\n              </div>\n            )}\n\n            {output && (\n              <div>\n                <h4 className=\"text-muted-foreground mb-2 text-sm font-medium\">\n                  Output\n                </h4>\n                <div className=\"bg-background max-h-60 overflow-auto rounded border p-2 font-mono text-sm\">\n                  <pre className=\"whitespace-pre-wrap\">\n                    {formatValue(output)}\n                  </pre>\n                </div>\n              </div>\n            )}\n\n            {state === \"output-error\" && toolPart.errorText && (\n              <div>\n                <h4 className=\"mb-2 text-sm font-medium text-red-500\">Error</h4>\n                <div className=\"bg-background rounded border border-red-200 p-2 text-sm dark:border-red-950 dark:bg-red-900/20\">\n                  {toolPart.errorText}\n                </div>\n              </div>\n            )}\n\n            {state === \"input-streaming\" && (\n              <div className=\"text-muted-foreground text-sm\">\n                Processing tool call...\n              </div>\n            )}\n\n            {toolCallId && (\n              <div className=\"text-muted-foreground border-t border-blue-200 pt-2 text-xs\">\n                <span className=\"font-mono\">Call ID: {toolCallId}</span>\n              </div>\n            )}\n          </div>\n        </CollapsibleContent>\n      </Collapsible>\n    </div>\n  )\n}\n\nexport { Tool }\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "source",
      "type": "registry:ui",
      "title": "Source",
      "description": "Displays website sources used by AI-generated content, showing URL details, titles, and descriptions on hover.",
      "dependencies": [],
      "devDependencies": [],
      "registryDependencies": [
        "hover-card"
      ],
      "files": [
        {
          "path": "components/prompt-kit/source.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport {\n  HoverCard,\n  HoverCardContent,\n  HoverCardTrigger,\n} from \"@/components/ui/hover-card\"\nimport { cn } from \"@/lib/utils\"\nimport { createContext, useContext } from \"react\"\n\nconst SourceContext = createContext<{\n  href: string\n  domain: string\n} | null>(null)\n\nfunction useSourceContext() {\n  const ctx = useContext(SourceContext)\n  if (!ctx) throw new Error(\"Source.* must be used inside <Source>\")\n  return ctx\n}\n\nexport type SourceProps = {\n  href: string\n  children: React.ReactNode\n}\n\nexport function Source({ href, children }: SourceProps) {\n  let domain = \"\"\n  try {\n    domain = new URL(href).hostname\n  } catch {\n    domain = href.split(\"/\").pop() || href\n  }\n\n  return (\n    <SourceContext.Provider value={{ href, domain }}>\n      <HoverCard openDelay={150} closeDelay={0}>\n        {children}\n      </HoverCard>\n    </SourceContext.Provider>\n  )\n}\n\nexport type SourceTriggerProps = {\n  label?: string | number\n  showFavicon?: boolean\n  className?: string\n}\n\nexport function SourceTrigger({\n  label,\n  showFavicon = false,\n  className,\n}: SourceTriggerProps) {\n  const { href, domain } = useSourceContext()\n  const labelToShow = label ?? domain.replace(\"www.\", \"\")\n\n  return (\n    <HoverCardTrigger asChild>\n      <a\n        href={href}\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n        className={cn(\n          \"bg-muted text-muted-foreground hover:bg-muted-foreground/30 hover:text-primary inline-flex h-5 max-w-32 items-center gap-1 overflow-hidden rounded-full py-0 text-xs no-underline transition-colors duration-150\",\n          showFavicon ? \"pr-2 pl-1\" : \"px-1\",\n          className\n        )}\n      >\n        {showFavicon && (\n          <img\n            src={`https://www.google.com/s2/favicons?sz=64&domain_url=${encodeURIComponent(\n              href\n            )}`}\n            alt=\"favicon\"\n            width={14}\n            height={14}\n            className=\"size-3.5 rounded-full\"\n          />\n        )}\n        <span className=\"truncate tabular-nums text-center font-normal\">{labelToShow}</span>\n      </a>\n    </HoverCardTrigger>\n  )\n}\n\nexport type SourceContentProps = {\n  title: string\n  description: string\n  className?: string\n}\n\nexport function SourceContent({\n  title,\n  description,\n  className,\n}: SourceContentProps) {\n  const { href, domain } = useSourceContext()\n\n  return (\n    <HoverCardContent className={cn(\"w-80 p-0 shadow-xs\", className)}>\n      <a\n        href={href}\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n        className=\"flex flex-col gap-2 p-3\"\n      >\n        <div className=\"flex items-center gap-1.5\">\n          <img\n            src={`https://www.google.com/s2/favicons?sz=64&domain_url=${encodeURIComponent(\n              href\n            )}`}\n            alt=\"favicon\"\n            className=\"size-4 rounded-full\"\n            width={16}\n            height={16}\n          />\n          <div className=\"text-primary truncate text-sm\">\n            {domain.replace(\"www.\", \"\")}\n          </div>\n        </div>\n        <div className=\"line-clamp-2 text-sm font-medium\">{title}</div>\n        <div className=\"text-muted-foreground line-clamp-2 text-sm\">\n          {description}\n        </div>\n      </a>\n    </HoverCardContent>\n  )\n}\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "image",
      "type": "registry:ui",
      "title": "Image",
      "description": "A component for displaying images from base64 or Uint8Array data, with full accessibility and responsive styling. Perfect for AI-generated or user-uploaded images.",
      "dependencies": [],
      "devDependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "components/prompt-kit/image.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport { useEffect, useState, type ImgHTMLAttributes } from \"react\"\n\nexport type GeneratedImageLike = {\n  base64?: string\n  uint8Array?: Uint8Array\n  mediaType?: string\n}\n\nexport type ImageProps = GeneratedImageLike &\n  Omit<ImgHTMLAttributes<HTMLImageElement>, \"src\"> & {\n    alt: string\n  }\n\nfunction getImageSrc({\n  base64,\n  mediaType,\n}: Pick<GeneratedImageLike, \"base64\" | \"mediaType\">) {\n  if (base64 && mediaType) {\n    return `data:${mediaType};base64,${base64}`\n  }\n  return undefined\n}\n\nexport const Image = ({\n  base64,\n  uint8Array,\n  mediaType = \"image/png\",\n  className,\n  alt,\n  ...props\n}: ImageProps) => {\n  const [objectUrl, setObjectUrl] = useState<string | undefined>(undefined)\n\n  useEffect(() => {\n    if (uint8Array && mediaType) {\n      const blob = new Blob([uint8Array as BlobPart], { type: mediaType })\n      const url = URL.createObjectURL(blob)\n      setObjectUrl(url)\n      return () => {\n        URL.revokeObjectURL(url)\n      }\n    }\n    setObjectUrl(undefined)\n    return\n  }, [uint8Array, mediaType])\n\n  const base64Src = getImageSrc({ base64, mediaType })\n  const src = base64Src ?? objectUrl\n\n  if (!src) {\n    return (\n      <div\n        aria-label={alt}\n        role=\"img\"\n        className={cn(\n          \"h-auto max-w-full animate-pulse overflow-hidden rounded-md bg-gray-100 dark:bg-neutral-800\",\n          className\n        )}\n        {...props}\n      />\n    )\n  }\n\n  return (\n    <img\n      src={src}\n      alt={alt}\n      className={cn(\"h-auto max-w-full overflow-hidden rounded-md\", className)}\n      role=\"img\"\n      {...props}\n    />\n  )\n}\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "steps",
      "type": "registry:ui",
      "title": "Steps",
      "description": "A component for displaying a sequence of operations in a collapsible layout. Each step can include details and an optional vertical bar. Useful for showing AI steps like reasoning traces, tool calls, or process logs.",
      "dependencies": [],
      "devDependencies": [],
      "registryDependencies": [
        "collapsible"
      ],
      "files": [
        {
          "path": "components/prompt-kit/steps.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\"\nimport { cn } from \"@/lib/utils\"\nimport { ChevronDown } from \"lucide-react\"\n\nexport type StepsItemProps = React.ComponentProps<\"div\">\n\nexport const StepsItem = ({\n  children,\n  className,\n  ...props\n}: StepsItemProps) => (\n  <div className={cn(\"text-muted-foreground text-sm\", className)} {...props}>\n    {children}\n  </div>\n)\n\nexport type StepsTriggerProps = React.ComponentProps<\n  typeof CollapsibleTrigger\n> & {\n  leftIcon?: React.ReactNode\n  swapIconOnHover?: boolean\n}\n\nexport const StepsTrigger = ({\n  children,\n  className,\n  leftIcon,\n  swapIconOnHover = true,\n  ...props\n}: StepsTriggerProps) => (\n  <CollapsibleTrigger\n    className={cn(\n      \"group text-muted-foreground hover:text-foreground flex w-full cursor-pointer items-center justify-start gap-1 text-sm transition-colors\",\n      className\n    )}\n    {...props}\n  >\n    <div className=\"flex items-center gap-2\">\n      {leftIcon ? (\n        <span className=\"relative inline-flex size-4 items-center justify-center\">\n          <span\n            className={cn(\n              \"transition-opacity\",\n              swapIconOnHover && \"group-hover:opacity-0\"\n            )}\n          >\n            {leftIcon}\n          </span>\n          {swapIconOnHover && (\n            <ChevronDown className=\"absolute size-4 opacity-0 transition-opacity group-hover:opacity-100 group-data-[state=open]:rotate-180\" />\n          )}\n        </span>\n      ) : null}\n      <span>{children}</span>\n    </div>\n    {!leftIcon && (\n      <ChevronDown className=\"size-4 transition-transform group-data-[state=open]:rotate-180\" />\n    )}\n  </CollapsibleTrigger>\n)\n\nexport type StepsContentProps = React.ComponentProps<\n  typeof CollapsibleContent\n> & {\n  bar?: React.ReactNode\n}\n\nexport const StepsContent = ({\n  children,\n  className,\n  bar,\n  ...props\n}: StepsContentProps) => {\n  return (\n    <CollapsibleContent\n      className={cn(\n        \"text-popover-foreground data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down overflow-hidden\",\n        className\n      )}\n      {...props}\n    >\n      <div className=\"mt-3 grid max-w-full min-w-0 grid-cols-[min-content_minmax(0,1fr)] items-start gap-x-3\">\n        <div className=\"min-w-0 self-stretch\">{bar ?? <StepsBar />}</div>\n        <div className=\"min-w-0 space-y-2\">{children}</div>\n      </div>\n    </CollapsibleContent>\n  )\n}\n\nexport type StepsBarProps = React.HTMLAttributes<HTMLDivElement>\n\nexport const StepsBar = ({ className, ...props }: StepsBarProps) => (\n  <div\n    className={cn(\"bg-muted h-full w-[2px]\", className)}\n    aria-hidden\n    {...props}\n  />\n)\n\nexport type StepsProps = React.ComponentProps<typeof Collapsible>\n\nexport function Steps({ defaultOpen = true, className, ...props }: StepsProps) {\n  return (\n    <Collapsible\n      className={cn(className)}\n      defaultOpen={defaultOpen}\n      {...props}\n    />\n  )\n}\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "system-message",
      "type": "registry:ui",
      "title": "System Message",
      "description": "A banner-style component for surfacing contextual information, warnings, or instructions within AI interfaces.",
      "dependencies": [],
      "devDependencies": [],
      "registryDependencies": [
        "button"
      ],
      "files": [
        {
          "path": "components/prompt-kit/system-message.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { cn } from \"@/lib/utils\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { AlertCircle, AlertTriangle, Info } from \"lucide-react\"\nimport React from \"react\"\n\nconst systemMessageVariants = cva(\n  \"flex flex-row items-center gap-3 rounded-[12px] border py-2 pr-2 pl-3\",\n  {\n    variants: {\n      variant: {\n        action: \"text-zinc-700 dark:text-zinc-300\",\n        error: \"text-red-700 dark:text-red-800\",\n        warning: \"text-amber-700 dark:text-amber-700\",\n      },\n      fill: {\n        true: \"bg-background\",\n        false: \"\",\n      },\n    },\n    compoundVariants: [\n      {\n        variant: \"action\",\n        fill: true,\n        class: \"bg-zinc-100 dark:bg-zinc-900 border-transparent\",\n      },\n      {\n        variant: \"error\",\n        fill: true,\n        class: \"bg-red-100 dark:bg-red-900/20 border-transparent\",\n      },\n      {\n        variant: \"warning\",\n        fill: true,\n        class: \"bg-amber-100 dark:bg-amber-900/20 border-transparent\",\n      },\n      {\n        variant: \"action\",\n        fill: false,\n        class: \"border-zinc-200 dark:border-zinc-800\",\n      },\n      {\n        variant: \"error\",\n        fill: false,\n        class: \"border-red-600 dark:border-red-900\",\n      },\n      {\n        variant: \"warning\",\n        fill: false,\n        class: \"border-amber-600 dark:border-amber-900\",\n      },\n    ],\n    defaultVariants: {\n      variant: \"action\",\n      fill: false,\n    },\n  }\n)\n\nexport type SystemMessageProps = React.ComponentProps<\"div\"> &\n  VariantProps<typeof systemMessageVariants> & {\n    icon?: React.ReactNode\n    isIconHidden?: boolean\n    cta?: {\n      label: string\n      onClick?: () => void\n      variant?: \"solid\" | \"outline\" | \"ghost\"\n    }\n  }\n\nexport function SystemMessage({\n  children,\n  variant = \"action\",\n  fill = false,\n  icon,\n  isIconHidden = false,\n  cta,\n  className,\n  ...props\n}: SystemMessageProps) {\n  const getDefaultIcon = () => {\n    if (isIconHidden) return null\n\n    switch (variant) {\n      case \"error\":\n        return <AlertCircle className=\"size-4\" />\n      case \"warning\":\n        return <AlertTriangle className=\"size-4\" />\n      default:\n        return <Info className=\"size-4\" />\n    }\n  }\n\n  const getIconToShow = () => {\n    if (isIconHidden) return null\n    if (icon) return icon\n    return getDefaultIcon()\n  }\n\n  const shouldShowIcon = getIconToShow() !== null\n\n  return (\n    <div\n      className={cn(systemMessageVariants({ variant, fill }), className)}\n      {...props}\n    >\n      <div className=\"flex flex-1 flex-row items-center gap-3 leading-normal\">\n        {shouldShowIcon && (\n          <div className=\"flex h-[1lh] shrink-0 items-center justify-center self-start\">\n            {getIconToShow()}\n          </div>\n        )}\n\n        <div\n          className={cn(\n            \"flex min-w-0 flex-1 items-center\",\n            shouldShowIcon ? \"gap-3\" : \"gap-0\"\n          )}\n        >\n          <div className=\"text-sm\">{children}</div>\n        </div>\n      </div>\n\n      {cta && (\n        <Button variant=\"default\" size=\"sm\" onClick={cta.onClick}>\n          {cta.label}\n        </Button>\n      )}\n    </div>\n  )\n}\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "chain-of-thought",
      "type": "registry:ui",
      "title": "Chain Of Thought",
      "description": "A component for displaying a chain of thought process with collapsible steps and triggers.",
      "dependencies": [
        "lucide-react"
      ],
      "devDependencies": [],
      "registryDependencies": [
        "collapsible"
      ],
      "files": [
        {
          "path": "components/prompt-kit/chain-of-thought.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\"\nimport { cn } from \"@/lib/utils\"\nimport { ChevronDown, Circle } from \"lucide-react\"\nimport React from \"react\"\n\nexport type ChainOfThoughtItemProps = React.ComponentProps<\"div\">\n\nexport const ChainOfThoughtItem = ({\n  children,\n  className,\n  ...props\n}: ChainOfThoughtItemProps) => (\n  <div className={cn(\"text-muted-foreground text-sm\", className)} {...props}>\n    {children}\n  </div>\n)\n\nexport type ChainOfThoughtTriggerProps = React.ComponentProps<\n  typeof CollapsibleTrigger\n> & {\n  leftIcon?: React.ReactNode\n  swapIconOnHover?: boolean\n}\n\nexport const ChainOfThoughtTrigger = ({\n  children,\n  className,\n  leftIcon,\n  swapIconOnHover = true,\n  ...props\n}: ChainOfThoughtTriggerProps) => (\n  <CollapsibleTrigger\n    className={cn(\n      \"group text-muted-foreground hover:text-foreground flex cursor-pointer items-center justify-start gap-1 text-left text-sm transition-colors\",\n      className\n    )}\n    {...props}\n  >\n    <div className=\"flex items-center gap-2\">\n      {leftIcon ? (\n        <span className=\"relative inline-flex size-4 items-center justify-center\">\n          <span\n            className={cn(\n              \"transition-opacity\",\n              swapIconOnHover && \"group-hover:opacity-0\"\n            )}\n          >\n            {leftIcon}\n          </span>\n          {swapIconOnHover && (\n            <ChevronDown className=\"absolute size-4 opacity-0 transition-opacity group-hover:opacity-100 group-data-[state=open]:rotate-180\" />\n          )}\n        </span>\n      ) : (\n        <span className=\"relative inline-flex size-4 items-center justify-center\">\n          <Circle className=\"size-2 fill-current\" />\n        </span>\n      )}\n      <span>{children}</span>\n    </div>\n    {!leftIcon && (\n      <ChevronDown className=\"size-4 transition-transform group-data-[state=open]:rotate-180\" />\n    )}\n  </CollapsibleTrigger>\n)\n\nexport type ChainOfThoughtContentProps = React.ComponentProps<\n  typeof CollapsibleContent\n>\n\nexport const ChainOfThoughtContent = ({\n  children,\n  className,\n  ...props\n}: ChainOfThoughtContentProps) => {\n  return (\n    <CollapsibleContent\n      className={cn(\n        \"text-popover-foreground data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down overflow-hidden\",\n        className\n      )}\n      {...props}\n    >\n      <div className=\"grid grid-cols-[min-content_minmax(0,1fr)] gap-x-4\">\n        <div className=\"bg-primary/20 ml-1.75 h-full w-px group-data-[last=true]:hidden\" />\n        <div className=\"ml-1.75 h-full w-px bg-transparent group-data-[last=false]:hidden\" />\n        <div className=\"mt-2 space-y-2\">{children}</div>\n      </div>\n    </CollapsibleContent>\n  )\n}\n\nexport type ChainOfThoughtProps = {\n  children: React.ReactNode\n  className?: string\n}\n\nexport function ChainOfThought({ children, className }: ChainOfThoughtProps) {\n  const childrenArray = React.Children.toArray(children)\n\n  return (\n    <div className={cn(\"space-y-0\", className)}>\n      {childrenArray.map((child, index) => (\n        <React.Fragment key={index}>\n          {React.isValidElement(child) &&\n            React.cloneElement(\n              child as React.ReactElement<ChainOfThoughtStepProps>,\n              {\n                isLast: index === childrenArray.length - 1,\n              }\n            )}\n        </React.Fragment>\n      ))}\n    </div>\n  )\n}\n\nexport type ChainOfThoughtStepProps = {\n  children: React.ReactNode\n  className?: string\n  isLast?: boolean\n}\n\nexport const ChainOfThoughtStep = ({\n  children,\n  className,\n  isLast = false,\n  ...props\n}: ChainOfThoughtStepProps & React.ComponentProps<typeof Collapsible>) => {\n  return (\n    <Collapsible\n      className={cn(\"group\", className)}\n      data-last={isLast}\n      {...props}\n    >\n      {children}\n      <div className=\"flex justify-start group-data-[last=true]:hidden\">\n        <div className=\"bg-primary/20 ml-1.75 h-4 w-px\" />\n      </div>\n    </Collapsible>\n  )\n}\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "text-shimmer",
      "type": "registry:ui",
      "title": "Text Shimmer",
      "description": "A component for displaying a shimmer effect on text, perfect for loading states or highlighting text.",
      "dependencies": [],
      "devDependencies": [],
      "registryDependencies": [],
      "tailwind": {
        "config": {
          "theme": {
            "keyframes": {
              "shimmer": {
                "0%": {
                  "backgroundPosition": "200% 50%"
                },
                "100%": {
                  "backgroundPosition": "-200% 50%"
                }
              }
            }
          }
        }
      },
      "files": [
        {
          "path": "components/prompt-kit/text-shimmer.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport type TextShimmerProps = {\n  as?: string\n  duration?: number\n  spread?: number\n  children: React.ReactNode\n} & React.HTMLAttributes<HTMLElement>\n\nexport function TextShimmer({\n  as = \"span\",\n  className,\n  duration = 4,\n  spread = 20,\n  children,\n  ...props\n}: TextShimmerProps) {\n  const dynamicSpread = Math.min(Math.max(spread, 5), 45)\n  const Component = as as React.ElementType\n\n  return (\n    <Component\n      className={cn(\n        \"bg-size-[200%_auto] bg-clip-text font-medium text-transparent\",\n        \"animate-[shimmer_4s_infinite_linear]\",\n        className\n      )}\n      style={{\n        backgroundImage: `linear-gradient(to right, var(--muted-foreground) ${50 - dynamicSpread}%, var(--foreground) 50%, var(--muted-foreground) ${50 + dynamicSpread}%)`,\n        animationDuration: `${duration}s`,\n      }}\n      {...props}\n    >\n      {children}\n    </Component>\n  )\n}\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "thinking-bar",
      "type": "registry:ui",
      "title": "Thinking Bar",
      "description": "A component to display the thinking state of an AI model with optional actions.",
      "dependencies": [
        "lucide-react"
      ],
      "devDependencies": [],
      "registryDependencies": [
        "text-shimmer"
      ],
      "files": [
        {
          "path": "components/prompt-kit/thinking-bar.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { TextShimmer } from \"@/components/prompt-kit/text-shimmer\"\nimport { cn } from \"@/lib/utils\"\nimport { ChevronRight } from \"lucide-react\"\n\ntype ThinkingBarProps = {\n  className?: string\n  text?: string\n  onStop?: () => void\n  stopLabel?: string\n  onClick?: () => void\n}\n\nexport function ThinkingBar({\n  className,\n  text = \"Thinking\",\n  onStop,\n  stopLabel = \"Answer now\",\n  onClick,\n}: ThinkingBarProps) {\n  return (\n    <div className={cn(\"flex w-full items-center justify-between\", className)}>\n      {onClick ? (\n        <button\n          type=\"button\"\n          onClick={onClick}\n          className=\"flex items-center gap-1 text-sm transition-opacity hover:opacity-80\"\n        >\n          <TextShimmer className=\"font-medium\">{text}</TextShimmer>\n          <ChevronRight className=\"text-muted-foreground size-4\" />\n        </button>\n      ) : (\n        <TextShimmer className=\"cursor-default font-medium\">{text}</TextShimmer>\n      )}\n      {onStop ? (\n        <button\n          onClick={onStop}\n          type=\"button\"\n          className=\"text-muted-foreground hover:text-foreground border-muted-foreground/50 hover:border-foreground border-b border-dotted text-sm transition-colors\"\n        >\n          {stopLabel}\n        </button>\n      ) : null}\n    </div>\n  )\n}\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "feedback-bar",
      "type": "registry:ui",
      "title": "Feedback Bar",
      "description": "A component to collect user feedback on AI responses.",
      "dependencies": [
        "lucide-react"
      ],
      "devDependencies": [],
      "registryDependencies": [],
      "files": [
        {
          "path": "components/prompt-kit/feedback-bar.tsx",
          "type": "registry:component",
          "content": "import { cn } from \"@/lib/utils\"\nimport { ThumbsDown, ThumbsUp, X } from \"lucide-react\"\n\ntype FeedbackBarProps = {\n  className?: string\n  title?: string\n  icon?: React.ReactNode\n  onHelpful?: () => void\n  onNotHelpful?: () => void\n  onClose?: () => void\n}\n\nexport function FeedbackBar({\n  className,\n  title,\n  icon,\n  onHelpful,\n  onNotHelpful,\n  onClose,\n}: FeedbackBarProps) {\n  return (\n    <div\n      className={cn(\n        \"bg-background border-border inline-flex rounded-[12px] border text-sm\",\n        className\n      )}\n    >\n      <div className=\"flex w-full items-center justify-between\">\n        <div className=\"flex flex-1 items-center justify-start gap-4 py-3 pl-4\">\n          {icon}\n          <span className=\"text-foreground font-medium\">{title}</span>\n        </div>\n        <div className=\"flex items-center justify-center gap-0.5 px-3 py-0\">\n          <button\n            type=\"button\"\n            className=\"text-muted-foreground hover:text-foreground flex size-8 items-center justify-center rounded-md transition-colors\"\n            aria-label=\"Helpful\"\n            onClick={onHelpful}\n          >\n            <ThumbsUp className=\"size-4\" />\n          </button>\n          <button\n            type=\"button\"\n            className=\"text-muted-foreground hover:text-foreground flex size-8 items-center justify-center rounded-md transition-colors\"\n            aria-label=\"Not helpful\"\n            onClick={onNotHelpful}\n          >\n            <ThumbsDown className=\"size-4\" />\n          </button>\n        </div>\n        <div className=\"border-border flex items-center justify-center border-l\">\n          <button\n            type=\"button\"\n            onClick={onClose}\n            className=\"text-muted-foreground hover:text-foreground flex items-center justify-center rounded-md p-3\"\n            aria-label=\"Close\"\n          >\n            <X className=\"size-5\" />\n          </button>\n        </div>\n      </div>\n    </div>\n  )\n}\n"
        }
      ],
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "chatbot",
      "type": "registry:item",
      "title": "Chatbot",
      "description": "A chatbot component that allows users to chat with an AI model. It uses prompt-kit, shadcn/ui, and AI SDK V5.",
      "dependencies": [
        "ai",
        "@ai-sdk/openai",
        "zod",
        "@ai-sdk/react",
        "use-stick-to-bottom",
        "react-markdown",
        "remark-gfm",
        "shiki",
        "marked",
        "remark-breaks"
      ],
      "devDependencies": [],
      "registryDependencies": [
        "avatar",
        "tooltip",
        "textarea"
      ],
      "files": [
        {
          "path": "components/primitives/chatbot.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport {\n  ChatContainerContent,\n  ChatContainerRoot,\n} from \"@/components/prompt-kit/chat-container\"\nimport { DotsLoader } from \"@/components/prompt-kit/loader\"\nimport {\n  Message,\n  MessageAction,\n  MessageActions,\n  MessageContent,\n} from \"@/components/prompt-kit/message\"\nimport {\n  PromptInput,\n  PromptInputActions,\n  PromptInputTextarea,\n} from \"@/components/prompt-kit/prompt-input\"\nimport { Button } from \"@/components/ui/button\"\nimport { cn } from \"@/lib/utils\"\nimport { useChat } from \"@ai-sdk/react\"\nimport { DefaultChatTransport } from \"ai\"\nimport type { UIMessage } from \"ai\"\nimport {\n  AlertTriangle,\n  ArrowUp,\n  Copy,\n  ThumbsDown,\n  ThumbsUp,\n} from \"lucide-react\"\nimport { memo, useState } from \"react\"\n\ntype MessageComponentProps = {\n  message: UIMessage\n  isLastMessage: boolean\n}\n\nexport const MessageComponent = memo(\n  ({ message, isLastMessage }: MessageComponentProps) => {\n    const isAssistant = message.role === \"assistant\"\n\n    return (\n      <Message\n        className={cn(\n          \"mx-auto flex w-full max-w-3xl flex-col gap-2 px-2 md:px-10\",\n          isAssistant ? \"items-start\" : \"items-end\"\n        )}\n      >\n        {isAssistant ? (\n          <div className=\"group flex w-full flex-col gap-0\">\n            <MessageContent\n              className=\"text-foreground prose w-full min-w-0 flex-1 rounded-lg bg-transparent p-0\"\n              markdown\n            >\n              {message.parts\n                .map((part) => (part.type === \"text\" ? part.text : null))\n                .join(\"\")}\n            </MessageContent>\n            <MessageActions\n              className={cn(\n                \"-ml-2.5 flex gap-0 opacity-0 transition-opacity duration-150 group-hover:opacity-100\",\n                isLastMessage && \"opacity-100\"\n              )}\n            >\n              <MessageAction tooltip=\"Copy\" delayDuration={100}>\n                <Button variant=\"ghost\" size=\"icon\" className=\"rounded-full\">\n                  <Copy />\n                </Button>\n              </MessageAction>\n              <MessageAction tooltip=\"Upvote\" delayDuration={100}>\n                <Button variant=\"ghost\" size=\"icon\" className=\"rounded-full\">\n                  <ThumbsUp />\n                </Button>\n              </MessageAction>\n              <MessageAction tooltip=\"Downvote\" delayDuration={100}>\n                <Button variant=\"ghost\" size=\"icon\" className=\"rounded-full\">\n                  <ThumbsDown />\n                </Button>\n              </MessageAction>\n            </MessageActions>\n          </div>\n        ) : (\n          <div className=\"group flex w-full flex-col items-end gap-1\">\n            <MessageContent className=\"bg-muted text-primary max-w-[85%] rounded-3xl px-5 py-2.5 whitespace-pre-wrap sm:max-w-[75%]\">\n              {message.parts\n                .map((part) => (part.type === \"text\" ? part.text : null))\n                .join(\"\")}\n            </MessageContent>\n            <MessageActions\n              className={cn(\n                \"flex gap-0 opacity-0 transition-opacity duration-150 group-hover:opacity-100\"\n              )}\n            >\n              <MessageAction tooltip=\"Copy\" delayDuration={100}>\n                <Button variant=\"ghost\" size=\"icon\" className=\"rounded-full\">\n                  <Copy />\n                </Button>\n              </MessageAction>\n            </MessageActions>\n          </div>\n        )}\n      </Message>\n    )\n  }\n)\n\nMessageComponent.displayName = \"MessageComponent\"\n\nconst LoadingMessage = memo(() => (\n  <Message className=\"mx-auto flex w-full max-w-3xl flex-col items-start gap-2 px-0 md:px-10\">\n    <div className=\"group flex w-full flex-col gap-0\">\n      <div className=\"text-foreground prose w-full min-w-0 flex-1 rounded-lg bg-transparent p-0\">\n        <DotsLoader />\n      </div>\n    </div>\n  </Message>\n))\n\nLoadingMessage.displayName = \"LoadingMessage\"\n\nconst ErrorMessage = memo(({ error }: { error: Error }) => (\n  <Message className=\"not-prose mx-auto flex w-full max-w-3xl flex-col items-start gap-2 px-0 md:px-10\">\n    <div className=\"group flex w-full flex-col items-start gap-0\">\n      <div className=\"text-primary flex min-w-0 flex-1 flex-row items-center gap-2 rounded-lg border-2 border-red-300 bg-red-300/20 px-2 py-1\">\n        <AlertTriangle size={16} className=\"text-red-500\" />\n        <p className=\"text-red-500\">{error.message}</p>\n      </div>\n    </div>\n  </Message>\n))\n\nErrorMessage.displayName = \"ErrorMessage\"\n\nfunction ConversationPromptInput() {\n  const [input, setInput] = useState(\"\")\n\n  const { messages, sendMessage, status, error } = useChat({\n    transport: new DefaultChatTransport({\n      api: \"/api/primitives/chatbot\",\n    }),\n  })\n\n  const handleSubmit = () => {\n    if (!input.trim()) return\n\n    sendMessage({ text: input })\n    setInput(\"\")\n  }\n\n  return (\n    <div className=\"flex h-screen flex-col overflow-hidden\">\n      <ChatContainerRoot className=\"relative flex-1 space-y-0 overflow-y-auto\">\n        <ChatContainerContent className=\"space-y-12 px-4 py-12\">\n          {messages.map((message, index) => {\n            const isLastMessage = index === messages.length - 1\n\n            return (\n              <MessageComponent\n                key={message.id}\n                message={message}\n                isLastMessage={isLastMessage}\n              />\n            )\n          })}\n\n          {status === \"submitted\" && <LoadingMessage />}\n          {status === \"error\" && error && <ErrorMessage error={error} />}\n        </ChatContainerContent>\n      </ChatContainerRoot>\n      <div className=\"inset-x-0 bottom-0 mx-auto w-full max-w-3xl shrink-0 px-3 pb-3 md:px-5 md:pb-5\">\n        <PromptInput\n          isLoading={status !== \"ready\"}\n          value={input}\n          onValueChange={setInput}\n          onSubmit={handleSubmit}\n          className=\"border-input bg-popover relative z-10 w-full rounded-3xl border p-0 pt-1 shadow-xs\"\n        >\n          <div className=\"flex flex-col\">\n            <PromptInputTextarea\n              placeholder=\"Ask anything\"\n              className=\"min-h-[44px] pt-3 pl-4 text-base leading-[1.3] sm:text-base md:text-base\"\n            />\n\n            <PromptInputActions className=\"mt-3 flex w-full items-center justify-between gap-2 p-2\">\n              <div />\n              <div className=\"flex items-center gap-2\">\n                <Button\n                  size=\"icon\"\n                  disabled={\n                    !input.trim() || (status !== \"ready\" && status !== \"error\")\n                  }\n                  onClick={handleSubmit}\n                  className=\"size-9 rounded-full\"\n                >\n                  {status === \"ready\" || status === \"error\" ? (\n                    <ArrowUp size={18} />\n                  ) : (\n                    <span className=\"size-3 rounded-xs bg-white\" />\n                  )}\n                </Button>\n              </div>\n            </PromptInputActions>\n          </div>\n        </PromptInput>\n      </div>\n    </div>\n  )\n}\n\nexport default ConversationPromptInput\n"
        },
        {
          "path": "app/api/primitives/chatbot/route.ts",
          "type": "registry:file",
          "content": "import { openai } from \"@ai-sdk/openai\"\nimport { convertToModelMessages, streamText, tool, UIMessage } from \"ai\"\nimport { z } from \"zod\"\n\nexport const maxDuration = 30\n\nexport async function POST(req: Request) {\n  const { messages }: { messages: UIMessage[] } = await req.json()\n\n  const result = streamText({\n    model: openai(\"gpt-4.1-nano\"),\n    system:\n      \"You are a helpful assistant with access to tools. Use the getCurrentDate tool when users ask about dates, time, or current information. You are also able to use the getTime tool to get the current time in a specific timezone.\",\n    messages: convertToModelMessages(messages),\n    tools: {\n      getTime: tool({\n        description: \"Get the current time in a specific timezone\",\n        inputSchema: z.object({\n          timezone: z\n            .string()\n            .describe(\"A valid IANA timezone, e.g. 'Europe/Paris'\"),\n        }),\n        execute: async ({ timezone }) => {\n          try {\n            const now = new Date()\n            const time = now.toLocaleString(\"en-US\", {\n              timeZone: timezone,\n              hour: \"2-digit\",\n              minute: \"2-digit\",\n              second: \"2-digit\",\n              hour12: false,\n            })\n\n            return { time, timezone }\n          } catch {\n            return { error: \"Invalid timezone format.\" }\n          }\n        },\n      }),\n      getCurrentDate: tool({\n        description: \"Get the current date and time with timezone information\",\n        inputSchema: z.object({}),\n        execute: async () => {\n          const now = new Date()\n          return {\n            timestamp: now.getTime(),\n            iso: now.toISOString(),\n            local: now.toLocaleString(\"en-US\", {\n              weekday: \"long\",\n              year: \"numeric\",\n              month: \"long\",\n              day: \"numeric\",\n              hour: \"2-digit\",\n              minute: \"2-digit\",\n              second: \"2-digit\",\n              timeZoneName: \"short\",\n            }),\n            timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n            utc: now.toUTCString(),\n          }\n        },\n      }),\n    },\n  })\n\n  return result.toUIMessageStreamResponse()\n}\n",
          "target": "app/api/primitives/chatbot/route.ts"
        },
        {
          "path": "components/prompt-kit/chat-container.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport { StickToBottom } from \"use-stick-to-bottom\"\n\nexport type ChatContainerRootProps = {\n  children: React.ReactNode\n  className?: string\n} & React.HTMLAttributes<HTMLDivElement>\n\nexport type ChatContainerContentProps = {\n  children: React.ReactNode\n  className?: string\n} & React.HTMLAttributes<HTMLDivElement>\n\nexport type ChatContainerScrollAnchorProps = {\n  className?: string\n  ref?: React.RefObject<HTMLDivElement>\n} & React.HTMLAttributes<HTMLDivElement>\n\nfunction ChatContainerRoot({\n  children,\n  className,\n  ...props\n}: ChatContainerRootProps) {\n  return (\n    <StickToBottom\n      className={cn(\"flex overflow-y-auto\", className)}\n      resize=\"smooth\"\n      initial=\"instant\"\n      role=\"log\"\n      {...props}\n    >\n      {children}\n    </StickToBottom>\n  )\n}\n\nfunction ChatContainerContent({\n  children,\n  className,\n  ...props\n}: ChatContainerContentProps) {\n  return (\n    <StickToBottom.Content\n      className={cn(\"flex w-full flex-col\", className)}\n      {...props}\n    >\n      {children}\n    </StickToBottom.Content>\n  )\n}\n\nfunction ChatContainerScrollAnchor({\n  className,\n  ...props\n}: ChatContainerScrollAnchorProps) {\n  return (\n    <div\n      className={cn(\"h-px w-full shrink-0 scroll-mt-4\", className)}\n      aria-hidden=\"true\"\n      {...props}\n    />\n  )\n}\n\nexport { ChatContainerRoot, ChatContainerContent, ChatContainerScrollAnchor }\n"
        },
        {
          "path": "components/prompt-kit/loader.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport React from \"react\"\n\nexport interface LoaderProps {\n  variant?:\n    | \"circular\"\n    | \"classic\"\n    | \"pulse\"\n    | \"pulse-dot\"\n    | \"dots\"\n    | \"typing\"\n    | \"wave\"\n    | \"bars\"\n    | \"terminal\"\n    | \"text-blink\"\n    | \"text-shimmer\"\n    | \"loading-dots\"\n  size?: \"sm\" | \"md\" | \"lg\"\n  text?: string\n  className?: string\n}\n\nexport function CircularLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const sizeClasses = {\n    sm: \"size-4\",\n    md: \"size-5\",\n    lg: \"size-6\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"border-primary animate-spin rounded-full border-2 border-t-transparent\",\n        sizeClasses[size],\n        className\n      )}\n    >\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function ClassicLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const sizeClasses = {\n    sm: \"size-4\",\n    md: \"size-5\",\n    lg: \"size-6\",\n  }\n\n  const barSizes = {\n    sm: { height: \"6px\", width: \"1.5px\" },\n    md: { height: \"8px\", width: \"2px\" },\n    lg: { height: \"10px\", width: \"2.5px\" },\n  }\n\n  return (\n    <div className={cn(\"relative\", sizeClasses[size], className)}>\n      <div className=\"absolute h-full w-full\">\n        {[...Array(12)].map((_, i) => (\n          <div\n            key={i}\n            className=\"bg-primary absolute animate-[spinner-fade_1.2s_linear_infinite] rounded-full\"\n            style={{\n              top: \"0\",\n              left: \"50%\",\n              marginLeft:\n                size === \"sm\" ? \"-0.75px\" : size === \"lg\" ? \"-1.25px\" : \"-1px\",\n              transformOrigin: `${size === \"sm\" ? \"0.75px\" : size === \"lg\" ? \"1.25px\" : \"1px\"} ${size === \"sm\" ? \"10px\" : size === \"lg\" ? \"14px\" : \"12px\"}`,\n              transform: `rotate(${i * 30}deg)`,\n              opacity: 0,\n              animationDelay: `${i * 0.1}s`,\n              height: barSizes[size].height,\n              width: barSizes[size].width,\n            }}\n          />\n        ))}\n      </div>\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function PulseLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const sizeClasses = {\n    sm: \"size-4\",\n    md: \"size-5\",\n    lg: \"size-6\",\n  }\n\n  return (\n    <div className={cn(\"relative\", sizeClasses[size], className)}>\n      <div className=\"border-primary absolute inset-0 animate-[thin-pulse_1.5s_ease-in-out_infinite] rounded-full border-2\" />\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function PulseDotLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const sizeClasses = {\n    sm: \"size-1\",\n    md: \"size-2\",\n    lg: \"size-3\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"bg-primary animate-[pulse-dot_1.2s_ease-in-out_infinite] rounded-full\",\n        sizeClasses[size],\n        className\n      )}\n    >\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function DotsLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const dotSizes = {\n    sm: \"h-1.5 w-1.5\",\n    md: \"h-2 w-2\",\n    lg: \"h-2.5 w-2.5\",\n  }\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center space-x-1\",\n        containerSizes[size],\n        className\n      )}\n    >\n      {[...Array(3)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\n            \"bg-primary animate-[bounce-dots_1.4s_ease-in-out_infinite] rounded-full\",\n            dotSizes[size]\n          )}\n          style={{\n            animationDelay: `${i * 160}ms`,\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function TypingLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const dotSizes = {\n    sm: \"h-1 w-1\",\n    md: \"h-1.5 w-1.5\",\n    lg: \"h-2 w-2\",\n  }\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center space-x-1\",\n        containerSizes[size],\n        className\n      )}\n    >\n      {[...Array(3)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\n            \"bg-primary animate-[typing_1s_infinite] rounded-full\",\n            dotSizes[size]\n          )}\n          style={{\n            animationDelay: `${i * 250}ms`,\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function WaveLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const barWidths = {\n    sm: \"w-0.5\",\n    md: \"w-0.5\",\n    lg: \"w-1\",\n  }\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  }\n\n  const heights = {\n    sm: [\"6px\", \"9px\", \"12px\", \"9px\", \"6px\"],\n    md: [\"8px\", \"12px\", \"16px\", \"12px\", \"8px\"],\n    lg: [\"10px\", \"15px\", \"20px\", \"15px\", \"10px\"],\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center gap-0.5\",\n        containerSizes[size],\n        className\n      )}\n    >\n      {[...Array(5)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\n            \"bg-primary animate-[wave_1s_ease-in-out_infinite] rounded-full\",\n            barWidths[size]\n          )}\n          style={{\n            animationDelay: `${i * 100}ms`,\n            height: heights[size][i],\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function BarsLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const barWidths = {\n    sm: \"w-1\",\n    md: \"w-1.5\",\n    lg: \"w-2\",\n  }\n\n  const containerSizes = {\n    sm: \"h-4 gap-1\",\n    md: \"h-5 gap-1.5\",\n    lg: \"h-6 gap-2\",\n  }\n\n  return (\n    <div className={cn(\"flex\", containerSizes[size], className)}>\n      {[...Array(3)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\n            \"bg-primary h-full animate-[wave-bars_1.2s_ease-in-out_infinite]\",\n            barWidths[size]\n          )}\n          style={{\n            animationDelay: `${i * 0.2}s`,\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function TerminalLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const cursorSizes = {\n    sm: \"h-3 w-1.5\",\n    md: \"h-4 w-2\",\n    lg: \"h-5 w-2.5\",\n  }\n\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  }\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center space-x-1\",\n        containerSizes[size],\n        className\n      )}\n    >\n      <span className={cn(\"text-primary font-mono\", textSizes[size])}>\n        {\">\"}\n      </span>\n      <div\n        className={cn(\n          \"bg-primary animate-[blink_1s_step-end_infinite]\",\n          cursorSizes[size]\n        )}\n      />\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function TextBlinkLoader({\n  text = \"Thinking\",\n  className,\n  size = \"md\",\n}: {\n  text?: string\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"animate-[text-blink_2s_ease-in-out_infinite] font-medium\",\n        textSizes[size],\n        className\n      )}\n    >\n      {text}\n    </div>\n  )\n}\n\nexport function TextShimmerLoader({\n  text = \"Thinking\",\n  className,\n  size = \"md\",\n}: {\n  text?: string\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"bg-[linear-gradient(to_right,var(--muted-foreground)_40%,var(--foreground)_60%,var(--muted-foreground)_80%)]\",\n        \"bg-size-[200%_auto] bg-clip-text font-medium text-transparent\",\n        \"animate-[shimmer_4s_infinite_linear]\",\n        textSizes[size],\n        className\n      )}\n    >\n      {text}\n    </div>\n  )\n}\n\nexport function TextDotsLoader({\n  className,\n  text = \"Thinking\",\n  size = \"md\",\n}: {\n  className?: string\n  text?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  }\n\n  return (\n    <div\n      className={cn(\"inline-flex items-center\", className)}\n    >\n      <span className={cn(\"text-primary font-medium\", textSizes[size])}>\n        {text}\n      </span>\n      <span className=\"inline-flex\">\n        <span className=\"text-primary animate-[loading-dots_1.4s_infinite_0.2s]\">\n          .\n        </span>\n        <span className=\"text-primary animate-[loading-dots_1.4s_infinite_0.4s]\">\n          .\n        </span>\n        <span className=\"text-primary animate-[loading-dots_1.4s_infinite_0.6s]\">\n          .\n        </span>\n      </span>\n    </div>\n  )\n}\n\nfunction Loader({\n  variant = \"circular\",\n  size = \"md\",\n  text,\n  className,\n}: LoaderProps) {\n  switch (variant) {\n    case \"circular\":\n      return <CircularLoader size={size} className={className} />\n    case \"classic\":\n      return <ClassicLoader size={size} className={className} />\n    case \"pulse\":\n      return <PulseLoader size={size} className={className} />\n    case \"pulse-dot\":\n      return <PulseDotLoader size={size} className={className} />\n    case \"dots\":\n      return <DotsLoader size={size} className={className} />\n    case \"typing\":\n      return <TypingLoader size={size} className={className} />\n    case \"wave\":\n      return <WaveLoader size={size} className={className} />\n    case \"bars\":\n      return <BarsLoader size={size} className={className} />\n    case \"terminal\":\n      return <TerminalLoader size={size} className={className} />\n    case \"text-blink\":\n      return <TextBlinkLoader text={text} size={size} className={className} />\n    case \"text-shimmer\":\n      return <TextShimmerLoader text={text} size={size} className={className} />\n    case \"loading-dots\":\n      return <TextDotsLoader text={text} size={size} className={className} />\n    default:\n      return <CircularLoader size={size} className={className} />\n  }\n}\n\nexport { Loader }\n"
        },
        {
          "path": "components/prompt-kit/message.tsx",
          "type": "registry:component",
          "content": "import { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\nimport { cn } from \"@/lib/utils\"\nimport { Markdown } from \"./markdown\"\n\nexport type MessageProps = {\n  children: React.ReactNode\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nconst Message = ({ children, className, ...props }: MessageProps) => (\n  <div className={cn(\"flex gap-3\", className)} {...props}>\n    {children}\n  </div>\n)\n\nexport type MessageAvatarProps = {\n  src: string\n  alt: string\n  fallback?: string\n  delayMs?: number\n  className?: string\n}\n\nconst MessageAvatar = ({\n  src,\n  alt,\n  fallback,\n  delayMs,\n  className,\n}: MessageAvatarProps) => {\n  return (\n    <Avatar className={cn(\"h-8 w-8 shrink-0\", className)}>\n      <AvatarImage src={src} alt={alt} />\n      {fallback && (\n        <AvatarFallback delayMs={delayMs}>{fallback}</AvatarFallback>\n      )}\n    </Avatar>\n  )\n}\n\nexport type MessageContentProps = {\n  children: React.ReactNode\n  markdown?: boolean\n  className?: string\n} & React.ComponentProps<typeof Markdown> &\n  React.HTMLProps<HTMLDivElement>\n\nconst MessageContent = ({\n  children,\n  markdown = false,\n  className,\n  ...props\n}: MessageContentProps) => {\n  const classNames = cn(\n    \"rounded-lg p-2 text-foreground bg-secondary prose break-words whitespace-normal\",\n    className\n  )\n\n  return markdown ? (\n    <Markdown className={classNames} {...props}>\n      {children as string}\n    </Markdown>\n  ) : (\n    <div className={classNames} {...props}>\n      {children}\n    </div>\n  )\n}\n\nexport type MessageActionsProps = {\n  children: React.ReactNode\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nconst MessageActions = ({\n  children,\n  className,\n  ...props\n}: MessageActionsProps) => (\n  <div\n    className={cn(\"text-muted-foreground flex items-center gap-2\", className)}\n    {...props}\n  >\n    {children}\n  </div>\n)\n\nexport type MessageActionProps = {\n  className?: string\n  tooltip: React.ReactNode\n  children: React.ReactNode\n  side?: \"top\" | \"bottom\" | \"left\" | \"right\"\n} & React.ComponentProps<typeof Tooltip>\n\nconst MessageAction = ({\n  tooltip,\n  children,\n  className,\n  side = \"top\",\n  ...props\n}: MessageActionProps) => {\n  return (\n    <TooltipProvider>\n      <Tooltip {...props}>\n        <TooltipTrigger asChild>{children}</TooltipTrigger>\n        <TooltipContent side={side} className={className}>\n          {tooltip}\n        </TooltipContent>\n      </Tooltip>\n    </TooltipProvider>\n  )\n}\n\nexport { Message, MessageAvatar, MessageContent, MessageActions, MessageAction }\n"
        },
        {
          "path": "components/prompt-kit/prompt-input.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { Textarea } from \"@/components/ui/textarea\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\nimport { cn } from \"@/lib/utils\"\nimport React, {\n  createContext,\n  useContext,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from \"react\"\n\ntype PromptInputContextType = {\n  isLoading: boolean\n  value: string\n  setValue: (value: string) => void\n  maxHeight: number | string\n  onSubmit?: () => void\n  disabled?: boolean\n  textareaRef: React.RefObject<HTMLTextAreaElement | null>\n}\n\nconst PromptInputContext = createContext<PromptInputContextType>({\n  isLoading: false,\n  value: \"\",\n  setValue: () => {},\n  maxHeight: 240,\n  onSubmit: undefined,\n  disabled: false,\n  textareaRef: React.createRef<HTMLTextAreaElement>(),\n})\n\nfunction usePromptInput() {\n  return useContext(PromptInputContext)\n}\n\nexport type PromptInputProps = {\n  isLoading?: boolean\n  value?: string\n  onValueChange?: (value: string) => void\n  maxHeight?: number | string\n  onSubmit?: () => void\n  children: React.ReactNode\n  className?: string\n  disabled?: boolean\n} & React.ComponentProps<\"div\">\n\nfunction PromptInput({\n  className,\n  isLoading = false,\n  maxHeight = 240,\n  value,\n  onValueChange,\n  onSubmit,\n  children,\n  disabled = false,\n  onClick,\n  ...props\n}: PromptInputProps) {\n  const [internalValue, setInternalValue] = useState(value || \"\")\n  const textareaRef = useRef<HTMLTextAreaElement>(null)\n\n  const handleChange = (newValue: string) => {\n    setInternalValue(newValue)\n    onValueChange?.(newValue)\n  }\n\n  const handleClick: React.MouseEventHandler<HTMLDivElement> = (e) => {\n    if (!disabled) textareaRef.current?.focus()\n    onClick?.(e)\n  }\n\n  return (\n    <TooltipProvider>\n      <PromptInputContext.Provider\n        value={{\n          isLoading,\n          value: value ?? internalValue,\n          setValue: onValueChange ?? handleChange,\n          maxHeight,\n          onSubmit,\n          disabled,\n          textareaRef,\n        }}\n      >\n        <div\n          onClick={handleClick}\n          className={cn(\n            \"border-input bg-background cursor-text rounded-3xl border p-2 shadow-xs\",\n            disabled && \"cursor-not-allowed opacity-60\",\n            className\n          )}\n          {...props}\n        >\n          {children}\n        </div>\n      </PromptInputContext.Provider>\n    </TooltipProvider>\n  )\n}\n\nexport type PromptInputTextareaProps = {\n  disableAutosize?: boolean\n} & React.ComponentProps<typeof Textarea>\n\nfunction PromptInputTextarea({\n  className,\n  onKeyDown,\n  disableAutosize = false,\n  ...props\n}: PromptInputTextareaProps) {\n  const { value, setValue, maxHeight, onSubmit, disabled, textareaRef } =\n    usePromptInput()\n\n  const adjustHeight = (el: HTMLTextAreaElement | null) => {\n    if (!el || disableAutosize) return\n\n    el.style.height = \"auto\"\n\n    if (typeof maxHeight === \"number\") {\n      el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`\n    } else {\n      el.style.height = `min(${el.scrollHeight}px, ${maxHeight})`\n    }\n  }\n\n  const handleRef = (el: HTMLTextAreaElement | null) => {\n    textareaRef.current = el\n    adjustHeight(el)\n  }\n\n  useLayoutEffect(() => {\n    if (!textareaRef.current || disableAutosize) return\n\n    const el = textareaRef.current\n    el.style.height = \"auto\"\n\n    if (typeof maxHeight === \"number\") {\n      el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`\n    } else {\n      el.style.height = `min(${el.scrollHeight}px, ${maxHeight})`\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [value, maxHeight, disableAutosize])\n\n  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n    adjustHeight(e.target)\n    setValue(e.target.value)\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n    if (e.key === \"Enter\" && !e.shiftKey) {\n      e.preventDefault()\n      onSubmit?.()\n    }\n    onKeyDown?.(e)\n  }\n\n  return (\n    <Textarea\n      ref={handleRef}\n      value={value}\n      onChange={handleChange}\n      onKeyDown={handleKeyDown}\n      className={cn(\n        \"text-primary min-h-[44px] w-full resize-none border-none bg-transparent shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0\",\n        className\n      )}\n      rows={1}\n      disabled={disabled}\n      {...props}\n    />\n  )\n}\n\nexport type PromptInputActionsProps = React.HTMLAttributes<HTMLDivElement>\n\nfunction PromptInputActions({\n  children,\n  className,\n  ...props\n}: PromptInputActionsProps) {\n  return (\n    <div className={cn(\"flex items-center gap-2\", className)} {...props}>\n      {children}\n    </div>\n  )\n}\n\nexport type PromptInputActionProps = {\n  className?: string\n  tooltip: React.ReactNode\n  children: React.ReactNode\n  side?: \"top\" | \"bottom\" | \"left\" | \"right\"\n} & React.ComponentProps<typeof Tooltip>\n\nfunction PromptInputAction({\n  tooltip,\n  children,\n  className,\n  side = \"top\",\n  ...props\n}: PromptInputActionProps) {\n  const { disabled } = usePromptInput()\n\n  return (\n    <Tooltip {...props}>\n      <TooltipTrigger\n        asChild\n        disabled={disabled}\n        onClick={(event) => event.stopPropagation()}\n      >\n        {children}\n      </TooltipTrigger>\n      <TooltipContent side={side} className={className}>\n        {tooltip}\n      </TooltipContent>\n    </Tooltip>\n  )\n}\n\nexport {\n  PromptInput,\n  PromptInputTextarea,\n  PromptInputActions,\n  PromptInputAction,\n}\n"
        },
        {
          "path": "components/prompt-kit/markdown.tsx",
          "type": "registry:component",
          "content": "import { cn } from \"@/lib/utils\"\nimport { marked } from \"marked\"\nimport { memo, useId, useMemo } from \"react\"\nimport ReactMarkdown, { Components } from \"react-markdown\"\nimport remarkBreaks from \"remark-breaks\"\nimport remarkGfm from \"remark-gfm\"\nimport { CodeBlock, CodeBlockCode } from \"./code-block\"\n\nexport type MarkdownProps = {\n  children: string\n  id?: string\n  className?: string\n  components?: Partial<Components>\n}\n\nfunction parseMarkdownIntoBlocks(markdown: string): string[] {\n  const tokens = marked.lexer(markdown)\n  return tokens.map((token) => token.raw)\n}\n\nfunction extractLanguage(className?: string): string {\n  if (!className) return \"plaintext\"\n  const match = className.match(/language-(\\w+)/)\n  return match ? match[1] : \"plaintext\"\n}\n\nconst INITIAL_COMPONENTS: Partial<Components> = {\n  code: function CodeComponent({ className, children, ...props }) {\n    const isInline =\n      !props.node?.position?.start.line ||\n      props.node?.position?.start.line === props.node?.position?.end.line\n\n    if (isInline) {\n      return (\n        <span\n          className={cn(\n            \"bg-primary-foreground rounded-sm px-1 font-mono text-sm\",\n            className\n          )}\n          {...props}\n        >\n          {children}\n        </span>\n      )\n    }\n\n    const language = extractLanguage(className)\n\n    return (\n      <CodeBlock className={className}>\n        <CodeBlockCode code={children as string} language={language} />\n      </CodeBlock>\n    )\n  },\n  pre: function PreComponent({ children }) {\n    return <>{children}</>\n  },\n}\n\nconst MemoizedMarkdownBlock = memo(\n  function MarkdownBlock({\n    content,\n    components = INITIAL_COMPONENTS,\n  }: {\n    content: string\n    components?: Partial<Components>\n  }) {\n    return (\n      <ReactMarkdown\n        remarkPlugins={[remarkGfm, remarkBreaks]}\n        components={components}\n      >\n        {content}\n      </ReactMarkdown>\n    )\n  },\n  function propsAreEqual(prevProps, nextProps) {\n    return prevProps.content === nextProps.content\n  }\n)\n\nMemoizedMarkdownBlock.displayName = \"MemoizedMarkdownBlock\"\n\nfunction MarkdownComponent({\n  children,\n  id,\n  className,\n  components = INITIAL_COMPONENTS,\n}: MarkdownProps) {\n  const generatedId = useId()\n  const blockId = id ?? generatedId\n  const blocks = useMemo(() => parseMarkdownIntoBlocks(children), [children])\n\n  return (\n    <div className={className}>\n      {blocks.map((block, index) => (\n        <MemoizedMarkdownBlock\n          key={`${blockId}-block-${index}`}\n          content={block}\n          components={components}\n        />\n      ))}\n    </div>\n  )\n}\n\nconst Markdown = memo(MarkdownComponent)\nMarkdown.displayName = \"Markdown\"\n\nexport { Markdown }\n"
        },
        {
          "path": "components/prompt-kit/code-block.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport React, { useEffect, useState } from \"react\"\nimport { codeToHtml } from \"shiki\"\n\nexport type CodeBlockProps = {\n  children?: React.ReactNode\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nfunction CodeBlock({ children, className, ...props }: CodeBlockProps) {\n  return (\n    <div\n      className={cn(\n        \"not-prose flex w-full flex-col overflow-clip border\",\n        \"border-border bg-card text-card-foreground rounded-xl\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\nexport type CodeBlockCodeProps = {\n  code: string\n  language?: string\n  theme?: string\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nfunction CodeBlockCode({\n  code,\n  language = \"tsx\",\n  theme = \"github-light\",\n  className,\n  ...props\n}: CodeBlockCodeProps) {\n  const [highlightedHtml, setHighlightedHtml] = useState<string | null>(null)\n\n  useEffect(() => {\n    async function highlight() {\n      if (!code) {\n        setHighlightedHtml(\"<pre><code></code></pre>\")\n        return\n      }\n\n      const html = await codeToHtml(code, { lang: language, theme })\n      setHighlightedHtml(html)\n    }\n    highlight()\n  }, [code, language, theme])\n\n  const classNames = cn(\n    \"w-full overflow-x-auto text-[13px] [&>pre]:px-4 [&>pre]:py-4\",\n    className\n  )\n\n  // SSR fallback: render plain code if not hydrated yet\n  return highlightedHtml ? (\n    <div\n      className={classNames}\n      dangerouslySetInnerHTML={{ __html: highlightedHtml }}\n      {...props}\n    />\n  ) : (\n    <div className={classNames} {...props}>\n      <pre>\n        <code>{code}</code>\n      </pre>\n    </div>\n  )\n}\n\nexport type CodeBlockGroupProps = React.HTMLAttributes<HTMLDivElement>\n\nfunction CodeBlockGroup({\n  children,\n  className,\n  ...props\n}: CodeBlockGroupProps) {\n  return (\n    <div\n      className={cn(\"flex items-center justify-between\", className)}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\nexport { CodeBlockGroup, CodeBlockCode, CodeBlock }\n"
        }
      ],
      "envVars": {
        "OPENAI_API_KEY": ""
      },
      "categories": [
        "ai",
        "prompt-kit"
      ]
    },
    {
      "name": "tool-calling",
      "type": "registry:item",
      "title": "Tool calling",
      "description": "A chatbot with tool calling feature. It uses prompt-kit, shadcn/ui, and AI SDK V5.",
      "dependencies": [
        "ai",
        "@ai-sdk/openai",
        "zod",
        "@ai-sdk/react",
        "use-stick-to-bottom",
        "react-markdown",
        "remark-gfm",
        "shiki",
        "marked",
        "remark-breaks"
      ],
      "devDependencies": [],
      "registryDependencies": [
        "avatar",
        "tooltip",
        "textarea",
        "collapsible",
        "button"
      ],
      "files": [
        {
          "path": "components/primitives/tool-calling.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport {\n  ChatContainerContent,\n  ChatContainerRoot,\n} from \"@/components/prompt-kit/chat-container\"\nimport { DotsLoader } from \"@/components/prompt-kit/loader\"\nimport {\n  Message,\n  MessageAction,\n  MessageActions,\n  MessageContent,\n} from \"@/components/prompt-kit/message\"\nimport {\n  PromptInput,\n  PromptInputActions,\n  PromptInputTextarea,\n} from \"@/components/prompt-kit/prompt-input\"\nimport { Tool } from \"@/components/prompt-kit/tool\"\nimport type { ToolPart } from \"@/components/prompt-kit/tool\"\nimport { Button } from \"@/components/ui/button\"\nimport { cn } from \"@/lib/utils\"\nimport { useChat } from \"@ai-sdk/react\"\nimport { DefaultChatTransport } from \"ai\"\nimport type { UIMessage, UIMessagePart } from \"ai\"\nimport {\n  AlertTriangle,\n  ArrowUp,\n  Copy,\n  ThumbsDown,\n  ThumbsUp,\n} from \"lucide-react\"\nimport { memo, useState } from \"react\"\n\ntype MessageComponentProps = {\n  message: UIMessage\n  isLastMessage: boolean\n}\n\nconst renderToolPart = (\n  part: UIMessagePart<any, any>,\n  index: number\n): React.ReactNode => {\n  if (!part.type?.startsWith(\"tool-\")) return null\n\n  return <Tool key={`${part.type}-${index}`} toolPart={part as ToolPart} />\n}\n\nexport const MessageComponent = memo(\n  ({ message, isLastMessage }: MessageComponentProps) => {\n    const isAssistant = message?.role === \"assistant\"\n\n    return (\n      <Message\n        className={cn(\n          \"mx-auto flex w-full max-w-3xl flex-col gap-2 px-2 md:px-10\",\n          isAssistant ? \"items-start\" : \"items-end\"\n        )}\n      >\n        {isAssistant ? (\n          <div className=\"group flex w-full flex-col gap-0 space-y-2\">\n            <div className=\"w-full\">\n              {message?.parts\n                .filter(\n                  (part: any) => part.type && part.type.startsWith(\"tool-\")\n                )\n                .map((part: any, index: number) => renderToolPart(part, index))}\n            </div>\n            <MessageContent\n              className=\"text-foreground prose w-full min-w-0 flex-1 rounded-lg bg-transparent p-0\"\n              markdown\n            >\n              {message?.parts\n                .filter((part: any) => part.type === \"text\")\n                .map((part: any) => part.text)\n                .join(\"\")}\n            </MessageContent>\n\n            <MessageActions\n              className={cn(\n                \"-ml-2.5 flex gap-0 opacity-0 transition-opacity duration-150 group-hover:opacity-100\",\n                isLastMessage && \"opacity-100\"\n              )}\n            >\n              <MessageAction tooltip=\"Copy\" delayDuration={100}>\n                <Button variant=\"ghost\" size=\"icon\" className=\"rounded-full\">\n                  <Copy />\n                </Button>\n              </MessageAction>\n              <MessageAction tooltip=\"Upvote\" delayDuration={100}>\n                <Button variant=\"ghost\" size=\"icon\" className=\"rounded-full\">\n                  <ThumbsUp />\n                </Button>\n              </MessageAction>\n              <MessageAction tooltip=\"Downvote\" delayDuration={100}>\n                <Button variant=\"ghost\" size=\"icon\" className=\"rounded-full\">\n                  <ThumbsDown />\n                </Button>\n              </MessageAction>\n            </MessageActions>\n          </div>\n        ) : (\n          <div className=\"group flex w-full flex-col items-end gap-1\">\n            <MessageContent className=\"bg-muted text-primary max-w-[85%] rounded-3xl px-5 py-2.5 whitespace-pre-wrap sm:max-w-[75%]\">\n              {message?.parts\n                .map((part: any) => (part.type === \"text\" ? part.text : null))\n                .join(\"\")}\n            </MessageContent>\n            <MessageActions\n              className={cn(\n                \"flex gap-0 opacity-0 transition-opacity duration-150 group-hover:opacity-100\"\n              )}\n            >\n              <MessageAction tooltip=\"Copy\" delayDuration={100}>\n                <Button variant=\"ghost\" size=\"icon\" className=\"rounded-full\">\n                  <Copy />\n                </Button>\n              </MessageAction>\n            </MessageActions>\n          </div>\n        )}\n      </Message>\n    )\n  }\n)\n\nMessageComponent.displayName = \"MessageComponent\"\n\nconst LoadingMessage = memo(() => (\n  <Message className=\"mx-auto flex w-full max-w-3xl flex-col items-start gap-2 px-2 md:px-10\">\n    <div className=\"group flex w-full flex-col gap-0\">\n      <div className=\"text-foreground prose w-full min-w-0 flex-1 rounded-lg bg-transparent p-0\">\n        <DotsLoader />\n      </div>\n    </div>\n  </Message>\n))\n\nLoadingMessage.displayName = \"LoadingMessage\"\n\nconst ErrorMessage = memo(({ error }: { error: Error }) => (\n  <Message className=\"not-prose mx-auto flex w-full max-w-3xl flex-col items-start gap-2 px-0 md:px-10\">\n    <div className=\"group flex w-full flex-col items-start gap-0\">\n      <div className=\"text-primary flex min-w-0 flex-1 flex-row items-center gap-2 rounded-lg border-2 border-red-300 bg-red-300/20 px-2 py-1\">\n        <AlertTriangle size={16} className=\"text-red-500\" />\n        <p className=\"text-red-500\">{error.message}</p>\n      </div>\n    </div>\n  </Message>\n))\n\nErrorMessage.displayName = \"ErrorMessage\"\n\nfunction ToolCallingChatbot() {\n  const [input, setInput] = useState(\"\")\n\n  const { messages, sendMessage, status, error } = useChat({\n    transport: new DefaultChatTransport({\n      api: \"/api/primitives/tool-calling\",\n    }),\n  })\n\n  const handleSubmit = () => {\n    if (!input.trim()) return\n\n    sendMessage({ text: input })\n    setInput(\"\")\n  }\n\n  return (\n    <div className=\"flex h-screen flex-col overflow-hidden\">\n      <ChatContainerRoot className=\"relative flex-1 space-y-0 overflow-y-auto\">\n        <ChatContainerContent className=\"space-y-12 px-4 py-12\">\n          {messages.length === 0 && (\n            <div className=\"mx-auto w-full max-w-3xl shrink-0 px-3 pb-3 md:px-5 md:pb-5\">\n              <div className=\"text-foreground mb-2 font-medium\">\n                Try asking:\n              </div>\n              <ul className=\"list-inside list-disc space-y-1\">\n                <li>what's the current date?</li>\n                <li>what time is it in Tokyo?</li>\n                <li>give me the current time in Europe/Paris</li>\n              </ul>\n            </div>\n          )}\n\n          {messages?.map((message, index) => {\n            const isLastMessage = index === messages.length - 1\n\n            return (\n              <MessageComponent\n                key={message.id}\n                message={message}\n                isLastMessage={isLastMessage}\n              />\n            )\n          })}\n\n          {status === \"submitted\" && <LoadingMessage />}\n          {status === \"error\" && error && <ErrorMessage error={error} />}\n        </ChatContainerContent>\n      </ChatContainerRoot>\n\n      <div className=\"inset-x-0 bottom-0 mx-auto w-full max-w-3xl shrink-0 px-3 pb-3 md:px-5 md:pb-5\">\n        <PromptInput\n          isLoading={status !== \"ready\"}\n          value={input}\n          onValueChange={setInput}\n          onSubmit={handleSubmit}\n          className=\"border-input bg-popover relative z-10 w-full rounded-3xl border p-0 pt-1 shadow-xs\"\n        >\n          <div className=\"flex flex-col\">\n            <PromptInputTextarea\n              placeholder=\"Ask anything\"\n              className=\"min-h-[44px] pt-3 pl-4 text-base leading-[1.3] sm:text-base md:text-base\"\n            />\n\n            <PromptInputActions className=\"mt-3 flex w-full items-center justify-between gap-2 p-2\">\n              <div />\n              <div className=\"flex items-center gap-2\">\n                <Button\n                  size=\"icon\"\n                  disabled={\n                    !input.trim() || (status !== \"ready\" && status !== \"error\")\n                  }\n                  onClick={handleSubmit}\n                  className=\"size-9 rounded-full\"\n                >\n                  {status === \"ready\" || status === \"error\" ? (\n                    <ArrowUp size={18} />\n                  ) : (\n                    <span className=\"size-3 rounded-xs bg-white\" />\n                  )}\n                </Button>\n              </div>\n            </PromptInputActions>\n          </div>\n        </PromptInput>\n      </div>\n    </div>\n  )\n}\n\nexport default ToolCallingChatbot\n"
        },
        {
          "path": "app/api/primitives/tool-calling/route.ts",
          "type": "registry:file",
          "content": "import { openai } from \"@ai-sdk/openai\"\nimport {\n  convertToModelMessages,\n  stepCountIs,\n  streamText,\n  tool,\n  UIMessage,\n} from \"ai\"\nimport { z } from \"zod\"\n\nexport const maxDuration = 30\n\nexport async function POST(req: Request) {\n  const { messages }: { messages: UIMessage[] } = await req.json()\n\n  const result = streamText({\n    model: openai(\"gpt-4.1-nano\"),\n    system:\n      \"You are a helpful assistant with access to tools. Use the getCurrentDate tool when users ask about dates, time, or current information. You are also able to use the getTime tool to get the current time in a specific timezone.\",\n    messages: convertToModelMessages(messages),\n    stopWhen: stepCountIs(5),\n    tools: {\n      getTime: tool({\n        description: \"Get the current time in a specific timezone\",\n        inputSchema: z.object({\n          timezone: z\n            .string()\n            .describe(\"A valid IANA timezone, e.g. 'Europe/Paris'\"),\n        }),\n        execute: async ({ timezone }) => {\n          try {\n            const now = new Date()\n            const time = now.toLocaleString(\"en-US\", {\n              timeZone: timezone,\n              hour: \"2-digit\",\n              minute: \"2-digit\",\n              second: \"2-digit\",\n              hour12: false,\n            })\n\n            return { time, timezone }\n          } catch {\n            return { error: \"Invalid timezone format.\" }\n          }\n        },\n      }),\n      getCurrentDate: tool({\n        description: \"Get the current date and time with timezone information\",\n        inputSchema: z.object({}),\n        execute: async () => {\n          const now = new Date()\n          return {\n            timestamp: now.getTime(),\n            iso: now.toISOString(),\n            local: now.toLocaleString(\"en-US\", {\n              weekday: \"long\",\n              year: \"numeric\",\n              month: \"long\",\n              day: \"numeric\",\n              hour: \"2-digit\",\n              minute: \"2-digit\",\n              second: \"2-digit\",\n              timeZoneName: \"short\",\n            }),\n            timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n            utc: now.toUTCString(),\n          }\n        },\n      }),\n    },\n  })\n\n  return result.toUIMessageStreamResponse()\n}\n",
          "target": "app/api/primitives/tool-calling/route.ts"
        },
        {
          "path": "components/prompt-kit/chat-container.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport { StickToBottom } from \"use-stick-to-bottom\"\n\nexport type ChatContainerRootProps = {\n  children: React.ReactNode\n  className?: string\n} & React.HTMLAttributes<HTMLDivElement>\n\nexport type ChatContainerContentProps = {\n  children: React.ReactNode\n  className?: string\n} & React.HTMLAttributes<HTMLDivElement>\n\nexport type ChatContainerScrollAnchorProps = {\n  className?: string\n  ref?: React.RefObject<HTMLDivElement>\n} & React.HTMLAttributes<HTMLDivElement>\n\nfunction ChatContainerRoot({\n  children,\n  className,\n  ...props\n}: ChatContainerRootProps) {\n  return (\n    <StickToBottom\n      className={cn(\"flex overflow-y-auto\", className)}\n      resize=\"smooth\"\n      initial=\"instant\"\n      role=\"log\"\n      {...props}\n    >\n      {children}\n    </StickToBottom>\n  )\n}\n\nfunction ChatContainerContent({\n  children,\n  className,\n  ...props\n}: ChatContainerContentProps) {\n  return (\n    <StickToBottom.Content\n      className={cn(\"flex w-full flex-col\", className)}\n      {...props}\n    >\n      {children}\n    </StickToBottom.Content>\n  )\n}\n\nfunction ChatContainerScrollAnchor({\n  className,\n  ...props\n}: ChatContainerScrollAnchorProps) {\n  return (\n    <div\n      className={cn(\"h-px w-full shrink-0 scroll-mt-4\", className)}\n      aria-hidden=\"true\"\n      {...props}\n    />\n  )\n}\n\nexport { ChatContainerRoot, ChatContainerContent, ChatContainerScrollAnchor }\n"
        },
        {
          "path": "components/prompt-kit/loader.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport React from \"react\"\n\nexport interface LoaderProps {\n  variant?:\n    | \"circular\"\n    | \"classic\"\n    | \"pulse\"\n    | \"pulse-dot\"\n    | \"dots\"\n    | \"typing\"\n    | \"wave\"\n    | \"bars\"\n    | \"terminal\"\n    | \"text-blink\"\n    | \"text-shimmer\"\n    | \"loading-dots\"\n  size?: \"sm\" | \"md\" | \"lg\"\n  text?: string\n  className?: string\n}\n\nexport function CircularLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const sizeClasses = {\n    sm: \"size-4\",\n    md: \"size-5\",\n    lg: \"size-6\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"border-primary animate-spin rounded-full border-2 border-t-transparent\",\n        sizeClasses[size],\n        className\n      )}\n    >\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function ClassicLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const sizeClasses = {\n    sm: \"size-4\",\n    md: \"size-5\",\n    lg: \"size-6\",\n  }\n\n  const barSizes = {\n    sm: { height: \"6px\", width: \"1.5px\" },\n    md: { height: \"8px\", width: \"2px\" },\n    lg: { height: \"10px\", width: \"2.5px\" },\n  }\n\n  return (\n    <div className={cn(\"relative\", sizeClasses[size], className)}>\n      <div className=\"absolute h-full w-full\">\n        {[...Array(12)].map((_, i) => (\n          <div\n            key={i}\n            className=\"bg-primary absolute animate-[spinner-fade_1.2s_linear_infinite] rounded-full\"\n            style={{\n              top: \"0\",\n              left: \"50%\",\n              marginLeft:\n                size === \"sm\" ? \"-0.75px\" : size === \"lg\" ? \"-1.25px\" : \"-1px\",\n              transformOrigin: `${size === \"sm\" ? \"0.75px\" : size === \"lg\" ? \"1.25px\" : \"1px\"} ${size === \"sm\" ? \"10px\" : size === \"lg\" ? \"14px\" : \"12px\"}`,\n              transform: `rotate(${i * 30}deg)`,\n              opacity: 0,\n              animationDelay: `${i * 0.1}s`,\n              height: barSizes[size].height,\n              width: barSizes[size].width,\n            }}\n          />\n        ))}\n      </div>\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function PulseLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const sizeClasses = {\n    sm: \"size-4\",\n    md: \"size-5\",\n    lg: \"size-6\",\n  }\n\n  return (\n    <div className={cn(\"relative\", sizeClasses[size], className)}>\n      <div className=\"border-primary absolute inset-0 animate-[thin-pulse_1.5s_ease-in-out_infinite] rounded-full border-2\" />\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function PulseDotLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const sizeClasses = {\n    sm: \"size-1\",\n    md: \"size-2\",\n    lg: \"size-3\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"bg-primary animate-[pulse-dot_1.2s_ease-in-out_infinite] rounded-full\",\n        sizeClasses[size],\n        className\n      )}\n    >\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function DotsLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const dotSizes = {\n    sm: \"h-1.5 w-1.5\",\n    md: \"h-2 w-2\",\n    lg: \"h-2.5 w-2.5\",\n  }\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center space-x-1\",\n        containerSizes[size],\n        className\n      )}\n    >\n      {[...Array(3)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\n            \"bg-primary animate-[bounce-dots_1.4s_ease-in-out_infinite] rounded-full\",\n            dotSizes[size]\n          )}\n          style={{\n            animationDelay: `${i * 160}ms`,\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function TypingLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const dotSizes = {\n    sm: \"h-1 w-1\",\n    md: \"h-1.5 w-1.5\",\n    lg: \"h-2 w-2\",\n  }\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center space-x-1\",\n        containerSizes[size],\n        className\n      )}\n    >\n      {[...Array(3)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\n            \"bg-primary animate-[typing_1s_infinite] rounded-full\",\n            dotSizes[size]\n          )}\n          style={{\n            animationDelay: `${i * 250}ms`,\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function WaveLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const barWidths = {\n    sm: \"w-0.5\",\n    md: \"w-0.5\",\n    lg: \"w-1\",\n  }\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  }\n\n  const heights = {\n    sm: [\"6px\", \"9px\", \"12px\", \"9px\", \"6px\"],\n    md: [\"8px\", \"12px\", \"16px\", \"12px\", \"8px\"],\n    lg: [\"10px\", \"15px\", \"20px\", \"15px\", \"10px\"],\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center gap-0.5\",\n        containerSizes[size],\n        className\n      )}\n    >\n      {[...Array(5)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\n            \"bg-primary animate-[wave_1s_ease-in-out_infinite] rounded-full\",\n            barWidths[size]\n          )}\n          style={{\n            animationDelay: `${i * 100}ms`,\n            height: heights[size][i],\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function BarsLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const barWidths = {\n    sm: \"w-1\",\n    md: \"w-1.5\",\n    lg: \"w-2\",\n  }\n\n  const containerSizes = {\n    sm: \"h-4 gap-1\",\n    md: \"h-5 gap-1.5\",\n    lg: \"h-6 gap-2\",\n  }\n\n  return (\n    <div className={cn(\"flex\", containerSizes[size], className)}>\n      {[...Array(3)].map((_, i) => (\n        <div\n          key={i}\n          className={cn(\n            \"bg-primary h-full animate-[wave-bars_1.2s_ease-in-out_infinite]\",\n            barWidths[size]\n          )}\n          style={{\n            animationDelay: `${i * 0.2}s`,\n          }}\n        />\n      ))}\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function TerminalLoader({\n  className,\n  size = \"md\",\n}: {\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const cursorSizes = {\n    sm: \"h-3 w-1.5\",\n    md: \"h-4 w-2\",\n    lg: \"h-5 w-2.5\",\n  }\n\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  }\n\n  const containerSizes = {\n    sm: \"h-4\",\n    md: \"h-5\",\n    lg: \"h-6\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center space-x-1\",\n        containerSizes[size],\n        className\n      )}\n    >\n      <span className={cn(\"text-primary font-mono\", textSizes[size])}>\n        {\">\"}\n      </span>\n      <div\n        className={cn(\n          \"bg-primary animate-[blink_1s_step-end_infinite]\",\n          cursorSizes[size]\n        )}\n      />\n      <span className=\"sr-only\">Loading</span>\n    </div>\n  )\n}\n\nexport function TextBlinkLoader({\n  text = \"Thinking\",\n  className,\n  size = \"md\",\n}: {\n  text?: string\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"animate-[text-blink_2s_ease-in-out_infinite] font-medium\",\n        textSizes[size],\n        className\n      )}\n    >\n      {text}\n    </div>\n  )\n}\n\nexport function TextShimmerLoader({\n  text = \"Thinking\",\n  className,\n  size = \"md\",\n}: {\n  text?: string\n  className?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  }\n\n  return (\n    <div\n      className={cn(\n        \"bg-[linear-gradient(to_right,var(--muted-foreground)_40%,var(--foreground)_60%,var(--muted-foreground)_80%)]\",\n        \"bg-size-[200%_auto] bg-clip-text font-medium text-transparent\",\n        \"animate-[shimmer_4s_infinite_linear]\",\n        textSizes[size],\n        className\n      )}\n    >\n      {text}\n    </div>\n  )\n}\n\nexport function TextDotsLoader({\n  className,\n  text = \"Thinking\",\n  size = \"md\",\n}: {\n  className?: string\n  text?: string\n  size?: \"sm\" | \"md\" | \"lg\"\n}) {\n  const textSizes = {\n    sm: \"text-xs\",\n    md: \"text-sm\",\n    lg: \"text-base\",\n  }\n\n  return (\n    <div\n      className={cn(\"inline-flex items-center\", className)}\n    >\n      <span className={cn(\"text-primary font-medium\", textSizes[size])}>\n        {text}\n      </span>\n      <span className=\"inline-flex\">\n        <span className=\"text-primary animate-[loading-dots_1.4s_infinite_0.2s]\">\n          .\n        </span>\n        <span className=\"text-primary animate-[loading-dots_1.4s_infinite_0.4s]\">\n          .\n        </span>\n        <span className=\"text-primary animate-[loading-dots_1.4s_infinite_0.6s]\">\n          .\n        </span>\n      </span>\n    </div>\n  )\n}\n\nfunction Loader({\n  variant = \"circular\",\n  size = \"md\",\n  text,\n  className,\n}: LoaderProps) {\n  switch (variant) {\n    case \"circular\":\n      return <CircularLoader size={size} className={className} />\n    case \"classic\":\n      return <ClassicLoader size={size} className={className} />\n    case \"pulse\":\n      return <PulseLoader size={size} className={className} />\n    case \"pulse-dot\":\n      return <PulseDotLoader size={size} className={className} />\n    case \"dots\":\n      return <DotsLoader size={size} className={className} />\n    case \"typing\":\n      return <TypingLoader size={size} className={className} />\n    case \"wave\":\n      return <WaveLoader size={size} className={className} />\n    case \"bars\":\n      return <BarsLoader size={size} className={className} />\n    case \"terminal\":\n      return <TerminalLoader size={size} className={className} />\n    case \"text-blink\":\n      return <TextBlinkLoader text={text} size={size} className={className} />\n    case \"text-shimmer\":\n      return <TextShimmerLoader text={text} size={size} className={className} />\n    case \"loading-dots\":\n      return <TextDotsLoader text={text} size={size} className={className} />\n    default:\n      return <CircularLoader size={size} className={className} />\n  }\n}\n\nexport { Loader }\n"
        },
        {
          "path": "components/prompt-kit/message.tsx",
          "type": "registry:component",
          "content": "import { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\nimport { cn } from \"@/lib/utils\"\nimport { Markdown } from \"./markdown\"\n\nexport type MessageProps = {\n  children: React.ReactNode\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nconst Message = ({ children, className, ...props }: MessageProps) => (\n  <div className={cn(\"flex gap-3\", className)} {...props}>\n    {children}\n  </div>\n)\n\nexport type MessageAvatarProps = {\n  src: string\n  alt: string\n  fallback?: string\n  delayMs?: number\n  className?: string\n}\n\nconst MessageAvatar = ({\n  src,\n  alt,\n  fallback,\n  delayMs,\n  className,\n}: MessageAvatarProps) => {\n  return (\n    <Avatar className={cn(\"h-8 w-8 shrink-0\", className)}>\n      <AvatarImage src={src} alt={alt} />\n      {fallback && (\n        <AvatarFallback delayMs={delayMs}>{fallback}</AvatarFallback>\n      )}\n    </Avatar>\n  )\n}\n\nexport type MessageContentProps = {\n  children: React.ReactNode\n  markdown?: boolean\n  className?: string\n} & React.ComponentProps<typeof Markdown> &\n  React.HTMLProps<HTMLDivElement>\n\nconst MessageContent = ({\n  children,\n  markdown = false,\n  className,\n  ...props\n}: MessageContentProps) => {\n  const classNames = cn(\n    \"rounded-lg p-2 text-foreground bg-secondary prose break-words whitespace-normal\",\n    className\n  )\n\n  return markdown ? (\n    <Markdown className={classNames} {...props}>\n      {children as string}\n    </Markdown>\n  ) : (\n    <div className={classNames} {...props}>\n      {children}\n    </div>\n  )\n}\n\nexport type MessageActionsProps = {\n  children: React.ReactNode\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nconst MessageActions = ({\n  children,\n  className,\n  ...props\n}: MessageActionsProps) => (\n  <div\n    className={cn(\"text-muted-foreground flex items-center gap-2\", className)}\n    {...props}\n  >\n    {children}\n  </div>\n)\n\nexport type MessageActionProps = {\n  className?: string\n  tooltip: React.ReactNode\n  children: React.ReactNode\n  side?: \"top\" | \"bottom\" | \"left\" | \"right\"\n} & React.ComponentProps<typeof Tooltip>\n\nconst MessageAction = ({\n  tooltip,\n  children,\n  className,\n  side = \"top\",\n  ...props\n}: MessageActionProps) => {\n  return (\n    <TooltipProvider>\n      <Tooltip {...props}>\n        <TooltipTrigger asChild>{children}</TooltipTrigger>\n        <TooltipContent side={side} className={className}>\n          {tooltip}\n        </TooltipContent>\n      </Tooltip>\n    </TooltipProvider>\n  )\n}\n\nexport { Message, MessageAvatar, MessageContent, MessageActions, MessageAction }\n"
        },
        {
          "path": "components/prompt-kit/prompt-input.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { Textarea } from \"@/components/ui/textarea\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\nimport { cn } from \"@/lib/utils\"\nimport React, {\n  createContext,\n  useContext,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from \"react\"\n\ntype PromptInputContextType = {\n  isLoading: boolean\n  value: string\n  setValue: (value: string) => void\n  maxHeight: number | string\n  onSubmit?: () => void\n  disabled?: boolean\n  textareaRef: React.RefObject<HTMLTextAreaElement | null>\n}\n\nconst PromptInputContext = createContext<PromptInputContextType>({\n  isLoading: false,\n  value: \"\",\n  setValue: () => {},\n  maxHeight: 240,\n  onSubmit: undefined,\n  disabled: false,\n  textareaRef: React.createRef<HTMLTextAreaElement>(),\n})\n\nfunction usePromptInput() {\n  return useContext(PromptInputContext)\n}\n\nexport type PromptInputProps = {\n  isLoading?: boolean\n  value?: string\n  onValueChange?: (value: string) => void\n  maxHeight?: number | string\n  onSubmit?: () => void\n  children: React.ReactNode\n  className?: string\n  disabled?: boolean\n} & React.ComponentProps<\"div\">\n\nfunction PromptInput({\n  className,\n  isLoading = false,\n  maxHeight = 240,\n  value,\n  onValueChange,\n  onSubmit,\n  children,\n  disabled = false,\n  onClick,\n  ...props\n}: PromptInputProps) {\n  const [internalValue, setInternalValue] = useState(value || \"\")\n  const textareaRef = useRef<HTMLTextAreaElement>(null)\n\n  const handleChange = (newValue: string) => {\n    setInternalValue(newValue)\n    onValueChange?.(newValue)\n  }\n\n  const handleClick: React.MouseEventHandler<HTMLDivElement> = (e) => {\n    if (!disabled) textareaRef.current?.focus()\n    onClick?.(e)\n  }\n\n  return (\n    <TooltipProvider>\n      <PromptInputContext.Provider\n        value={{\n          isLoading,\n          value: value ?? internalValue,\n          setValue: onValueChange ?? handleChange,\n          maxHeight,\n          onSubmit,\n          disabled,\n          textareaRef,\n        }}\n      >\n        <div\n          onClick={handleClick}\n          className={cn(\n            \"border-input bg-background cursor-text rounded-3xl border p-2 shadow-xs\",\n            disabled && \"cursor-not-allowed opacity-60\",\n            className\n          )}\n          {...props}\n        >\n          {children}\n        </div>\n      </PromptInputContext.Provider>\n    </TooltipProvider>\n  )\n}\n\nexport type PromptInputTextareaProps = {\n  disableAutosize?: boolean\n} & React.ComponentProps<typeof Textarea>\n\nfunction PromptInputTextarea({\n  className,\n  onKeyDown,\n  disableAutosize = false,\n  ...props\n}: PromptInputTextareaProps) {\n  const { value, setValue, maxHeight, onSubmit, disabled, textareaRef } =\n    usePromptInput()\n\n  const adjustHeight = (el: HTMLTextAreaElement | null) => {\n    if (!el || disableAutosize) return\n\n    el.style.height = \"auto\"\n\n    if (typeof maxHeight === \"number\") {\n      el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`\n    } else {\n      el.style.height = `min(${el.scrollHeight}px, ${maxHeight})`\n    }\n  }\n\n  const handleRef = (el: HTMLTextAreaElement | null) => {\n    textareaRef.current = el\n    adjustHeight(el)\n  }\n\n  useLayoutEffect(() => {\n    if (!textareaRef.current || disableAutosize) return\n\n    const el = textareaRef.current\n    el.style.height = \"auto\"\n\n    if (typeof maxHeight === \"number\") {\n      el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`\n    } else {\n      el.style.height = `min(${el.scrollHeight}px, ${maxHeight})`\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [value, maxHeight, disableAutosize])\n\n  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n    adjustHeight(e.target)\n    setValue(e.target.value)\n  }\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n    if (e.key === \"Enter\" && !e.shiftKey) {\n      e.preventDefault()\n      onSubmit?.()\n    }\n    onKeyDown?.(e)\n  }\n\n  return (\n    <Textarea\n      ref={handleRef}\n      value={value}\n      onChange={handleChange}\n      onKeyDown={handleKeyDown}\n      className={cn(\n        \"text-primary min-h-[44px] w-full resize-none border-none bg-transparent shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0\",\n        className\n      )}\n      rows={1}\n      disabled={disabled}\n      {...props}\n    />\n  )\n}\n\nexport type PromptInputActionsProps = React.HTMLAttributes<HTMLDivElement>\n\nfunction PromptInputActions({\n  children,\n  className,\n  ...props\n}: PromptInputActionsProps) {\n  return (\n    <div className={cn(\"flex items-center gap-2\", className)} {...props}>\n      {children}\n    </div>\n  )\n}\n\nexport type PromptInputActionProps = {\n  className?: string\n  tooltip: React.ReactNode\n  children: React.ReactNode\n  side?: \"top\" | \"bottom\" | \"left\" | \"right\"\n} & React.ComponentProps<typeof Tooltip>\n\nfunction PromptInputAction({\n  tooltip,\n  children,\n  className,\n  side = \"top\",\n  ...props\n}: PromptInputActionProps) {\n  const { disabled } = usePromptInput()\n\n  return (\n    <Tooltip {...props}>\n      <TooltipTrigger\n        asChild\n        disabled={disabled}\n        onClick={(event) => event.stopPropagation()}\n      >\n        {children}\n      </TooltipTrigger>\n      <TooltipContent side={side} className={className}>\n        {tooltip}\n      </TooltipContent>\n    </Tooltip>\n  )\n}\n\nexport {\n  PromptInput,\n  PromptInputTextarea,\n  PromptInputActions,\n  PromptInputAction,\n}\n"
        },
        {
          "path": "components/prompt-kit/markdown.tsx",
          "type": "registry:component",
          "content": "import { cn } from \"@/lib/utils\"\nimport { marked } from \"marked\"\nimport { memo, useId, useMemo } from \"react\"\nimport ReactMarkdown, { Components } from \"react-markdown\"\nimport remarkBreaks from \"remark-breaks\"\nimport remarkGfm from \"remark-gfm\"\nimport { CodeBlock, CodeBlockCode } from \"./code-block\"\n\nexport type MarkdownProps = {\n  children: string\n  id?: string\n  className?: string\n  components?: Partial<Components>\n}\n\nfunction parseMarkdownIntoBlocks(markdown: string): string[] {\n  const tokens = marked.lexer(markdown)\n  return tokens.map((token) => token.raw)\n}\n\nfunction extractLanguage(className?: string): string {\n  if (!className) return \"plaintext\"\n  const match = className.match(/language-(\\w+)/)\n  return match ? match[1] : \"plaintext\"\n}\n\nconst INITIAL_COMPONENTS: Partial<Components> = {\n  code: function CodeComponent({ className, children, ...props }) {\n    const isInline =\n      !props.node?.position?.start.line ||\n      props.node?.position?.start.line === props.node?.position?.end.line\n\n    if (isInline) {\n      return (\n        <span\n          className={cn(\n            \"bg-primary-foreground rounded-sm px-1 font-mono text-sm\",\n            className\n          )}\n          {...props}\n        >\n          {children}\n        </span>\n      )\n    }\n\n    const language = extractLanguage(className)\n\n    return (\n      <CodeBlock className={className}>\n        <CodeBlockCode code={children as string} language={language} />\n      </CodeBlock>\n    )\n  },\n  pre: function PreComponent({ children }) {\n    return <>{children}</>\n  },\n}\n\nconst MemoizedMarkdownBlock = memo(\n  function MarkdownBlock({\n    content,\n    components = INITIAL_COMPONENTS,\n  }: {\n    content: string\n    components?: Partial<Components>\n  }) {\n    return (\n      <ReactMarkdown\n        remarkPlugins={[remarkGfm, remarkBreaks]}\n        components={components}\n      >\n        {content}\n      </ReactMarkdown>\n    )\n  },\n  function propsAreEqual(prevProps, nextProps) {\n    return prevProps.content === nextProps.content\n  }\n)\n\nMemoizedMarkdownBlock.displayName = \"MemoizedMarkdownBlock\"\n\nfunction MarkdownComponent({\n  children,\n  id,\n  className,\n  components = INITIAL_COMPONENTS,\n}: MarkdownProps) {\n  const generatedId = useId()\n  const blockId = id ?? generatedId\n  const blocks = useMemo(() => parseMarkdownIntoBlocks(children), [children])\n\n  return (\n    <div className={className}>\n      {blocks.map((block, index) => (\n        <MemoizedMarkdownBlock\n          key={`${blockId}-block-${index}`}\n          content={block}\n          components={components}\n        />\n      ))}\n    </div>\n  )\n}\n\nconst Markdown = memo(MarkdownComponent)\nMarkdown.displayName = \"Markdown\"\n\nexport { Markdown }\n"
        },
        {
          "path": "components/prompt-kit/code-block.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport React, { useEffect, useState } from \"react\"\nimport { codeToHtml } from \"shiki\"\n\nexport type CodeBlockProps = {\n  children?: React.ReactNode\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nfunction CodeBlock({ children, className, ...props }: CodeBlockProps) {\n  return (\n    <div\n      className={cn(\n        \"not-prose flex w-full flex-col overflow-clip border\",\n        \"border-border bg-card text-card-foreground rounded-xl\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\nexport type CodeBlockCodeProps = {\n  code: string\n  language?: string\n  theme?: string\n  className?: string\n} & React.HTMLProps<HTMLDivElement>\n\nfunction CodeBlockCode({\n  code,\n  language = \"tsx\",\n  theme = \"github-light\",\n  className,\n  ...props\n}: CodeBlockCodeProps) {\n  const [highlightedHtml, setHighlightedHtml] = useState<string | null>(null)\n\n  useEffect(() => {\n    async function highlight() {\n      if (!code) {\n        setHighlightedHtml(\"<pre><code></code></pre>\")\n        return\n      }\n\n      const html = await codeToHtml(code, { lang: language, theme })\n      setHighlightedHtml(html)\n    }\n    highlight()\n  }, [code, language, theme])\n\n  const classNames = cn(\n    \"w-full overflow-x-auto text-[13px] [&>pre]:px-4 [&>pre]:py-4\",\n    className\n  )\n\n  // SSR fallback: render plain code if not hydrated yet\n  return highlightedHtml ? (\n    <div\n      className={classNames}\n      dangerouslySetInnerHTML={{ __html: highlightedHtml }}\n      {...props}\n    />\n  ) : (\n    <div className={classNames} {...props}>\n      <pre>\n        <code>{code}</code>\n      </pre>\n    </div>\n  )\n}\n\nexport type CodeBlockGroupProps = React.HTMLAttributes<HTMLDivElement>\n\nfunction CodeBlockGroup({\n  children,\n  className,\n  ...props\n}: CodeBlockGroupProps) {\n  return (\n    <div\n      className={cn(\"flex items-center justify-between\", className)}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\nexport { CodeBlockGroup, CodeBlockCode, CodeBlock }\n"
        },
        {
          "path": "components/prompt-kit/tool.tsx",
          "type": "registry:component",
          "content": "\"use client\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\"\nimport { cn } from \"@/lib/utils\"\nimport {\n  CheckCircle,\n  ChevronDown,\n  Loader2,\n  Settings,\n  XCircle,\n} from \"lucide-react\"\nimport { useState } from \"react\"\n\nexport type ToolPart = {\n  type: string\n  state:\n    | \"input-streaming\"\n    | \"input-available\"\n    | \"output-available\"\n    | \"output-error\"\n  input?: Record<string, unknown>\n  output?: Record<string, unknown>\n  toolCallId?: string\n  errorText?: string\n}\n\nexport type ToolProps = {\n  toolPart: ToolPart\n  defaultOpen?: boolean\n  className?: string\n}\n\nconst Tool = ({ toolPart, defaultOpen = false, className }: ToolProps) => {\n  const [isOpen, setIsOpen] = useState(defaultOpen)\n\n  const { state, input, output, toolCallId } = toolPart\n\n  const getStateIcon = () => {\n    switch (state) {\n      case \"input-streaming\":\n        return <Loader2 className=\"h-4 w-4 animate-spin text-blue-500\" />\n      case \"input-available\":\n        return <Settings className=\"h-4 w-4 text-orange-500\" />\n      case \"output-available\":\n        return <CheckCircle className=\"h-4 w-4 text-green-500\" />\n      case \"output-error\":\n        return <XCircle className=\"h-4 w-4 text-red-500\" />\n      default:\n        return <Settings className=\"text-muted-foreground h-4 w-4\" />\n    }\n  }\n\n  const getStateBadge = () => {\n    const baseClasses = \"px-2 py-1 rounded-full text-xs font-medium\"\n    switch (state) {\n      case \"input-streaming\":\n        return (\n          <span\n            className={cn(\n              baseClasses,\n              \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\"\n            )}\n          >\n            Processing\n          </span>\n        )\n      case \"input-available\":\n        return (\n          <span\n            className={cn(\n              baseClasses,\n              \"bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400\"\n            )}\n          >\n            Ready\n          </span>\n        )\n      case \"output-available\":\n        return (\n          <span\n            className={cn(\n              baseClasses,\n              \"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400\"\n            )}\n          >\n            Completed\n          </span>\n        )\n      case \"output-error\":\n        return (\n          <span\n            className={cn(\n              baseClasses,\n              \"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400\"\n            )}\n          >\n            Error\n          </span>\n        )\n      default:\n        return (\n          <span\n            className={cn(\n              baseClasses,\n              \"bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400\"\n            )}\n          >\n            Pending\n          </span>\n        )\n    }\n  }\n\n  const formatValue = (value: unknown): string => {\n    if (value === null) return \"null\"\n    if (value === undefined) return \"undefined\"\n    if (typeof value === \"string\") return value\n    if (typeof value === \"object\") {\n      return JSON.stringify(value, null, 2)\n    }\n    return String(value)\n  }\n\n  return (\n    <div\n      className={cn(\n        \"border-border mt-3 overflow-hidden rounded-lg border\",\n        className\n      )}\n    >\n      <Collapsible open={isOpen} onOpenChange={setIsOpen}>\n        <CollapsibleTrigger asChild>\n          <Button\n            variant=\"ghost\"\n            className=\"bg-background h-auto w-full justify-between rounded-b-none px-3 py-2 font-normal\"\n          >\n            <div className=\"flex items-center gap-2\">\n              {getStateIcon()}\n              <span className=\"font-mono text-sm font-medium\">\n                {toolPart.type}\n              </span>\n              {getStateBadge()}\n            </div>\n            <ChevronDown className={cn(\"h-4 w-4\", isOpen && \"rotate-180\")} />\n          </Button>\n        </CollapsibleTrigger>\n        <CollapsibleContent\n          className={cn(\n            \"border-border border-t\",\n            \"data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down overflow-hidden\"\n          )}\n        >\n          <div className=\"bg-background space-y-3 p-3\">\n            {input && Object.keys(input).length > 0 && (\n              <div>\n                <h4 className=\"text-muted-foreground mb-2 text-sm font-medium\">\n                  Input\n                </h4>\n                <div className=\"bg-background rounded border p-2 font-mono text-sm\">\n                  {Object.entries(input).map(([key, value]) => (\n                    <div key={key} className=\"mb-1\">\n                      <span className=\"text-muted-foreground\">{key}:</span>{\" \"}\n                      <span>{formatValue(value)}</span>\n                    </div>\n                  ))}\n                </div>\n              </div>\n            )}\n\n            {output && (\n              <div>\n                <h4 className=\"text-muted-foreground mb-2 text-sm font-medium\">\n                  Output\n                </h4>\n                <div className=\"bg-background max-h-60 overflow-auto rounded border p-2 font-mono text-sm\">\n                  <pre className=\"whitespace-pre-wrap\">\n                    {formatValue(output)}\n                  </pre>\n                </div>\n              </div>\n            )}\n\n            {state === \"output-error\" && toolPart.errorText && (\n              <div>\n                <h4 className=\"mb-2 text-sm font-medium text-red-500\">Error</h4>\n                <div className=\"bg-background rounded border border-red-200 p-2 text-sm dark:border-red-950 dark:bg-red-900/20\">\n                  {toolPart.errorText}\n                </div>\n              </div>\n            )}\n\n            {state === \"input-streaming\" && (\n              <div className=\"text-muted-foreground text-sm\">\n                Processing tool call...\n              </div>\n            )}\n\n            {toolCallId && (\n              <div className=\"text-muted-foreground border-t border-blue-200 pt-2 text-xs\">\n                <span className=\"font-mono\">Call ID: {toolCallId}</span>\n              </div>\n            )}\n          </div>\n        </CollapsibleContent>\n      </Collapsible>\n    </div>\n  )\n}\n\nexport { Tool }\n"
        }
      ],
      "envVars": {
        "OPENAI_API_KEY": ""
      },
      "categories": [
        "ai",
        "prompt-kit"
      ]
    }
  ]
}