{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "mic-selector",
  "registryDependencies": [
    "button",
    "card",
    "dropdown-menu",
    "https://ui.elevenlabs.io/r/live-waveform.json"
  ],
  "files": [
    {
      "path": "components/ui/mic-selector.tsx",
      "content": "\"use client\"\n\nimport { useCallback, useEffect, useState } from \"react\"\nimport { Check, ChevronsUpDown, Mic, MicOff } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport { LiveWaveform } from \"@/components/ui/live-waveform\"\n\nexport interface AudioDevice {\n  deviceId: string\n  label: string\n  groupId: string\n}\n\nexport interface MicSelectorProps {\n  value?: string\n  onValueChange?: (deviceId: string) => void\n  muted?: boolean\n  onMutedChange?: (muted: boolean) => void\n  disabled?: boolean\n  className?: string\n}\n\nexport function MicSelector({\n  value,\n  onValueChange,\n  muted,\n  onMutedChange,\n  disabled,\n  className,\n}: MicSelectorProps) {\n  const { devices, loading, error, hasPermission, loadDevices } =\n    useAudioDevices()\n  const [selectedDevice, setSelectedDevice] = useState<string>(value || \"\")\n  const [internalMuted, setInternalMuted] = useState(false)\n  const [isDropdownOpen, setIsDropdownOpen] = useState(false)\n\n  // Use controlled muted if provided, otherwise use internal state\n  const isMuted = muted !== undefined ? muted : internalMuted\n\n  // Update internal state when controlled value changes\n  useEffect(() => {\n    if (value !== undefined) {\n      setSelectedDevice(value)\n    }\n  }, [value])\n\n  // Select first device by default\n  const defaultDeviceId = devices[0]?.deviceId || \"\"\n  useEffect(() => {\n    if (!selectedDevice && defaultDeviceId) {\n      const newDevice = defaultDeviceId\n      setSelectedDevice(newDevice)\n      onValueChange?.(newDevice)\n    }\n  }, [defaultDeviceId, selectedDevice, onValueChange])\n\n  const currentDevice = devices.find((d) => d.deviceId === selectedDevice) ||\n    devices[0] || {\n      label: loading ? \"Loading...\" : \"No microphone\",\n      deviceId: \"\",\n    }\n\n  const handleDeviceSelect = (deviceId: string, e?: React.MouseEvent) => {\n    e?.preventDefault()\n    setSelectedDevice(deviceId)\n    onValueChange?.(deviceId)\n  }\n\n  const handleDropdownOpenChange = async (open: boolean) => {\n    setIsDropdownOpen(open)\n    if (open && !hasPermission && !loading) {\n      await loadDevices()\n    }\n  }\n\n  const toggleMute = () => {\n    const newMuted = !isMuted\n    if (muted === undefined) {\n      setInternalMuted(newMuted)\n    }\n    onMutedChange?.(newMuted)\n  }\n\n  const isPreviewActive = isDropdownOpen && !isMuted\n\n  return (\n    <DropdownMenu onOpenChange={handleDropdownOpenChange}>\n      <DropdownMenuTrigger asChild>\n        <Button\n          variant=\"ghost\"\n          size=\"sm\"\n          className={cn(\n            \"hover:bg-accent flex w-40 min-w-0 shrink cursor-pointer items-center gap-1.5 sm:w-48\",\n            className\n          )}\n          disabled={loading || disabled}\n        >\n          {isMuted ? (\n            <MicOff className=\"h-4 w-4 flex-shrink-0\" />\n          ) : (\n            <Mic className=\"h-4 w-4 flex-shrink-0\" />\n          )}\n          <span className=\"min-w-0 flex-1 truncate text-left text-xs sm:text-sm\">\n            {currentDevice.label}\n          </span>\n          <ChevronsUpDown className=\"h-3 w-3 flex-shrink-0\" />\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"center\" side=\"top\" className=\"w-72\">\n        {loading ? (\n          <DropdownMenuItem disabled>Loading devices...</DropdownMenuItem>\n        ) : error ? (\n          <DropdownMenuItem disabled>Error: {error}</DropdownMenuItem>\n        ) : (\n          devices.map((device) => (\n            <DropdownMenuItem\n              key={device.deviceId}\n              onClick={(e) => handleDeviceSelect(device.deviceId, e)}\n              onSelect={(e) => e.preventDefault()}\n              className=\"flex items-center justify-between\"\n            >\n              <span className=\"truncate\">{device.label}</span>\n              {selectedDevice === device.deviceId && (\n                <Check className=\"h-4 w-4 flex-shrink-0\" />\n              )}\n            </DropdownMenuItem>\n          ))\n        )}\n        {devices.length > 0 && (\n          <>\n            <DropdownMenuSeparator />\n            <div className=\"flex items-center gap-2 p-2\">\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                onClick={(e) => {\n                  e.preventDefault()\n                  toggleMute()\n                }}\n                className=\"h-8 gap-2\"\n              >\n                {isMuted ? (\n                  <MicOff className=\"h-4 w-4\" />\n                ) : (\n                  <Mic className=\"h-4 w-4\" />\n                )}\n                <span className=\"text-sm\">{isMuted ? \"Unmute\" : \"Mute\"}</span>\n              </Button>\n              <div className=\"bg-accent ml-auto w-16 overflow-hidden rounded-md p-1.5\">\n                <LiveWaveform\n                  active={isPreviewActive}\n                  deviceId={selectedDevice || defaultDeviceId}\n                  mode=\"static\"\n                  height={15}\n                  barWidth={3}\n                  barGap={1}\n                />\n              </div>\n            </div>\n          </>\n        )}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\nexport function useAudioDevices() {\n  const [devices, setDevices] = useState<AudioDevice[]>([])\n  const [loading, setLoading] = useState(true)\n  const [error, setError] = useState<string | null>(null)\n  const [hasPermission, setHasPermission] = useState(false)\n\n  const loadDevicesWithoutPermission = useCallback(async () => {\n    try {\n      setLoading(true)\n      setError(null)\n\n      const deviceList = await navigator.mediaDevices.enumerateDevices()\n\n      const audioInputs = deviceList\n        .filter((device) => device.kind === \"audioinput\")\n        .map((device) => {\n          let cleanLabel =\n            device.label || `Microphone ${device.deviceId.slice(0, 8)}`\n          cleanLabel = cleanLabel.replace(/\\s*\\([^)]*\\)/g, \"\").trim()\n\n          return {\n            deviceId: device.deviceId,\n            label: cleanLabel,\n            groupId: device.groupId,\n          }\n        })\n\n      setDevices(audioInputs)\n    } catch (err) {\n      setError(\n        err instanceof Error ? err.message : \"Failed to get audio devices\"\n      )\n      console.error(\"Error getting audio devices:\", err)\n    } finally {\n      setLoading(false)\n    }\n  }, [])\n\n  const loadDevicesWithPermission = useCallback(async () => {\n    if (loading) return\n\n    try {\n      setLoading(true)\n      setError(null)\n\n      const tempStream = await navigator.mediaDevices.getUserMedia({\n        audio: true,\n      })\n      tempStream.getTracks().forEach((track) => track.stop())\n\n      const deviceList = await navigator.mediaDevices.enumerateDevices()\n\n      const audioInputs = deviceList\n        .filter((device) => device.kind === \"audioinput\")\n        .map((device) => {\n          let cleanLabel =\n            device.label || `Microphone ${device.deviceId.slice(0, 8)}`\n          cleanLabel = cleanLabel.replace(/\\s*\\([^)]*\\)/g, \"\").trim()\n\n          return {\n            deviceId: device.deviceId,\n            label: cleanLabel,\n            groupId: device.groupId,\n          }\n        })\n\n      setDevices(audioInputs)\n      setHasPermission(true)\n    } catch (err) {\n      setError(\n        err instanceof Error ? err.message : \"Failed to get audio devices\"\n      )\n      console.error(\"Error getting audio devices:\", err)\n    } finally {\n      setLoading(false)\n    }\n  }, [loading])\n\n  useEffect(() => {\n    loadDevicesWithoutPermission()\n  }, [loadDevicesWithoutPermission])\n\n  useEffect(() => {\n    const handleDeviceChange = () => {\n      if (hasPermission) {\n        loadDevicesWithPermission()\n      } else {\n        loadDevicesWithoutPermission()\n      }\n    }\n\n    navigator.mediaDevices.addEventListener(\"devicechange\", handleDeviceChange)\n\n    return () => {\n      navigator.mediaDevices.removeEventListener(\n        \"devicechange\",\n        handleDeviceChange\n      )\n    }\n  }, [hasPermission, loadDevicesWithPermission, loadDevicesWithoutPermission])\n\n  return {\n    devices,\n    loading,\n    error,\n    hasPermission,\n    loadDevices: loadDevicesWithPermission,\n  }\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}