import { useState, useEffect, useCallback } from "react"; import { useSettings } from "../../hooks/useSettings"; interface AudioDevice { deviceId: string; label: string; } export default function MicrophoneSettings() { const { appSettings, saveSettings } = useSettings(); const [devices, setDevices] = useState([]); const [selected, setSelected] = useState(appSettings?.default_microphone ?? ""); const [loading, setLoading] = useState(false); const [permissionNeeded, setPermissionNeeded] = useState(false); // Sync local state when appSettings change useEffect(() => { setSelected(appSettings?.default_microphone ?? ""); }, [appSettings?.default_microphone]); const enumerateDevices = useCallback(async () => { setLoading(true); setPermissionNeeded(false); try { // Request mic permission first so device labels are available const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); stream.getTracks().forEach((t) => t.stop()); const allDevices = await navigator.mediaDevices.enumerateDevices(); const mics = allDevices .filter((d) => d.kind === "audioinput") .map((d) => ({ deviceId: d.deviceId, label: d.label || `Microphone (${d.deviceId.slice(0, 8)}...)`, })); setDevices(mics); } catch { setPermissionNeeded(true); } finally { setLoading(false); } }, []); // Enumerate devices on mount useEffect(() => { enumerateDevices(); }, [enumerateDevices]); const handleChange = async (deviceId: string) => { setSelected(deviceId); if (appSettings) { await saveSettings({ ...appSettings, default_microphone: deviceId || null }); } }; return (

Audio input device for Claude Code voice mode (/voice)

{permissionNeeded ? (
Microphone permission required
) : (
)}
); }