{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chat-simple-demo",
  "registryDependencies": [
    "@delta/ai-elements"
  ],
  "files": [
    {
      "path": "examples/chat-simple-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 { 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\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  \"Hello! I'm an AI assistant built with Delta Components. I can help you with coding questions, explain concepts, or just have a conversation. What would you like to talk about?\",\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]\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 addResponse = useCallback(\n    async (messageId: string, content: string) => {\n      setStatus(\"streaming\")\n      setStreamingMessageId(messageId)\n      shouldCancelRef.current = false\n\n      // Simulate API delay\n      await new Promise((resolve) => setTimeout(resolve, 1500))\n\n      // Check if cancelled during delay\n      if (shouldCancelRef.current) {\n        setStatus(\"ready\")\n        setStreamingMessageId(null)\n        return\n      }\n\n      // Add complete response at once\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 } : v\n              ),\n            }\n          }\n          return msg\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        addResponse(assistantMessageId, randomResponse)\n        addMessageTimeoutRef.current = null\n      }, 500)\n    },\n    [addResponse]\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 className=\"hidden sm:inline\">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"
}