{ "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", "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,\n index: number\n): React.ReactNode => {\n if (!part.type?.startsWith(\"tool-\")) return null\n\n return \n}\n\nexport const MessageComponent = memo(\n ({ message, isLastMessage }: MessageComponentProps) => {\n const isAssistant = message?.role === \"assistant\"\n\n return (\n \n {isAssistant ? (\n
\n
\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
\n \n {message?.parts\n .filter((part: any) => part.type === \"text\")\n .map((part: any) => part.text)\n .join(\"\")}\n \n\n \n \n \n \n \n \n \n \n \n \n \n
\n ) : (\n
\n \n {message?.parts\n .map((part: any) => (part.type === \"text\" ? part.text : null))\n .join(\"\")}\n \n \n \n \n \n \n
\n )}\n \n )\n }\n)\n\nMessageComponent.displayName = \"MessageComponent\"\n\nconst LoadingMessage = memo(() => (\n \n
\n
\n \n
\n
\n
\n))\n\nLoadingMessage.displayName = \"LoadingMessage\"\n\nconst ErrorMessage = memo(({ error }: { error: Error }) => (\n \n
\n
\n \n

{error.message}

\n
\n
\n
\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
\n \n \n {messages.length === 0 && (\n
\n
\n Try asking:\n
\n
    \n
  • what's the current date?
  • \n
  • what time is it in Tokyo?
  • \n
  • give me the current time in Europe/Paris
  • \n
\n
\n )}\n\n {messages?.map((message, index) => {\n const isLastMessage = index === messages.length - 1\n\n return (\n \n )\n })}\n\n {status === \"submitted\" && }\n {status === \"error\" && error && }\n
\n
\n\n
\n \n
\n \n\n \n
\n
\n \n {status === \"ready\" || status === \"error\" ? (\n \n ) : (\n \n )}\n \n
\n \n
\n \n
\n
\n )\n}\n\nexport default ToolCallingChatbot\n", "type": "registry:component" }, { "path": "app/api/primitives/tool-calling/route.ts", "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", "type": "registry:file", "target": "app/api/primitives/tool-calling/route.ts" }, { "path": "components/prompt-kit/chat-container.tsx", "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\n\nexport type ChatContainerContentProps = {\n children: React.ReactNode\n className?: string\n} & React.HTMLAttributes\n\nexport type ChatContainerScrollAnchorProps = {\n className?: string\n ref?: React.RefObject\n} & React.HTMLAttributes\n\nfunction ChatContainerRoot({\n children,\n className,\n ...props\n}: ChatContainerRootProps) {\n return (\n \n {children}\n \n )\n}\n\nfunction ChatContainerContent({\n children,\n className,\n ...props\n}: ChatContainerContentProps) {\n return (\n \n {children}\n \n )\n}\n\nfunction ChatContainerScrollAnchor({\n className,\n ...props\n}: ChatContainerScrollAnchorProps) {\n return (\n \n )\n}\n\nexport { ChatContainerRoot, ChatContainerContent, ChatContainerScrollAnchor }\n", "type": "registry:component" }, { "path": "components/prompt-kit/loader.tsx", "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 \n Loading\n
\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
\n
\n {[...Array(12)].map((_, i) => (\n \n ))}\n
\n Loading\n
\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
\n
\n Loading\n
\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 \n Loading\n
\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 \n {[...Array(3)].map((_, i) => (\n \n ))}\n Loading\n \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 \n {[...Array(3)].map((_, i) => (\n \n ))}\n Loading\n \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 \n {[...Array(5)].map((_, i) => (\n \n ))}\n Loading\n \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
\n {[...Array(3)].map((_, i) => (\n \n ))}\n Loading\n
\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 \n \n {\">\"}\n \n \n Loading\n \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 \n {text}\n \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 \n {text}\n \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 \n \n {text}\n \n \n \n .\n \n \n .\n \n \n .\n \n \n \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 \n case \"classic\":\n return \n case \"pulse\":\n return \n case \"pulse-dot\":\n return \n case \"dots\":\n return \n case \"typing\":\n return \n case \"wave\":\n return \n case \"bars\":\n return \n case \"terminal\":\n return \n case \"text-blink\":\n return \n case \"text-shimmer\":\n return \n case \"loading-dots\":\n return \n default:\n return \n }\n}\n\nexport { Loader }\n", "type": "registry:component" }, { "path": "components/prompt-kit/message.tsx", "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\n\nconst Message = ({ children, className, ...props }: MessageProps) => (\n
\n {children}\n
\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 \n \n {fallback && (\n {fallback}\n )}\n \n )\n}\n\nexport type MessageContentProps = {\n children: React.ReactNode\n markdown?: boolean\n className?: string\n} & React.ComponentProps &\n React.HTMLProps\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 \n {children as string}\n \n ) : (\n
\n {children}\n
\n )\n}\n\nexport type MessageActionsProps = {\n children: React.ReactNode\n className?: string\n} & React.HTMLProps\n\nconst MessageActions = ({\n children,\n className,\n ...props\n}: MessageActionsProps) => (\n \n {children}\n \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\n\nconst MessageAction = ({\n tooltip,\n children,\n className,\n side = \"top\",\n ...props\n}: MessageActionProps) => {\n return (\n \n \n {children}\n \n {tooltip}\n \n \n \n )\n}\n\nexport { Message, MessageAvatar, MessageContent, MessageActions, MessageAction }\n", "type": "registry:component" }, { "path": "components/prompt-kit/prompt-input.tsx", "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\n}\n\nconst PromptInputContext = createContext({\n isLoading: false,\n value: \"\",\n setValue: () => {},\n maxHeight: 240,\n onSubmit: undefined,\n disabled: false,\n textareaRef: React.createRef(),\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(null)\n\n const handleChange = (newValue: string) => {\n setInternalValue(newValue)\n onValueChange?.(newValue)\n }\n\n const handleClick: React.MouseEventHandler = (e) => {\n if (!disabled) textareaRef.current?.focus()\n onClick?.(e)\n }\n\n return (\n \n \n \n {children}\n \n \n \n )\n}\n\nexport type PromptInputTextareaProps = {\n disableAutosize?: boolean\n} & React.ComponentProps\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) => {\n adjustHeight(e.target)\n setValue(e.target.value)\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault()\n onSubmit?.()\n }\n onKeyDown?.(e)\n }\n\n return (\n \n )\n}\n\nexport type PromptInputActionsProps = React.HTMLAttributes\n\nfunction PromptInputActions({\n children,\n className,\n ...props\n}: PromptInputActionsProps) {\n return (\n
\n {children}\n
\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\n\nfunction PromptInputAction({\n tooltip,\n children,\n className,\n side = \"top\",\n ...props\n}: PromptInputActionProps) {\n const { disabled } = usePromptInput()\n\n return (\n \n event.stopPropagation()}\n >\n {children}\n \n \n {tooltip}\n \n \n )\n}\n\nexport {\n PromptInput,\n PromptInputTextarea,\n PromptInputActions,\n PromptInputAction,\n}\n", "type": "registry:component" }, { "path": "components/prompt-kit/markdown.tsx", "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\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 = {\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 \n {children}\n \n )\n }\n\n const language = extractLanguage(className)\n\n return (\n \n \n \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\n }) {\n return (\n \n {content}\n \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
\n {blocks.map((block, index) => (\n \n ))}\n
\n )\n}\n\nconst Markdown = memo(MarkdownComponent)\nMarkdown.displayName = \"Markdown\"\n\nexport { Markdown }\n", "type": "registry:component" }, { "path": "components/prompt-kit/code-block.tsx", "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\n\nfunction CodeBlock({ children, className, ...props }: CodeBlockProps) {\n return (\n \n {children}\n \n )\n}\n\nexport type CodeBlockCodeProps = {\n code: string\n language?: string\n theme?: string\n className?: string\n} & React.HTMLProps\n\nfunction CodeBlockCode({\n code,\n language = \"tsx\",\n theme = \"github-light\",\n className,\n ...props\n}: CodeBlockCodeProps) {\n const [highlightedHtml, setHighlightedHtml] = useState(null)\n\n useEffect(() => {\n async function highlight() {\n if (!code) {\n setHighlightedHtml(\"
\")\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 \n ) : (\n
\n
\n        {code}\n      
\n
\n )\n}\n\nexport type CodeBlockGroupProps = React.HTMLAttributes\n\nfunction CodeBlockGroup({\n children,\n className,\n ...props\n}: CodeBlockGroupProps) {\n return (\n \n {children}\n \n )\n}\n\nexport { CodeBlockGroup, CodeBlockCode, CodeBlock }\n", "type": "registry:component" }, { "path": "components/prompt-kit/tool.tsx", "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\n output?: Record\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 \n case \"input-available\":\n return \n case \"output-available\":\n return \n case \"output-error\":\n return \n default:\n return \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 \n Processing\n \n )\n case \"input-available\":\n return (\n \n Ready\n \n )\n case \"output-available\":\n return (\n \n Completed\n \n )\n case \"output-error\":\n return (\n \n Error\n \n )\n default:\n return (\n \n Pending\n \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 \n \n \n \n
\n {getStateIcon()}\n \n {toolPart.type}\n \n {getStateBadge()}\n
\n \n \n
\n \n
\n {input && Object.keys(input).length > 0 && (\n
\n

\n Input\n

\n
\n {Object.entries(input).map(([key, value]) => (\n
\n {key}:{\" \"}\n {formatValue(value)}\n
\n ))}\n
\n
\n )}\n\n {output && (\n
\n

\n Output\n

\n
\n
\n                    {formatValue(output)}\n                  
\n
\n
\n )}\n\n {state === \"output-error\" && toolPart.errorText && (\n
\n

Error

\n
\n {toolPart.errorText}\n
\n
\n )}\n\n {state === \"input-streaming\" && (\n
\n Processing tool call...\n
\n )}\n\n {toolCallId && (\n
\n Call ID: {toolCallId}\n
\n )}\n
\n \n
\n \n )\n}\n\nexport { Tool }\n", "type": "registry:component" } ], "envVars": { "OPENAI_API_KEY": "" } }