"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { Card, CardHeader, CardTitle } from "@/components/ui"; import { useSettings } from "@/context/SettingsProvider"; import { useEmotionLogger } from "@/hooks/useEmotionLogger"; import { useFaceTracking } from "@/hooks/useFaceTracking"; import { useMediaStream } from "@/hooks/useMediaStream"; import { useTelemetryLog } from "@/hooks/useTelemetryLog"; import { formatDuration, formatTime } from "@/lib/format"; import { CameraFeed } from "./CameraFeed"; import { EmotionDisplay } from "./EmotionDisplay"; import { FaceOverlayCanvas } from "./FaceOverlayCanvas"; import { StatsPanel } from "./StatsPanel"; import { TelemetryConsole } from "./TelemetryConsole"; import { TrackingControls } from "./TrackingControls"; /** * The tracking tab. * * Owns only what coordinates its children: whether tracking is on. Per-frame data * never passes through here -- the canvas and the stats panel subscribe to it * directly, which is what keeps this component from re-rendering sixty times a * second along with everything below it. */ export function TrackingView() { const { settings } = useSettings(); const [isTracking, setIsTracking] = useState(false); const { videoRef, state: cameraState, error: cameraError, start, stop } = useMediaStream(); const { lines, push } = useTelemetryLog(); const { currentEmotion, faceDetected, fps, isLoaded, loadError, subscribe } = useFaceTracking( videoRef, isTracking && cameraState === "ready", ); const { lastLog, logError, connectionStatus, queueCount } = useEmotionLogger( currentEmotion, isTracking && cameraState === "ready", ); // Telemetry, driven by state changes rather than by eight separate effects in // the root component. const previousEmotion = useRef(currentEmotion); useEffect(() => { if (!isTracking) return; if (previousEmotion.current === currentEmotion) return; previousEmotion.current = currentEmotion; push(`Expression changed to ${currentEmotion}`); }, [currentEmotion, isTracking, push]); useEffect(() => { if (isLoaded) push("Face tracking model ready"); }, [isLoaded, push]); useEffect(() => { if (loadError) push(`Model error: ${loadError}`); }, [loadError, push]); useEffect(() => { if (cameraError) push(`Camera error: ${cameraError}`); }, [cameraError, push]); useEffect(() => { if (lastLog) push(`Logged ${lastLog.dominant_emotion} for ${lastLog.duration_seconds}s`); }, [lastLog, push]); useEffect(() => { if (logError) push(`Sync issue: ${logError}`); }, [logError, push]); const handleStart = useCallback(async () => { push("Starting camera"); await start(); setIsTracking(true); }, [start, push]); const handleStop = useCallback(() => { setIsTracking(false); // Releases the camera tracks, so the indicator light actually goes out. stop(); push("Tracking stopped, camera released"); }, [stop, push]); return (
{settings.showScannerGrid && isTracking && (
Last entry {lastLog ? (

{lastLog.dominant_emotion}

{formatDuration(lastLog.duration_seconds)} ยท {formatTime(lastLog.queued_at)}

) : (

Nothing logged yet.

)}
{settings.showTerminal && }
); }