{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "conversation-bar",
  "dependencies": [
    "@elevenlabs/react"
  ],
  "registryDependencies": [
    "button",
    "https://ui.elevenlabs.io/r/live-waveform.json",
    "card",
    "separator",
    "textarea"
  ],
  "files": [
    {
      "path": "components/ui/conversation-bar.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  useConversationControls,\n  useConversationInput,\n  useConversationStatus,\n} from \"@elevenlabs/react\"\nimport {\n  ArrowUpIcon,\n  ChevronDown,\n  Keyboard,\n  Mic,\n  MicOff,\n  PhoneIcon,\n  XIcon,\n} from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Card } from \"@/components/ui/card\"\nimport { LiveWaveform } from \"@/components/ui/live-waveform\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { Textarea } from \"@/components/ui/textarea\"\n\nexport interface ConversationBarProps {\n  /**\n   * ElevenLabs Agent ID to connect to\n   */\n  agentId: string\n\n  /**\n   * Custom className for the container\n   */\n  className?: string\n\n  /**\n   * Custom className for the waveform\n   */\n  waveformClassName?: string\n\n  /**\n   * Callback when user sends a message\n   */\n  onSendMessage?: (message: string) => void\n}\n\nexport const ConversationBar = React.forwardRef<\n  HTMLDivElement,\n  ConversationBarProps\n>(({ agentId, className, waveformClassName, onSendMessage }, ref) => {\n  const { status } = useConversationStatus()\n  const { startSession, endSession, sendUserMessage, sendContextualUpdate } =\n    useConversationControls()\n  const { isMuted, setMuted } = useConversationInput()\n  const [keyboardOpen, setKeyboardOpen] = React.useState(false)\n  const [textInput, setTextInput] = React.useState(\"\")\n  const mediaStreamRef = React.useRef<MediaStream | null>(null)\n\n  const isConnected = status === \"connected\"\n\n  const getMicStream = React.useCallback(async () => {\n    if (mediaStreamRef.current) return mediaStreamRef.current\n\n    const stream = await navigator.mediaDevices.getUserMedia({ audio: true })\n    mediaStreamRef.current = stream\n\n    return stream\n  }, [])\n\n  const startConversation = React.useCallback(async () => {\n    try {\n      await getMicStream()\n\n      startSession({\n        agentId,\n        connectionType: \"webrtc\",\n      })\n    } catch (error) {\n      console.error(\"Error starting conversation:\", error)\n    }\n  }, [getMicStream, agentId, startSession])\n\n  const handleEndSession = React.useCallback(() => {\n    endSession()\n\n    if (mediaStreamRef.current) {\n      mediaStreamRef.current.getTracks().forEach((t) => t.stop())\n      mediaStreamRef.current = null\n    }\n  }, [endSession])\n\n  const toggleMute = React.useCallback(() => {\n    setMuted(!isMuted)\n  }, [isMuted, setMuted])\n\n  const handleStartOrEnd = React.useCallback(() => {\n    if (status === \"connected\" || status === \"connecting\") {\n      handleEndSession()\n    } else if (status === \"disconnected\" || status === \"error\") {\n      startConversation()\n    }\n  }, [status, handleEndSession, startConversation])\n\n  const handleSendText = React.useCallback(() => {\n    if (!textInput.trim()) return\n\n    const messageToSend = textInput\n    sendUserMessage(messageToSend)\n    setTextInput(\"\")\n    onSendMessage?.(messageToSend)\n  }, [sendUserMessage, textInput, onSendMessage])\n\n  const handleTextChange = React.useCallback(\n    (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n      const value = e.target.value\n      setTextInput(value)\n\n      if (value.trim() && isConnected) {\n        sendContextualUpdate(value)\n      }\n    },\n    [sendContextualUpdate, isConnected]\n  )\n\n  const handleKeyDown = React.useCallback(\n    (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n      if (e.key === \"Enter\" && !e.shiftKey) {\n        e.preventDefault()\n        handleSendText()\n      }\n    },\n    [handleSendText]\n  )\n\n  React.useEffect(() => {\n    return () => {\n      if (mediaStreamRef.current) {\n        mediaStreamRef.current.getTracks().forEach((t) => t.stop())\n      }\n    }\n  }, [])\n\n  return (\n    <div\n      ref={ref}\n      className={cn(\"flex w-full items-end justify-center p-4\", className)}\n    >\n      <Card className=\"m-0 w-full gap-0 border p-0 shadow-lg\">\n        <div className=\"flex flex-col-reverse\">\n          <div>\n            {keyboardOpen && <Separator />}\n            <div className=\"flex items-center justify-between gap-2 p-2\">\n              <div className=\"h-8 w-[120px] md:h-10\">\n                <div\n                  className={cn(\n                    \"flex h-full items-center gap-2 rounded-md py-1\",\n                    \"bg-foreground/5 text-foreground/70\"\n                  )}\n                >\n                  <div className=\"h-full flex-1\">\n                    <div\n                      className={cn(\n                        \"relative flex h-full w-full shrink-0 items-center justify-center overflow-hidden rounded-sm\",\n                        waveformClassName\n                      )}\n                    >\n                      <LiveWaveform\n                        key={\n                          status === \"disconnected\" || status === \"error\"\n                            ? \"idle\"\n                            : \"active\"\n                        }\n                        active={isConnected && !isMuted}\n                        processing={status === \"connecting\"}\n                        barWidth={3}\n                        barGap={1}\n                        barRadius={4}\n                        fadeEdges={true}\n                        fadeWidth={24}\n                        sensitivity={1.8}\n                        smoothingTimeConstant={0.85}\n                        height={20}\n                        mode=\"static\"\n                        className={cn(\n                          \"h-full w-full transition-opacity duration-300\",\n                          (status === \"disconnected\" || status === \"error\") &&\n                            \"opacity-0\"\n                        )}\n                      />\n                      {(status === \"disconnected\" || status === \"error\") && (\n                        <div className=\"absolute inset-0 flex items-center justify-center\">\n                          <span className=\"text-foreground/50 text-[10px] font-medium\">\n                            Customer Support\n                          </span>\n                        </div>\n                      )}\n                    </div>\n                  </div>\n                </div>\n              </div>\n              <div className=\"flex items-center\">\n                <Button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  onClick={toggleMute}\n                  aria-pressed={isMuted}\n                  className={cn(isMuted ? \"bg-foreground/5\" : \"\")}\n                  disabled={!isConnected}\n                >\n                  {isMuted ? <MicOff /> : <Mic />}\n                </Button>\n                <Button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  onClick={() => setKeyboardOpen((v) => !v)}\n                  aria-pressed={keyboardOpen}\n                  className=\"relative\"\n                  disabled={!isConnected}\n                >\n                  <Keyboard\n                    className={\n                      \"h-5 w-5 transform-gpu transition-all duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] \" +\n                      (keyboardOpen\n                        ? \"scale-75 opacity-0\"\n                        : \"scale-100 opacity-100\")\n                    }\n                  />\n                  <ChevronDown\n                    className={\n                      \"absolute inset-0 m-auto h-5 w-5 transform-gpu transition-all delay-50 duration-200 ease-[cubic-bezier(0.34,1.56,0.64,1)] \" +\n                      (keyboardOpen\n                        ? \"scale-100 opacity-100\"\n                        : \"scale-75 opacity-0\")\n                    }\n                  />\n                </Button>\n                <Separator orientation=\"vertical\" className=\"mx-1 -my-2.5\" />\n                <Button variant=\"ghost\" size=\"icon\" onClick={handleStartOrEnd}>\n                  {isConnected || status === \"connecting\" ? (\n                    <XIcon className=\"h-5 w-5\" />\n                  ) : (\n                    <PhoneIcon className=\"h-5 w-5\" />\n                  )}\n                </Button>\n              </div>\n            </div>\n          </div>\n\n          <div\n            className={cn(\n              \"overflow-hidden transition-all duration-300 ease-out\",\n              keyboardOpen ? \"max-h-[120px]\" : \"max-h-0\"\n            )}\n          >\n            <div className=\"relative px-2 pt-2 pb-2\">\n              <Textarea\n                value={textInput}\n                onChange={handleTextChange}\n                onKeyDown={handleKeyDown}\n                placeholder=\"Enter your message...\"\n                className=\"min-h-[100px] resize-none border-0 pr-12 shadow-none focus-visible:ring-0\"\n                disabled={!isConnected}\n              />\n              <Button\n                size=\"icon\"\n                variant=\"ghost\"\n                onClick={handleSendText}\n                disabled={!textInput.trim() || !isConnected}\n                className=\"absolute right-3 bottom-3 h-8 w-8\"\n              >\n                <ArrowUpIcon className=\"h-4 w-4\" />\n              </Button>\n            </div>\n          </div>\n        </div>\n      </Card>\n    </div>\n  )\n})\n\nConversationBar.displayName = \"ConversationBar\"\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}