{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "firebase-story-telling",
  "type": "registry:block",
  "title": "Firebase Story Telling",
  "description": "A chat interface for creating illustrated stories with interleaved text and image generation",
  "dependencies": [
    "@ai-sdk/react",
    "@mui/material",
    "firebase",
    "lucide-react",
    "sonner",
    "@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/firebase-chat-transport.json"
  ],
  "files": [
    {
      "path": "firebase/firebase-story-telling/firebase-story-telling.tsx",
      "target": "src/mui-treasury/firebase/firebase-story-telling/firebase-story-telling.tsx",
      "content": "'use client';\n\nimport React, { useState } from 'react';\n\nimport { UIMessage, 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 { ResponseModality } from 'firebase/ai';\nimport {\n  BookOpenIcon,\n  CopyIcon,\n  RefreshCwIcon,\n  SquareIcon,\n} from 'lucide-react';\nimport { toast } from 'sonner';\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 { FirebaseChatTransport } from '@/registry/firebase/firebase-chat-transport';\n\nconst SUGGESTED_PROMPTS = [\n  'Generate an illustrated recipe for a paella. Create images alongside the text as you generate the recipe.',\n  'Generate a story about a dog in a 3D cartoon animation style. For each scene, generate an image.',\n  \"Create a children's bedtime story about a magical forest with illustrations for each page.\",\n  'Write a travel guide for Tokyo with photos of each landmark you describe.',\n];\n\nexport default function FirebaseStoryTelling() {\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-image',\n              systemInstruction: `You are a creative storyteller and visual artist.\nWhen asked to create stories, recipes, guides, or any narrative content, you should:\n1. Generate rich, engaging text content\n2. Create relevant images that illustrate key moments, scenes, or steps\n3. Interleave images naturally with the text as you progress through the narrative\n4. Maintain visual consistency in style throughout the story\nAlways aim to create an immersive, illustrated experience.`,\n              generationConfig: {\n                responseModalities: [\n                  ResponseModality.TEXT,\n                  ResponseModality.IMAGE,\n                ],\n              },\n            },\n          })\n        : null,\n    [],\n  );\n\n  const { messages, status, error, sendMessage, stop, regenerate } =\n    useChat<UIMessage>({\n      id: 'firebase-story-telling',\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\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      toast.success('Copied to clipboard');\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                <BookOpenIcon size={48} />\n                <Typography variant=\"h4\" sx={{ fontWeight: 500 }}>\n                  Story Telling\n                </Typography>\n                <Typography\n                  sx={{\n                    color: 'text.secondary',\n                  }}\n                >\n                  Create illustrated stories, recipes, and guides\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 === 'file' &&\n                            part.mediaType?.startsWith('image/')\n                          ) {\n                            return (\n                              <Box\n                                key={index}\n                                component=\"img\"\n                                src={part.url}\n                                alt=\"Generated illustration\"\n                                sx={{\n                                  maxWidth: '100%',\n                                  height: 'auto',\n                                  borderRadius: 2,\n                                  my: 1,\n                                }}\n                              />\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\" />\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                          Creating your story...\n                        </Typography>\n                      </Box>\n                    </MessageContent>\n                  </Message>\n                )}\n\n                {error && (\n                  <Message from=\"assistant\">\n                    <MessageAvatar name=\"AI\" />\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                  Writing and illustrating...\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=\"Describe a story, recipe, or guide you'd like illustrated...\"\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-story-telling/index.ts",
      "target": "src/mui-treasury/firebase/firebase-story-telling/index.ts",
      "content": "export * from './firebase-story-telling';\nexport { default as FirebaseStoryTelling } from './firebase-story-telling';\n",
      "type": "registry:item"
    }
  ],
  "meta": {
    "category": "ai",
    "subcategory": "firebase",
    "previewClassName": "h-[600px!important]"
  }
}