{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chat-previous-messages-demo",
  "registryDependencies": [
    "@delta/ai-elements"
  ],
  "files": [
    {
      "path": "examples/chat-previous-messages-demo.tsx",
      "content": "\"use client\"\n\nimport { useCallback, useRef, useState } from \"react\"\nimport type { ToolUIPart } from \"ai\"\nimport { GlobeIcon, ThumbsDownIcon, ThumbsUpIcon } from \"lucide-react\"\nimport { nanoid } from \"nanoid\"\nimport { toast } from \"sonner\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  Action,\n  Actions,\n  CopyAction,\n} from \"@/components/ui/ai-elements/actions\"\nimport {\n  Branch,\n  BranchMessages,\n  BranchNext,\n  BranchPage,\n  BranchPrevious,\n  BranchSelector,\n} from \"@/components/ui/ai-elements/branch\"\nimport { ChatContainer } from \"@/components/ui/ai-elements/chat-container\"\nimport {\n  Conversation,\n  ConversationContent,\n  ConversationScrollButton,\n} from \"@/components/ui/ai-elements/conversation\"\nimport { Loader } from \"@/components/ui/ai-elements/loader\"\nimport {\n  Message,\n  MessageContent,\n} from \"@/components/ui/ai-elements/message\"\nimport {\n  PromptInput,\n  PromptInputActionAddAttachments,\n  PromptInputActionMenu,\n  PromptInputActionMenuContent,\n  PromptInputActionMenuTrigger,\n  PromptInputAttachment,\n  PromptInputAttachments,\n  PromptInputBody,\n  PromptInputButton,\n  PromptInputFooter,\n  PromptInputModelSelect,\n  PromptInputModelSelectContent,\n  PromptInputModelSelectItem,\n  PromptInputModelSelectTrigger,\n  PromptInputModelSelectValue,\n  PromptInputSpeechButton,\n  PromptInputSubmit,\n  PromptInputTextarea,\n  PromptInputTools,\n  type PromptInputMessage,\n} from \"@/components/ui/ai-elements/prompt-input\"\nimport {\n  Reasoning,\n  ReasoningContent,\n  ReasoningTrigger,\n} from \"@/components/ui/ai-elements/reasoning\"\nimport { Response } from \"@/components/ui/ai-elements/response\"\nimport {\n  Source,\n  Sources,\n  SourcesContent,\n  SourcesTrigger,\n} from \"@/components/ui/ai-elements/sources\"\n\ntype MessageType = {\n  key: string\n  from: \"user\" | \"assistant\"\n  sources?: { href: string; title: string }[]\n  versions: {\n    id: string\n    content: string\n  }[]\n  reasoning?: {\n    content: string\n    duration: number\n  }\n  tools?: {\n    name: string\n    description: string\n    status: ToolUIPart[\"state\"]\n    parameters: Record<string, unknown>\n    result: string | undefined\n    error: string | undefined\n  }[]\n  avatar: string\n  name: string\n}\n\nconst initialMessages: MessageType[] = [\n  {\n    key: nanoid(),\n    from: \"user\",\n    versions: [\n      {\n        id: nanoid(),\n        content: \"Can you explain how to use React hooks effectively?\",\n      },\n    ],\n    avatar: \"https://patrickprunty.com/icon.webp\",\n    name: \"Hayden Bleasel\",\n  },\n  {\n    key: nanoid(),\n    from: \"assistant\",\n    sources: [\n      {\n        href: \"https://react.dev/reference/react\",\n        title: \"React Documentation\",\n      },\n      {\n        href: \"https://react.dev/reference/react-dom\",\n        title: \"React DOM Documentation\",\n      },\n    ],\n    tools: [\n      {\n        name: \"mcp\",\n        description: \"Searching React documentation\",\n        status: \"input-available\",\n        parameters: {\n          query: \"React hooks best practices\",\n          source: \"react.dev\",\n        },\n        result: `{\n  \"query\": \"React hooks best practices\",\n  \"results\": [\n    {\n      \"title\": \"Rules of Hooks\",\n      \"url\": \"https://react.dev/warnings/invalid-hook-call-warning\",\n      \"snippet\": \"Hooks must be called at the top level of your React function components or custom hooks. Don't call hooks inside loops, conditions, or nested functions.\"\n    },\n    {\n      \"title\": \"useState Hook\",\n      \"url\": \"https://react.dev/reference/react/useState\",\n      \"snippet\": \"useState is a React Hook that lets you add state to your function components. It returns an array with two values: the current state and a function to update it.\"\n    },\n    {\n      \"title\": \"useEffect Hook\",\n      \"url\": \"https://react.dev/reference/react/useEffect\",\n      \"snippet\": \"useEffect lets you synchronize a component with external systems. It runs after render and can be used to perform side effects like data fetching.\"\n    }\n  ]\n}`,\n        error: undefined,\n      },\n    ],\n    versions: [\n      {\n        id: nanoid(),\n        content: `# React Hooks Best Practices\n\nReact hooks are a powerful feature that let you use state and other React features without writing classes. Here are some tips for using them effectively:\n\n## Rules of Hooks\n\n1. **Only call hooks at the top level** of your component or custom hooks\n2. **Don't call hooks inside loops, conditions, or nested functions**\n\n## Common Hooks\n\n- **useState**: For local component state\n- **useEffect**: For side effects like data fetching\n- **useContext**: For consuming context\n- **useReducer**: For complex state logic\n- **useCallback**: For memoizing functions\n- **useMemo**: For memoizing values\n\n## Example of useState and useEffect\n\n\\`\\`\\`jsx\nfunction ProfilePage({ userId }) {\n  const [user, setUser] = useState(null);\n  \n  useEffect(() => {\n    // This runs after render and when userId changes\n    fetchUser(userId).then(userData => {\n      setUser(userData);\n    });\n  }, [userId]);\n  \n  return user ? <Profile user={user} /> : <Loading />;\n}\n\\`\\`\\`\n\nWould you like me to explain any specific hook in more detail?`,\n      },\n    ],\n    avatar: \"https://github.com/openai.png\",\n    name: \"OpenAI\",\n  },\n  {\n    key: nanoid(),\n    from: \"user\",\n    versions: [\n      {\n        id: nanoid(),\n        content:\n          \"Yes, could you explain useCallback and useMemo in more detail? When should I use one over the other?\",\n      },\n      {\n        id: nanoid(),\n        content:\n          \"I'm particularly interested in understanding the performance implications of useCallback and useMemo. Could you break down when each is most appropriate?\",\n      },\n      {\n        id: nanoid(),\n        content:\n          \"Thanks for the overview! Could you dive deeper into the specific use cases where useCallback and useMemo make the biggest difference in React applications?\",\n      },\n    ],\n    avatar: \"https://patrickprunty.com/icon.webp\",\n    name: \"Hayden Bleasel\",\n  },\n  {\n    key: nanoid(),\n    from: \"assistant\",\n    reasoning: {\n      content: `The user is asking for a detailed explanation of useCallback and useMemo. I should provide a clear and concise explanation of each hook's purpose and how they differ.\n      \nThe useCallback hook is used to memoize functions to prevent unnecessary re-renders of child components that receive functions as props.\n\nThe useMemo hook is used to memoize values to avoid expensive recalculations on every render.\n\nBoth hooks help with performance optimization, but they serve different purposes.`,\n      duration: 10,\n    },\n    versions: [\n      {\n        id: nanoid(),\n        content: `## useCallback vs useMemo\n\nBoth hooks help with performance optimization, but they serve different purposes:\n\n### useCallback\n\n\\`useCallback\\` memoizes **functions** to prevent unnecessary re-renders of child components that receive functions as props.\n\n\\`\\`\\`jsx\n// Without useCallback - a new function is created on every render\nconst handleClick = () => {\n  console.log(count);\n};\n\n// With useCallback - the function is only recreated when dependencies change\nconst handleClick = useCallback(() => {\n  console.log(count);\n}, [count]);\n\\`\\`\\`\n\n### useMemo\n\n\\`useMemo\\` memoizes **values** to avoid expensive recalculations on every render.\n\n\\`\\`\\`jsx\n// Without useMemo - expensive calculation runs on every render\nconst sortedList = expensiveSort(items);\n\n// With useMemo - calculation only runs when items change\nconst sortedList = useMemo(() => expensiveSort(items), [items]);\n\\`\\`\\`\n\n### When to use which?\n\n- Use **useCallback** when:\n  - Passing callbacks to optimized child components that rely on reference equality\n  - Working with event handlers that you pass to child components\n\n- Use **useMemo** when:\n  - You have computationally expensive calculations\n  - You want to avoid recreating objects that are used as dependencies for other hooks\n\n### Performance Note\n\nDon't overuse these hooks! They come with their own overhead. Only use them when you have identified a genuine performance issue.`,\n      },\n    ],\n    avatar: \"https://github.com/openai.png\",\n    name: \"OpenAI\",\n  },\n]\n\nconst models = [\n  { id: \"gpt-4\", name: \"GPT-4\" },\n  { id: \"gpt-3.5-turbo\", name: \"GPT-3.5 Turbo\" },\n  { id: \"claude-2\", name: \"Claude 2\" },\n  { id: \"claude-instant\", name: \"Claude Instant\" },\n  { id: \"palm-2\", name: \"PaLM 2\" },\n  { id: \"llama-2-70b\", name: \"Llama 2 70B\" },\n  { id: \"llama-2-13b\", name: \"Llama 2 13B\" },\n  { id: \"cohere-command\", name: \"Command\" },\n  { id: \"mistral-7b\", name: \"Mistral 7B\" },\n]\n\nconst mockResponses = [\n  \"That's a great question! Let me help you understand this concept better. The key thing to remember is that proper implementation requires careful consideration of the underlying principles and best practices in the field.\",\n  \"I'd be happy to explain this topic in detail. From my understanding, there are several important factors to consider when approaching this problem. Let me break it down step by step for you.\",\n  \"This is an interesting topic that comes up frequently. The solution typically involves understanding the core concepts and applying them in the right context. Here's what I recommend...\",\n  \"Great choice of topic! This is something that many developers encounter. The approach I'd suggest is to start with the fundamentals and then build up to more complex scenarios.\",\n  \"That's definitely worth exploring. From what I can see, the best way to handle this is to consider both the theoretical aspects and practical implementation details.\",\n]\n\nconst Example = () => {\n  const [model, setModel] = useState<string>(models[0].id)\n  const [text, setText] = useState<string>(\"\")\n  const [useWebSearch, setUseWebSearch] = useState<boolean>(false)\n  const [status, setStatus] = useState<\n    \"submitted\" | \"streaming\" | \"ready\" | \"error\"\n  >(\"ready\")\n  const [messages, setMessages] = useState<MessageType[]>(initialMessages)\n  const [streamingMessageId, setStreamingMessageId] = useState<string | null>(\n    null\n  )\n  const [touchedMessages, setTouchedMessages] = useState<Set<string>>(new Set())\n  const shouldCancelRef = useRef<boolean>(false)\n  const addMessageTimeoutRef = useRef<NodeJS.Timeout | null>(null)\n  const textareaRef = useRef<HTMLTextAreaElement>(null)\n\n  const stop = useCallback(() => {\n    console.log(\"Stopping generation...\")\n\n    // Set cancellation flag\n    shouldCancelRef.current = true\n\n    // Clear timeout for adding assistant message\n    if (addMessageTimeoutRef.current) {\n      clearTimeout(addMessageTimeoutRef.current)\n      addMessageTimeoutRef.current = null\n    }\n\n    setStatus(\"ready\")\n    setStreamingMessageId(null)\n  }, [])\n\n  const streamResponse = useCallback(\n    async (messageId: string, content: string) => {\n      setStatus(\"streaming\")\n      setStreamingMessageId(messageId)\n      shouldCancelRef.current = false\n\n      const words = content.split(\" \")\n      let currentContent = \"\"\n\n      for (let i = 0; i < words.length; i++) {\n        // Check if streaming should be cancelled\n        if (shouldCancelRef.current) {\n          setStatus(\"ready\")\n          setStreamingMessageId(null)\n          return\n        }\n\n        currentContent += (i > 0 ? \" \" : \"\") + words[i]\n\n        setMessages((prev) =>\n          prev.map((msg) => {\n            if (msg.versions.some((v) => v.id === messageId)) {\n              return {\n                ...msg,\n                versions: msg.versions.map((v) =>\n                  v.id === messageId ? { ...v, content: currentContent } : v\n                ),\n              }\n            }\n            return msg\n          })\n        )\n\n        await new Promise((resolve) =>\n          setTimeout(resolve, Math.random() * 100 + 50)\n        )\n      }\n\n      setStatus(\"ready\")\n      setStreamingMessageId(null)\n    },\n    []\n  )\n\n  const handleMessageTouch = useCallback((messageKey: string) => {\n    setTouchedMessages((prev) => new Set(prev).add(messageKey))\n  }, [])\n\n  const addUserMessage = useCallback(\n    (content: string) => {\n      const userMessage: MessageType = {\n        key: `user-${Date.now()}`,\n        from: \"user\",\n        versions: [\n          {\n            id: `user-${Date.now()}`,\n            content,\n          },\n        ],\n        avatar: \"https://patrickprunty.com/icon.webp\",\n        name: \"User\",\n      }\n\n      setMessages((prev) => [...prev, userMessage])\n\n      addMessageTimeoutRef.current = setTimeout(() => {\n        const assistantMessageId = `assistant-${Date.now()}`\n        const randomResponse =\n          mockResponses[Math.floor(Math.random() * mockResponses.length)]\n\n        const assistantMessage: MessageType = {\n          key: `assistant-${Date.now()}`,\n          from: \"assistant\",\n          versions: [\n            {\n              id: assistantMessageId,\n              content: \"\",\n            },\n          ],\n          avatar: \"https://github.com/openai.png\",\n          name: \"Assistant\",\n        }\n\n        setMessages((prev) => [...prev, assistantMessage])\n        streamResponse(assistantMessageId, randomResponse)\n        addMessageTimeoutRef.current = null\n      }, 500)\n    },\n    [streamResponse]\n  )\n\n  const handleSubmit = (message: PromptInputMessage) => {\n    // If currently streaming or submitted, stop instead of submitting\n    if (status === \"streaming\" || status === \"submitted\") {\n      stop()\n      return\n    }\n\n    const hasText = Boolean(message.text)\n    const hasAttachments = Boolean(message.files?.length)\n\n    if (!(hasText || hasAttachments)) {\n      return\n    }\n\n    setStatus(\"submitted\")\n\n    if (message.files?.length) {\n      toast.success(\"Files attached\", {\n        description: `${message.files.length} file(s) attached to message`,\n      })\n    }\n\n    addUserMessage(message.text || \"Sent with attachments\")\n    setText(\"\")\n  }\n\n  return (\n    <ChatContainer>\n      <Conversation>\n        <ConversationContent>\n          {messages.map(({ versions, ...message }) => {\n            const assistantMessages = messages.filter(\n              (m) => m.from === \"assistant\"\n            )\n            const isLastAssistantMessage =\n              message.from === \"assistant\" &&\n              assistantMessages.length > 0 &&\n              assistantMessages[assistantMessages.length - 1].key ===\n                message.key\n\n            return (\n              <Branch defaultBranch={0} key={message.key}>\n                <BranchMessages>\n                  {versions.map((version) => (\n                    <Message\n                      from={message.from}\n                      key={`${message.key}-${version.id}`}\n                      className={cn(\n                        message.from === \"user\"\n                          ? \"items-end justify-end\"\n                          : undefined,\n                        \"group/message\"\n                      )}\n                      onTouchStart={() => handleMessageTouch(message.key)}\n                    >\n                      <div>\n                        {message.sources?.length && (\n                          <Sources>\n                            <SourcesTrigger count={message.sources.length} />\n                            <SourcesContent>\n                              {message.sources.map((source) => (\n                                <Source\n                                  href={source.href}\n                                  key={source.href}\n                                  title={source.title}\n                                />\n                              ))}\n                            </SourcesContent>\n                          </Sources>\n                        )}\n                        {message.reasoning && (\n                          <Reasoning duration={message.reasoning.duration}>\n                            <ReasoningTrigger />\n                            <ReasoningContent>\n                              {message.reasoning.content}\n                            </ReasoningContent>\n                          </Reasoning>\n                        )}\n                        <MessageContent\n                          className={cn(\n                            message.from === \"assistant\" ? \"max-w-full\" : \"\",\n                            message.from === \"user\" && \"ml-auto w-fit\"\n                          )}\n                        >\n                          <div className=\"text-base leading-[1.65rem]\">\n                            <Response>{version.content}</Response>\n                          </div>\n                        </MessageContent>\n                        {message.from === \"assistant\" && (\n                          <div className=\"flex items-center justify-between\">\n                            <div className=\"flex items-center\">\n                              {status === \"streaming\" &&\n                                streamingMessageId === version.id && (\n                                  <Loader\n                                    size={16}\n                                    className=\"text-muted-foreground ml-1\"\n                                  />\n                                )}\n                            </div>\n                            {status !== \"streaming\" &&\n                              streamingMessageId !== version.id && (\n                                <Actions\n                                  position=\"right\"\n                                  className={cn(\n                                    isLastAssistantMessage ||\n                                      touchedMessages.has(message.key)\n                                      ? \"opacity-100\"\n                                      : \"opacity-0 group-hover/message:opacity-100\"\n                                  )}\n                                >\n                                  <CopyAction\n                                    value={version.content}\n                                    tooltip=\"Copy message\"\n                                  />\n                                  <Action\n                                    tooltip=\"Good response\"\n                                    onClick={() =>\n                                      toast.success(\"Feedback recorded\")\n                                    }\n                                  >\n                                    <ThumbsUpIcon className=\"h-4 w-4\" />\n                                  </Action>\n                                  <Action\n                                    tooltip=\"Poor response\"\n                                    onClick={() =>\n                                      toast.success(\"Feedback recorded\")\n                                    }\n                                  >\n                                    <ThumbsDownIcon className=\"h-4 w-4\" />\n                                  </Action>\n                                  <Action\n                                    tooltip=\"Regenerate response\"\n                                    onClick={() =>\n                                      toast.info(\"Regenerating response...\")\n                                    }\n                                    className=\"text-muted-foreground hover:text-foreground hover:bg-accent relative size-9 h-9 w-auto min-w-0 rounded-md p-1.5 px-2 text-sm font-medium transition-colors duration-200\"\n                                  >\n                                    Retry\n                                  </Action>\n                                </Actions>\n                              )}\n                          </div>\n                        )}\n                      </div>\n                    </Message>\n                  ))}\n                </BranchMessages>\n                {versions.length > 1 && (\n                  <BranchSelector from={message.from}>\n                    <BranchPrevious />\n                    <BranchPage />\n                    <BranchNext />\n                  </BranchSelector>\n                )}\n              </Branch>\n            )\n          })}\n        </ConversationContent>\n        <ConversationScrollButton />\n      </Conversation>\n      <div className=\"grid shrink-0 gap-4\">\n        <div className=\"w-full px-4 pb-4\">\n          <PromptInput globalDrop multiple onSubmit={handleSubmit}>\n            <PromptInputBody>\n              <PromptInputAttachments>\n                {(attachment: any) => (\n                  <PromptInputAttachment data={attachment} />\n                )}\n              </PromptInputAttachments>\n              <PromptInputTextarea\n                onChange={(event: any) => setText(event.target.value)}\n                ref={textareaRef}\n                value={text}\n                className=\"text-base leading-[1.65rem]\"\n              />\n            </PromptInputBody>\n            <PromptInputFooter>\n              <PromptInputTools>\n                <PromptInputActionMenu>\n                  <PromptInputActionMenuTrigger />\n                  <PromptInputActionMenuContent>\n                    <PromptInputActionAddAttachments />\n                  </PromptInputActionMenuContent>\n                </PromptInputActionMenu>\n                <PromptInputSpeechButton\n                  onTranscriptionChange={setText}\n                  textareaRef={textareaRef}\n                />\n                <PromptInputButton\n                  onClick={() => setUseWebSearch(!useWebSearch)}\n                  variant={useWebSearch ? \"default\" : \"ghost\"}\n                >\n                  <GlobeIcon size={16} />\n                  <span>Search</span>\n                </PromptInputButton>\n              </PromptInputTools>\n              <PromptInputTools>\n                <PromptInputModelSelect onValueChange={setModel} value={model}>\n                  <PromptInputModelSelectTrigger>\n                    <PromptInputModelSelectValue />\n                  </PromptInputModelSelectTrigger>\n                  <PromptInputModelSelectContent>\n                    {models.map((model: any) => (\n                      <PromptInputModelSelectItem\n                        key={model.id || model.name}\n                        value={model.id || model.name}\n                      >\n                        {model.name || model.id}\n                      </PromptInputModelSelectItem>\n                    ))}\n                  </PromptInputModelSelectContent>\n                </PromptInputModelSelect>\n                <PromptInputSubmit\n                  disabled={(!text.trim() && !status) || status === \"streaming\"}\n                  status={status}\n                />\n              </PromptInputTools>\n            </PromptInputFooter>\n          </PromptInput>\n        </div>\n      </div>\n    </ChatContainer>\n  )\n}\n\nexport default Example\n",
      "type": "registry:example"
    }
  ],
  "type": "registry:example"
}