{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "face-detection",
  "title": "Face Detection",
  "description": "Face detection using MediaPipe Tasks Vision.",
  "dependencies": [
    "@mediapipe/tasks-vision"
  ],
  "files": [
    {
      "path": "components/face-detection/FaceDetection.tsx",
      "content": "\"use client\";\r\n\r\nimport { useEffect, useRef, useState } from \"react\";\r\nimport { Camera, CameraOff, LoaderCircle } from \"lucide-react\";\r\nimport {\r\n  DrawingUtils,\r\n  FaceDetector,\r\n  FilesetResolver,\r\n} from \"@mediapipe/tasks-vision\";\r\n\r\ntype CameraPosition = \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\";\r\n\r\ninterface FaceDetectionProps {\r\n  position: CameraPosition;\r\n  boxColor: string;\r\n  labelColor: string;\r\n  size: number;\r\n}\r\n\r\nfunction FaceDetection({\r\n  position,\r\n  boxColor,\r\n  labelColor,\r\n  size,\r\n}: FaceDetectionProps) {\r\n  const safePosition = position ?? \"bottom-right\";\r\n  const safeBoxColor = boxColor ?? \"#7dd3fc\";\r\n  const safeLabelColor = labelColor ?? \"#0f172a\";\r\n\r\n  const videoRef = useRef<HTMLVideoElement>(null);\r\n  const canvasRef = useRef<HTMLCanvasElement>(null);\r\n  const detectorRef = useRef<FaceDetector | null>(null);\r\n  const streamRef = useRef<MediaStream | null>(null);\r\n  const animationFrameRef = useRef<number | null>(null);\r\n  const webcamRunningRef = useRef(false);\r\n  const [isModelReady, setIsModelReady] = useState(false);\r\n  const [isCameraRunning, setIsCameraRunning] = useState(false);\r\n  const [isCameraStarting, setIsCameraStarting] = useState(false);\r\n  const [faceDetected, setFaceDetected] = useState(false);\r\n  const [errorMessage, setErrorMessage] = useState<string | null>(null);\r\n\r\n  const cameraPositionClasses: Record<CameraPosition, string> = {\r\n    \"top-left\": \"left-4 top-4 sm:left-6 sm:top-6\",\r\n    \"top-right\": \"right-4 top-4 sm:right-6 sm:top-6\",\r\n    \"bottom-left\": \"bottom-4 left-4 sm:bottom-6 sm:left-6\",\r\n    \"bottom-right\": \"bottom-4 right-4 sm:bottom-6 sm:right-6\",\r\n  };\r\n\r\n  useEffect(() => {\r\n    let cancelled = false;\r\n\r\n    async function createDetector() {\r\n      try {\r\n        const vision = await FilesetResolver.forVisionTasks(\r\n          \"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@1.1.0/wasm\",\r\n        );\r\n\r\n        const detectorOptions = {\r\n          baseOptions: {\r\n            modelAssetPath:\r\n              \"https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.tflite\",\r\n          },\r\n          runningMode: \"VIDEO\" as const,\r\n          minDetectionConfidence: 0.75,\r\n          minSuppressionThreshold: 0.3,\r\n        };\r\n        let detector: FaceDetector;\r\n\r\n        try {\r\n          detector = await FaceDetector.createFromOptions(vision, {\r\n            ...detectorOptions,\r\n            baseOptions: { ...detectorOptions.baseOptions, delegate: \"GPU\" },\r\n          });\r\n        } catch (gpuError) {\r\n          console.warn(\"GPU face detection unavailable; retrying with CPU:\", gpuError);\r\n          detector = await FaceDetector.createFromOptions(vision, {\r\n            ...detectorOptions,\r\n            baseOptions: { ...detectorOptions.baseOptions, delegate: \"CPU\" },\r\n          });\r\n        }\r\n\r\n        if (cancelled) {\r\n          detector.close();\r\n          return;\r\n        }\r\n\r\n        detectorRef.current = detector;\r\n        setIsModelReady(true);\r\n      } catch (error) {\r\n        console.error(\"Failed to load the face detection model:\", error);\r\n        if (!cancelled) {\r\n          setErrorMessage(\"Could not load the face detector. Check your connection and reload.\");\r\n        }\r\n      }\r\n    }\r\n\r\n    void createDetector();\r\n\r\n    return () => {\r\n      cancelled = true;\r\n      webcamRunningRef.current = false;\r\n      if (animationFrameRef.current !== null) {\r\n        window.cancelAnimationFrame(animationFrameRef.current);\r\n      }\r\n      streamRef.current?.getTracks().forEach((track) => track.stop());\r\n      detectorRef.current?.close();\r\n    };\r\n  }, []);\r\n\r\n  async function toggleCamera() {\r\n    if (webcamRunningRef.current) {\r\n      webcamRunningRef.current = false;\r\n      setIsCameraRunning(false);\r\n      if (animationFrameRef.current !== null) {\r\n        window.cancelAnimationFrame(animationFrameRef.current);\r\n        animationFrameRef.current = null;\r\n      }\r\n      streamRef.current?.getTracks().forEach((track) => track.stop());\r\n      streamRef.current = null;\r\n\r\n      if (videoRef.current) {\r\n        videoRef.current.srcObject = null;\r\n      }\r\n\r\n      const canvas = canvasRef.current;\r\n      const context = canvas?.getContext(\"2d\");\r\n      if (canvas && context) {\r\n        context.clearRect(0, 0, canvas.width, canvas.height);\r\n      }\r\n      setFaceDetected(false);\r\n      return;\r\n    }\r\n\r\n    setIsCameraStarting(true);\r\n    setErrorMessage(null);\r\n    try {\r\n      const stream = await navigator.mediaDevices.getUserMedia({\r\n        video: { facingMode: \"user\" },\r\n      });\r\n\r\n      const video = videoRef.current;\r\n      if (!video) {\r\n        stream.getTracks().forEach((track) => track.stop());\r\n        return;\r\n      }\r\n\r\n      streamRef.current = stream;\r\n      video.srcObject = stream;\r\n      await video.play();\r\n\r\n      webcamRunningRef.current = true;\r\n      setIsCameraRunning(true);\r\n      animationFrameRef.current = window.requestAnimationFrame((timestamp) => {\r\n        void predictWebcam(timestamp);\r\n      });\r\n    } catch (error) {\r\n      streamRef.current?.getTracks().forEach((track) => track.stop());\r\n      streamRef.current = null;\r\n      console.error(\"Failed to start the camera:\", error);\r\n      setErrorMessage(\r\n        error instanceof Error && error.name === \"NotAllowedError\"\r\n          ? \"Camera access was denied. Allow camera access in your browser settings and try again.\"\r\n          : \"Could not start the camera. Check that it is connected and not being used by another app.\",\r\n      );\r\n    } finally {\r\n      setIsCameraStarting(false);\r\n    }\r\n  }\r\n\r\n  async function predictWebcam(timestamp: number) {\r\n    const video = videoRef.current;\r\n    const canvas = canvasRef.current;\r\n    const detector = detectorRef.current;\r\n\r\n    if (!video || !canvas || !webcamRunningRef.current) {\r\n      return;\r\n    }\r\n\r\n    if (!detector || video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA || video.videoWidth === 0) {\r\n      animationFrameRef.current = window.requestAnimationFrame((frameTime) => {\r\n        void predictWebcam(frameTime);\r\n      });\r\n      return;\r\n    }\r\n\r\n    const canvasContext = canvas.getContext(\"2d\");\r\n    if (!canvasContext) {\r\n      setErrorMessage(\"Could not create the face detection canvas.\");\r\n      webcamRunningRef.current = false;\r\n      setIsCameraRunning(false);\r\n      streamRef.current?.getTracks().forEach((track) => track.stop());\r\n      streamRef.current = null;\r\n      return;\r\n    }\r\n\r\n    if (canvas.width !== video.videoWidth || canvas.height !== video.videoHeight) {\r\n      canvas.width = video.videoWidth;\r\n      canvas.height = video.videoHeight;\r\n    }\r\n\r\n    let detections;\r\n    try {\r\n      detections = detector.detectForVideo(video, timestamp).detections;\r\n    } catch (error) {\r\n      console.error(\"Face detection failed:\", error);\r\n      setErrorMessage(\"Face detection failed. Please restart the camera.\");\r\n      webcamRunningRef.current = false;\r\n      setIsCameraRunning(false);\r\n      streamRef.current?.getTracks().forEach((track) => track.stop());\r\n      streamRef.current = null;\r\n      return;\r\n    }\r\n\r\n    const faceDetections = detections.filter(\r\n      (detection) => (detection.categories?.[0]?.score ?? 0) >= 0.75,\r\n    );\r\n\r\n    canvasContext.clearRect(0, 0, canvas.width, canvas.height);\r\n    setFaceDetected(faceDetections.length > 0);\r\n\r\n    if (faceDetections.length > 0) {\r\n      const drawingUtils = new DrawingUtils(canvasContext);\r\n      canvasContext.font = \"16px sans-serif\";\r\n      for (const detection of faceDetections) {\r\n        if (!detection.boundingBox) continue;\r\n\r\n        const box = detection.boundingBox;\r\n        const mirroredBox = {\r\n          ...box,\r\n          originX: canvas.width - box.originX - box.width,\r\n        };\r\n        drawingUtils.drawBoundingBox(mirroredBox, {\r\n          color: safeBoxColor,\r\n          lineWidth: size,\r\n          fillColor: \"transparent\",\r\n        });\r\n\r\n        const category = detection.categories?.[0];\r\n        const score = category?.score ? Math.round(category.score * 100) : 0;\r\n        const labelText = `Face - ${score}%`;\r\n        const textWidth = canvasContext.measureText(labelText).width;\r\n\r\n        canvasContext.fillStyle = safeLabelColor;\r\n        canvasContext.fillRect(mirroredBox.originX, mirroredBox.originY, textWidth + 12, 26);\r\n        canvasContext.fillStyle = \"#ffffff\";\r\n        canvasContext.fillText(labelText, mirroredBox.originX + 6, mirroredBox.originY + 18);\r\n      }\r\n    }\r\n\r\n    if (webcamRunningRef.current) {\r\n      animationFrameRef.current = window.requestAnimationFrame((frameTime) => {\r\n        void predictWebcam(frameTime);\r\n      });\r\n    }\r\n  }\r\n\r\n  return (\r\n    <main className=\"relative min-h-screen w-full overflow-hidden bg-[#090a0c]\">\r\n      <div\r\n        className={`absolute z-10 aspect-video w-[min(24rem,calc(100vw-2rem))] overflow-hidden rounded-2xl border border-white/[0.09] bg-[#0e1012] shadow-2xl shadow-black/50 sm:w-[min(28rem,calc(100vw-3rem))] ${cameraPositionClasses[safePosition] ?? cameraPositionClasses[\"bottom-right\"]}`}\r\n      >\r\n        <video\r\n          ref={videoRef}\r\n          autoPlay\r\n          muted\r\n          playsInline\r\n          aria-label=\"Camera preview\"\r\n          className={`absolute inset-0 size-full object-cover ${isCameraRunning ? \"scale-x-[-1]\" : \"hidden\"}`}\r\n        />\r\n\r\n        <canvas\r\n          ref={canvasRef}\r\n          aria-hidden=\"true\"\r\n          className={`absolute inset-0 size-full object-cover ${isCameraRunning ? \"block\" : \"hidden\"}`}\r\n        />\r\n\r\n        {!isCameraRunning && (\r\n          <div className=\"absolute inset-0 flex flex-col items-center justify-center px-4 text-center\">\r\n            <CameraOff className=\"mb-3 size-7 text-[#59c596]\" aria-hidden=\"true\" />\r\n            <p className=\"text-sm font-medium text-[#f2f3f4]\">\r\n              {errorMessage ?? \"Camera is off\"}\r\n            </p>\r\n            <p className=\"mt-1 text-xs text-[#92969b]\">\r\n              {isModelReady\r\n                ? \"Press the camera button to start\"\r\n                : errorMessage\r\n                  ? \"Face detection could not be initialized\"\r\n                  : \"Face detection is loading\"}\r\n            </p>\r\n          </div>\r\n        )}\r\n\r\n        {isCameraRunning && (\r\n          <div\r\n            className=\"absolute bottom-3 left-3 rounded-full border border-white/10 bg-[#101311]/90 px-2.5 py-1 text-xs text-[#f2f3f4] shadow-lg backdrop-blur\"\r\n            role=\"status\"\r\n            aria-live=\"polite\"\r\n          >\r\n            {faceDetected ? \"Face detected\" : \"No face detected\"}\r\n          </div>\r\n        )}\r\n\r\n        <button\r\n          type=\"button\"\r\n          onClick={() => void toggleCamera()}\r\n          disabled={!isModelReady || isCameraStarting}\r\n          aria-label={\r\n            isCameraStarting\r\n              ? \"Starting camera\"\r\n              : isCameraRunning\r\n                ? \"Turn camera off\"\r\n                : \"Turn camera on\"\r\n          }\r\n          title={\r\n            isCameraStarting\r\n              ? \"Starting camera\"\r\n              : isCameraRunning\r\n                ? \"Turn camera off\"\r\n                : \"Turn camera on\"\r\n          }\r\n          className=\"absolute right-3 top-3 z-20 flex size-10 items-center justify-center rounded-full border border-white/10 bg-[#101311]/90 text-white shadow-lg backdrop-blur transition hover:border-[#59c596]/40 hover:bg-[#171c19] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#59c596] disabled:cursor-wait disabled:opacity-70\"\r\n        >\r\n          {isCameraStarting ? (\r\n            <LoaderCircle className=\"size-4 animate-spin text-[#59c596]\" aria-hidden=\"true\" />\r\n          ) : isCameraRunning ? (\r\n            <CameraOff className=\"size-4 text-[#d1d5d8]\" aria-hidden=\"true\" />\r\n          ) : (\r\n            <Camera className=\"size-4 text-[#59c596]\" aria-hidden=\"true\" />\r\n          )}\r\n        </button>\r\n      </div>\r\n    </main>\r\n  );\r\n}\r\n\r\nexport default FaceDetection;",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}