{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "firebase-tool-calling",
  "type": "registry:block",
  "title": "Firebase Tool Calling",
  "description": "A chat interface demonstrating Firebase AI tool calling with weather and time tools",
  "dependencies": [
    "@ai-sdk/react",
    "@mui/material",
    "ai",
    "lucide-react",
    "sonner",
    "zod",
    "@emotion/react",
    "@emotion/styled"
  ],
  "registryDependencies": [
    "https://www.mui-treasury.com/r/ai-actions.json",
    "https://www.mui-treasury.com/r/ai-conversation.json",
    "https://www.mui-treasury.com/r/ai-loader.json",
    "https://www.mui-treasury.com/r/ai-message.json",
    "https://www.mui-treasury.com/r/ai-prompt-input.json",
    "https://www.mui-treasury.com/r/ai-response.json",
    "https://www.mui-treasury.com/r/ai-suggestion.json",
    "https://www.mui-treasury.com/r/ai-tool.json",
    "https://www.mui-treasury.com/r/firebase-chat-transport.json"
  ],
  "files": [
    {
      "path": "firebase/firebase-tool-calling/firebase-tool-calling.tsx",
      "target": "src/mui-treasury/firebase/firebase-tool-calling/firebase-tool-calling.tsx",
      "content": "'use client';\n\nimport React, { useState } from 'react';\n\nimport { useChat } from '@ai-sdk/react';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport CircularProgress from '@mui/material/CircularProgress';\nimport Typography from '@mui/material/Typography';\nimport { stepCountIs, tool } from 'ai';\nimport { Bot, CopyIcon, RefreshCwIcon, SquareIcon } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { z } from 'zod';\n\nimport { app } from '@/lib/firebase-setup';\nimport { Action, Actions } from '@/registry/components/ai-actions/ai-actions';\nimport {\n  Conversation,\n  ConversationContent,\n  ConversationScrollButton,\n} from '@/registry/components/ai-conversation/ai-conversation';\nimport { Loader } from '@/registry/components/ai-loader/ai-loader';\nimport {\n  Message,\n  MessageAvatar,\n  MessageContent,\n} from '@/registry/components/ai-message/ai-message';\nimport {\n  PromptInput,\n  PromptInputBody,\n  type PromptInputMessage,\n  PromptInputSubmit,\n  PromptInputTextarea,\n  PromptInputToolbar,\n} from '@/registry/components/ai-prompt-input/ai-prompt-input';\nimport { Response } from '@/registry/components/ai-response/ai-response';\nimport {\n  Suggestion,\n  Suggestions,\n} from '@/registry/components/ai-suggestion/ai-suggestion';\nimport {\n  Tool,\n  ToolContent,\n  ToolHeader,\n  ToolInput,\n  ToolOutput,\n} from '@/registry/components/ai-tool/ai-tool';\nimport { FirebaseChatTransport } from '@/registry/firebase/firebase-chat-transport';\n\nconst SUGGESTED_PROMPTS = [\n  \"What's the weather like in Tokyo?\",\n  'Get the current time in New York',\n  'Tell me the weather in Paris and London',\n];\n\nexport default function FirebaseToolCalling() {\n  const [inputValue, setInputValue] = useState('');\n\n  const transport = React.useMemo(\n    () =>\n      app\n        ? new FirebaseChatTransport({\n            firebaseApp: app,\n            modelParams: {\n              model: 'gemini-2.5-flash',\n              systemInstruction: `You are a helpful AI assistant with access to tools.\nWhen the user asks about weather or time, use the appropriate tool to get the information.\nAlways respond in a concise and clear manner using Markdown format.`,\n            },\n            tools: {\n              getWeather: tool({\n                description: 'Get the current weather for a city',\n                inputSchema: z.object({\n                  city: z.string().describe('The city name'),\n                  country: z.string().optional().describe('The country code'),\n                }),\n                execute: async ({ city, country }) => {\n                  await new Promise((resolve) => setTimeout(resolve, 1000));\n                  return {\n                    city,\n                    country: country || 'US',\n                    temperature: Math.round(15 + Math.random() * 20),\n                    conditions: ['sunny', 'cloudy', 'rainy', 'windy'][\n                      Math.floor(Math.random() * 4)\n                    ],\n                    humidity: Math.round(40 + Math.random() * 40),\n                  };\n                },\n              }),\n              getTime: tool({\n                description: 'Get the current time for a timezone',\n                inputSchema: z.object({\n                  timezone: z\n                    .string()\n                    .describe(\n                      'The timezone (e.g., America/New_York, Europe/London)',\n                    ),\n                }),\n                execute: async ({ timezone }) => {\n                  await new Promise((resolve) => setTimeout(resolve, 500));\n                  const now = new Date();\n                  const formatter = new Intl.DateTimeFormat('en-US', {\n                    timeZone: timezone,\n                    hour: '2-digit',\n                    minute: '2-digit',\n                    second: '2-digit',\n                    hour12: true,\n                    weekday: 'long',\n                    year: 'numeric',\n                    month: 'long',\n                    day: 'numeric',\n                  });\n                  return {\n                    timezone,\n                    datetime: formatter.format(now),\n                  };\n                },\n              }),\n            },\n            stopWhen: stepCountIs(5),\n          })\n        : null,\n    [],\n  );\n\n  const { messages, status, error, sendMessage, stop, regenerate } = useChat({\n    id: 'firebase-tool-calling',\n    transport: transport!,\n  });\n\n  const handleSubmit = (\n    message: PromptInputMessage,\n    event: React.FormEvent,\n  ) => {\n    event.preventDefault();\n    const hasText = message.text?.trim();\n    if (hasText) {\n      sendMessage({ text: message.text! });\n    }\n    setInputValue('');\n  };\n\n  const handleSuggestionClick = (suggestion: string) => {\n    setInputValue(suggestion);\n  };\n\n  const handleCopy = async (text: string) => {\n    try {\n      await navigator.clipboard.writeText(text);\n    } catch (err) {\n      toast.error(\n        `Failed to copy (${\n          err instanceof Error ? err.message : 'Unknown error'\n        })`,\n      );\n    }\n  };\n\n  const showSuggestions = messages.length === 0 && status === 'ready';\n\n  if (!app) {\n    return (\n      <Box\n        sx={{\n          height: '100%',\n          display: 'flex',\n          alignItems: 'center',\n          justifyContent: 'center',\n          p: 2,\n        }}\n      >\n        <Typography\n          sx={{\n            color: 'text.secondary',\n          }}\n        >\n          Firebase not configured. Please set up Firebase config at the top of\n          the page.\n        </Typography>\n      </Box>\n    );\n  }\n\n  return (\n    <Box\n      sx={{\n        height: '100%',\n        width: '100%',\n        display: 'flex',\n        flexDirection: 'column',\n        maxWidth: 768,\n        mx: 'auto',\n      }}\n    >\n      <Box\n        sx={{\n          flex: 1,\n          display: 'flex',\n          flexDirection: 'column',\n          overflow: 'hidden',\n        }}\n      >\n        <Conversation>\n          <ConversationContent>\n            {messages.length === 0 && status === 'ready' ? (\n              <Box\n                sx={{\n                  flex: 1,\n                  display: 'flex',\n                  alignItems: 'center',\n                  justifyContent: 'center',\n                  flexDirection: 'column',\n                  gap: 2,\n                  color: 'text.tertiary',\n                }}\n              >\n                <Bot size={48} />\n                <Typography variant=\"h4\" sx={{ fontWeight: 500 }}>\n                  Tool Calling\n                </Typography>\n                <Typography\n                  sx={{\n                    color: 'text.secondary',\n                  }}\n                >\n                  Ask me about weather or time\n                </Typography>\n              </Box>\n            ) : (\n              <Box sx={{ display: 'flex', flexDirection: 'column' }}>\n                {messages.map((message) => {\n                  const messageText = message.parts\n                    ?.filter((part) => part.type === 'text')\n                    .map((part) => part.text)\n                    .join('\\n');\n\n                  return (\n                    <Message key={message.id} from={message.role}>\n                      <MessageAvatar\n                        name={message.role === 'user' ? 'You' : 'AI'}\n                      />\n                      <MessageContent variant=\"flat\">\n                        {message.parts?.map((part, index: number) => {\n                          if (part.type === 'text') {\n                            if (\n                              message.role === 'assistant' &&\n                              part.state !== 'done' &&\n                              !part.text\n                            ) {\n                              return null;\n                            }\n                            return message.role === 'assistant' ? (\n                              <Response key={index}>{part.text}</Response>\n                            ) : (\n                              <Box key={index}>{part.text}</Box>\n                            );\n                          }\n                          if (\n                            part.type.startsWith('tool-') &&\n                            'state' in part &&\n                            'input' in part\n                          ) {\n                            return (\n                              <Box key={index} sx={{ my: 1 }}>\n                                <Tool>\n                                  <ToolHeader\n                                    type={part.type as `tool-${string}`}\n                                    state={part.state}\n                                  />\n                                  <ToolContent>\n                                    <ToolInput input={part.input} />\n                                    {'output' in part &&\n                                      (part.output || part.errorText) && (\n                                        <ToolOutput\n                                          output={part.output}\n                                          errorText={part.errorText}\n                                        />\n                                      )}\n                                  </ToolContent>\n                                </Tool>\n                              </Box>\n                            );\n                          }\n                          return null;\n                        })}\n                        {message.role === 'assistant' && messageText && (\n                          <Actions>\n                            <Action\n                              tooltip=\"Copy\"\n                              onClick={() => handleCopy(messageText)}\n                            >\n                              <CopyIcon size={16} />\n                            </Action>\n                            <Action\n                              tooltip=\"Regenerate\"\n                              onClick={() => regenerate()}\n                            >\n                              <RefreshCwIcon size={16} />\n                            </Action>\n                          </Actions>\n                        )}\n                      </MessageContent>\n                    </Message>\n                  );\n                })}\n\n                {status === 'submitted' && (\n                  <Message from=\"assistant\">\n                    <MessageAvatar name=\"AI Assistant\" />\n                    <MessageContent variant=\"flat\">\n                      <Box\n                        sx={{ display: 'flex', alignItems: 'center', gap: 1 }}\n                      >\n                        <CircularProgress size={20} />\n                        <Typography sx={{ color: 'text.secondary' }}>\n                          Thinking...\n                        </Typography>\n                      </Box>\n                    </MessageContent>\n                  </Message>\n                )}\n\n                {error && (\n                  <Message from=\"assistant\">\n                    <MessageAvatar name=\"AI Assistant\" />\n                    <MessageContent variant=\"flat\">\n                      <Typography sx={{ color: 'error.text' }}>\n                        {error.message ||\n                          'An error occurred. Please try again.'}\n                      </Typography>\n                    </MessageContent>\n                  </Message>\n                )}\n              </Box>\n            )}\n            {status === 'streaming' && (\n              <Box\n                sx={{\n                  display: 'flex',\n                  alignItems: 'center',\n                  gap: 1,\n                  mt: 1,\n                }}\n              >\n                <Loader />\n                <Typography sx={{ color: 'text.secondary' }}>\n                  Streaming...\n                </Typography>\n              </Box>\n            )}\n          </ConversationContent>\n\n          <ConversationScrollButton />\n        </Conversation>\n      </Box>\n      {showSuggestions && (\n        <Box sx={{ mb: 2 }}>\n          <Suggestions>\n            {SUGGESTED_PROMPTS.map((prompt, index) => (\n              <Suggestion\n                key={index}\n                suggestion={prompt}\n                onClick={handleSuggestionClick}\n              />\n            ))}\n          </Suggestions>\n        </Box>\n      )}\n      <PromptInput onSubmit={handleSubmit}>\n        <PromptInputBody>\n          <PromptInputTextarea\n            placeholder=\"Ask about weather or time...\"\n            value={inputValue}\n            onChange={(e) => setInputValue(e.target.value)}\n            disabled={status === 'submitted' || error != null}\n          />\n        </PromptInputBody>\n        <PromptInputToolbar>\n          {status === 'streaming' || status === 'submitted' ? (\n            <Button\n              variant=\"outlined\"\n              onClick={(e) => {\n                e.preventDefault();\n                stop();\n              }}\n              sx={{\n                minWidth: 'auto',\n                borderRadius: 2,\n                p: 1,\n              }}\n            >\n              <SquareIcon size={16} />\n            </Button>\n          ) : (\n            <PromptInputSubmit\n              status={status as 'ready' | 'submitted' | 'streaming' | 'error'}\n              disabled={error != null}\n            />\n          )}\n        </PromptInputToolbar>\n      </PromptInput>\n    </Box>\n  );\n}\n",
      "type": "registry:item"
    },
    {
      "path": "firebase/firebase-tool-calling/index.ts",
      "target": "src/mui-treasury/firebase/firebase-tool-calling/index.ts",
      "content": "export * from './firebase-tool-calling';\nexport { default as FirebaseToolCalling } from './firebase-tool-calling';\n",
      "type": "registry:item"
    }
  ],
  "meta": {
    "category": "ai",
    "subcategory": "firebase",
    "previewClassName": "h-[600px!important]"
  }
}