import { useState, useEffect, useCallback, useMemo } from "react"; import Button from "./Button"; import { THEME } from "../constants"; const ERROR_TYPES = { HTTPS: "https", NOT_SUPPORTED: "not-supported", PERMISSION: "permission", GENERAL: "general", } as const; const VIDEO_CONSTRAINTS = { video: { width: { ideal: 1920, max: 1920 }, height: { ideal: 1080, max: 1080 }, facingMode: "user", }, }; interface ErrorInfo { type: (typeof ERROR_TYPES)[keyof typeof ERROR_TYPES]; message: string; } interface WebcamPermissionDialogProps { onPermissionGranted: (stream: MediaStream) => void; } export default function WebcamPermissionDialog({ onPermissionGranted, }: WebcamPermissionDialogProps) { const [isRequesting, setIsRequesting] = useState(false); const [error, setError] = useState(null); const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); const getErrorInfo = (err: unknown): ErrorInfo => { if (!navigator.mediaDevices) { return { type: ERROR_TYPES.HTTPS, message: "Camera access requires a secure connection (HTTPS)", }; } if (!navigator.mediaDevices.getUserMedia) { return { type: ERROR_TYPES.NOT_SUPPORTED, message: "Camera access not supported in this browser", }; } if (err instanceof DOMException) { switch (err.name) { case "NotAllowedError": return { type: ERROR_TYPES.PERMISSION, message: "Camera access denied", }; case "NotFoundError": return { type: ERROR_TYPES.GENERAL, message: "No camera found", }; case "NotReadableError": return { type: ERROR_TYPES.GENERAL, message: "Camera is in use by another application", }; case "OverconstrainedError": return { type: ERROR_TYPES.GENERAL, message: "Camera doesn't meet requirements", }; case "SecurityError": return { type: ERROR_TYPES.HTTPS, message: "Security error accessing camera", }; default: return { type: ERROR_TYPES.GENERAL, message: `Camera error: ${err.name}`, }; } } return { type: ERROR_TYPES.GENERAL, message: "Failed to access camera", }; }; const requestWebcamAccess = useCallback(async () => { setIsRequesting(true); setError(null); try { if (!navigator.mediaDevices?.getUserMedia) { throw new Error("NOT_SUPPORTED"); } const stream = await navigator.mediaDevices.getUserMedia(VIDEO_CONSTRAINTS); onPermissionGranted(stream); } catch (err) { const errorInfo = getErrorInfo(err); setError(errorInfo); console.error("Error accessing webcam:", err, errorInfo); } finally { setIsRequesting(false); } }, [onPermissionGranted]); useEffect(() => { requestWebcamAccess(); }, [requestWebcamAccess]); const troubleshootingData = useMemo( () => ({ [ERROR_TYPES.HTTPS]: { title: "HTTPS Required", items: [ "Access this app via https:// instead of http://", "If developing locally, use localhost", "Deploy to a secure hosting service (Hugging Face spaces)", ], }, [ERROR_TYPES.NOT_SUPPORTED]: { title: "Browser Compatibility", items: [ "Update your browser to the latest version", "Try Chrome, Edge, or Firefox", "Enable JavaScript if disabled", ], }, [ERROR_TYPES.PERMISSION]: { title: "Permission Issues", items: [ "Click the camera icon in your address bar", 'Select "Always allow" for camera access', "Check System Preferences → Security & Privacy", "Refresh the page after changing settings", ], }, [ERROR_TYPES.GENERAL]: { title: "General Troubleshooting", items: [ "Ensure no other apps (Zoom, Teams) are using the camera", "Try unplugging and replugging your webcam", "Try using a different browser", ], }, }), [], ); const getTroubleshootingContent = () => { if (!error) return null; const content = troubleshootingData[error.type]; return (

Troubleshooting

{content.title}

    {content.items.map((item, index) => (
  • / {" "} {item}
  • ))}
{/* Technical Details Footer */}

Diagnostics

LOC: {window.location.protocol}//{window.location.host}
ERR: {error.type.toUpperCase()}
); }; const getTitle = () => { if (isRequesting) return "Initialize Camera"; if (error) return "Connection Failed"; return "Permission Required"; }; const getDescription = () => { if (isRequesting) return "Requesting access to video input device..."; if (error) return error.message; return "Ministral WebGPU requires local camera access for real-time inference."; }; return ( <>
{/* Header Bar */}
{/* Icon Area */}
{isRequesting ? (
) : (
{error ? ( ) : ( )}
)}
{/* Text Content */}

{getTitle()}

{getDescription()}

{/* Error Actions */} {error && (
{getTroubleshootingContent()}
)} {/* Loading State Helper */} {isRequesting && (

Waiting for browser permission...

)}
); }