'use client'; import React, { useEffect, useRef, useState } from 'react'; import { HARCore } from '@/lib/pose/HARCore'; // Import from the official tasks-vision package import { PoseLandmarker, FilesetResolver, DrawingUtils } from '@mediapipe/tasks-vision'; import { ArrowLeft, Activity, ShieldAlert, Ban, CheckCircle, Edit3, Trash2, MousePointerClick, BellRing } from 'lucide-react'; import Link from 'next/link'; import { AuthProvider, useAuth } from '@/lib/auth'; export default function MonitorPageWrap() { return ( ); } function MonitorPage() { const videoRef = useRef(null); const canvasRef = useRef(null); const [isLoading, setIsLoading] = useState(true); // State const [stats, setStats] = useState({ status: 'Initializing...', confidence: 0 }); // Safety Zone State (Normalized 0-1) const [safetyZone, setSafetyZone] = useState<{x: number, y: number, w: number, h: number} | null>(null); const [isEditingZone, setIsEditingZone] = useState(false); const [isDrawing, setIsDrawing] = useState(false); const [startPoint, setStartPoint] = useState<{x: number, y: number} | null>(null); const zoneRef = useRef<{x: number, y: number, w: number, h: number} | null>(null); // Alarm State const [alarmTriggered, setAlarmTriggered] = useState(false); const fallStartTimeRef = useRef(null); const [timeToAlarm, setTimeToAlarm] = useState(null); // Sync ref for loop access useEffect(() => { zoneRef.current = safetyZone; }, [safetyZone]); // Refs const harRef = useRef(null); const landmarkerRef = useRef(null); const requestRef = useRef(null); useEffect(() => { let isMounted = true; async function init() { try { // 1. Init Core const core = new HARCore(); harRef.current = core; // 2. Init Vision const vision = await FilesetResolver.forVisionTasks( "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.14/wasm" ); if (!isMounted) return; const landmarker = await PoseLandmarker.createFromOptions(vision, { baseOptions: { modelAssetPath: `https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_full/float16/1/pose_landmarker_full.task`, delegate: "GPU" }, runningMode: "VIDEO", numPoses: 1 }); landmarkerRef.current = landmarker; // 3. Init Camera if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { const stream = await navigator.mediaDevices.getUserMedia({ video: { width: 640, height: 480 } }); if (videoRef.current) { videoRef.current.srcObject = stream; await videoRef.current.play(); setIsLoading(false); requestRef.current = requestAnimationFrame(predictWebcam); } } } catch (e) { console.error("Init Error:", e); setIsLoading(false); } } init(); return () => { isMounted = false; if (requestRef.current) cancelAnimationFrame(requestRef.current); if (videoRef.current && videoRef.current.srcObject) { (videoRef.current.srcObject as MediaStream).getTracks().forEach(t => t.stop()); } }; }, []); // Logging // Logging const { user } = useAuth(); const userRef = useRef(user); // Keep userRef synced useEffect(() => { userRef.current = user; }, [user]); const lastLogRef = useRef(Date.now()); const alarmLoggedRef = useRef(false); const sendLog = async (data: any) => { if (!userRef.current) return; try { await fetch('/api/logs', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-user-id': userRef.current.id }, body: JSON.stringify(data) }); } catch (e) { console.error("Log failed", e); } }; // Loop const lastVideoTimeRef = useRef(-1); const predictWebcam = async () => { const video = videoRef.current; const canvas = canvasRef.current; const landmarker = landmarkerRef.current; const har = harRef.current; if (video && canvas && landmarker && har) { let startTimeMs = performance.now(); if (lastVideoTimeRef.current !== video.currentTime && video.videoWidth > 0 && video.videoHeight > 0) { lastVideoTimeRef.current = video.currentTime; const result = landmarker.detectForVideo(video, startTimeMs); // Draw const ctx = canvas.getContext('2d'); if (ctx) { ctx.save(); ctx.clearRect(0, 0, canvas.width, canvas.height); // Mirror ctx.scale(-1, 1); ctx.translate(-canvas.width, 0); // Draw Video Frame ctx.drawImage(video, 0, 0, canvas.width, canvas.height); if (result.landmarks) { const drawingUtils = new DrawingUtils(ctx); for (const lm of result.landmarks) { drawingUtils.drawLandmarks(lm, { radius: 1, color: '#00FF00' }); drawingUtils.drawConnectors(lm, PoseLandmarker.POSE_CONNECTIONS, { color: '#00FF00', lineWidth: 2 }); } } ctx.restore(); } // Process Logic if (result.landmarks && result.landmarks.length > 0) { const lm = result.landmarks[0]; // 1. Run HAR first (Always detect status) const res = await har.process(lm as any); if (res) { // 2. Check Safety Zone let isUnsafe = false; if (zoneRef.current) { const z = zoneRef.current; const inZone = (p: {x:number, y:number}) => p.x >= z.x && p.x <= (z.x + z.w) && p.y >= z.y && p.y <= (z.y + z.h); let outsideCount = 0; for (const point of lm) { // Convert to Screen Coords (Mirrored) const screenPoint = { x: 1 - point.x, y: point.y }; if (!inZone(screenPoint)) { outsideCount++; } } // Threshold: > 70% of points outside triggers Unsafe if ((outsideCount / lm.length) > 0.7) { isUnsafe = true; } } // Update Status setStats({ status: res.status, confidence: res.confidence || 0 }); // 3. Check Alarm Condition let currentAlarmState = false; if (res.status === 'Fall Detected' && isUnsafe) { const now = Date.now(); if (!fallStartTimeRef.current) { fallStartTimeRef.current = now; } const elapsed = now - fallStartTimeRef.current; if (elapsed > 10000) { // Trigger Alarm setAlarmTriggered(true); currentAlarmState = true; } else { // Update countdown for UI setTimeToAlarm(Math.ceil((10000 - elapsed) / 1000)); } } else { // Reset fallStartTimeRef.current = null; setTimeToAlarm(null); } // 4. Logging Logic const now = Date.now(); // A. Check for Alarm Log (Immediate) // If alarm just triggered (transition) or is buzzing if (currentAlarmState && !alarmLoggedRef.current) { sendLog({ status: 'ALARM: Fall Outside Zone', confidence: '1.0', details: { reason: 'Fall detected outside safe zone > 10s' } }); alarmLoggedRef.current = true; // Prevent spamming per frame } // B. Periodic Log (Every 1 min) if (now - lastLogRef.current > 60000) { sendLog({ status: res.status, confidence: String(res.confidence), details: { isUnsafe, zoneConfigured: !!zoneRef.current } }); lastLogRef.current = now; } } } } } requestRef.current = requestAnimationFrame(predictWebcam); }; // Drawing Handlers const handleMouseDown = (e: React.MouseEvent) => { if (!isEditingZone || !canvasRef.current) return; const rect = canvasRef.current.getBoundingClientRect(); const x = (e.clientX - rect.left) / rect.width; const y = (e.clientY - rect.top) / rect.height; setIsDrawing(true); setStartPoint({x, y}); setSafetyZone({x, y, w: 0, h: 0}); }; const handleMouseMove = (e: React.MouseEvent) => { if (!isDrawing || !startPoint || !canvasRef.current) return; const rect = canvasRef.current.getBoundingClientRect(); const currentX = (e.clientX - rect.left) / rect.width; const currentY = (e.clientY - rect.top) / rect.height; const w = Math.abs(currentX - startPoint.x); const h = Math.abs(currentY - startPoint.y); const x = Math.min(currentX, startPoint.x); const y = Math.min(currentY, startPoint.y); setSafetyZone({x, y, w, h}); }; const handleMouseUp = () => { setIsDrawing(false); }; return (

LIVE.MONITOR

System Active
{/* Main Camera View */}
{isLoading &&
Loading AI Engine (WASM)...
}
{/* Sidebar Controls */}

Safety Controls

{safetyZone && ( )}
{safetyZone ? (
Zone Active

Alarm triggers if a fall is detected OUTSIDE this zone for > 10 seconds.

) : (
No Zone

Click "Edit Safe Zone" and draw a box on the camera to define the safe area.

)}

Stats

Current State
{stats.status}
AI Confidence
{(stats.confidence * 100).toFixed(1)}%
); }