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", }, }; type SourceType = "camera" | "screen"; interface ErrorInfo { type: (typeof ERROR_TYPES)[keyof typeof ERROR_TYPES]; message: string; } interface WebcamPermissionDialogProps { onPermissionGranted: (stream: MediaStream, sourceType: SourceType) => void; } export default function WebcamPermissionDialog({ onPermissionGranted, }: WebcamPermissionDialogProps) { const [isRequesting, setIsRequesting] = useState(false); const [error, setError] = useState(null); const [selectedSource, setSelectedSource] = 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 requestAccess = useCallback(async (sourceType: SourceType) => { setIsRequesting(true); setError(null); setSelectedSource(sourceType); try { let stream: MediaStream; if (sourceType === "camera") { if (!navigator.mediaDevices?.getUserMedia) { throw new Error("NOT_SUPPORTED"); } stream = await navigator.mediaDevices.getUserMedia(VIDEO_CONSTRAINTS); } else { // Screen capture if (!navigator.mediaDevices?.getDisplayMedia) { throw new Error("NOT_SUPPORTED"); } stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false, } as DisplayMediaStreamOptions); } onPermissionGranted(stream, sourceType); } catch (err) { const errorInfo = getErrorInfo(err); setError(errorInfo); console.error(`Error accessing ${sourceType}:`, err, errorInfo); } finally { setIsRequesting(false); } }, [onPermissionGranted]); 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 selectedSource === "screen" ? "Initialize Screen Capture" : "Initialize Camera"; if (error) return "Connection Failed"; return "Select Video Source"; }; const getDescription = () => { if (isRequesting) return "Requesting access to video source..."; if (error) return error.message; return "Choose your video source for real-time visual inference."; }; return ( <>
{/* Header Bar */}
{/* Icon Area */}
{isRequesting ? (
) : (
{error ? ( ) : ( )}
)}
{/* Text Content */}

{getTitle()}

{getDescription()}

{/* Source Selection Buttons */} {!isRequesting && !error && (
)} {/* Error Actions */} {error && (
{getTroubleshootingContent()}
)} {/* Loading State Helper */} {isRequesting && (

Waiting for browser permission...

)}
); }