Spaces:
Running
Running
Download frontend/components/tracking/TrackingView.tsx from Aynkhn/Face-Expression-Analyzer: direct link, hf CLI and curl.
- Browser
- Download file 5.74 kB
-
https://huggingface.co/spaces/Aynkhn/Face-Expression-Analyzer/resolve/main/frontend/components/tracking/TrackingView.tsx
- Command line
-
hf download hf://spaces/Aynkhn/Face-Expression-Analyzer/frontend/components/tracking/TrackingView.tsx
-
curl -L -o TrackingView.tsx https://huggingface.co/spaces/Aynkhn/Face-Expression-Analyzer/resolve/main/frontend/components/tracking/TrackingView.tsx
5.74 kB
| "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 ( | |
| <div className="grid gap-6 lg:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]"> | |
| <div className="flex flex-col gap-4"> | |
| <CameraFeed | |
| videoRef={videoRef} | |
| state={cameraState} | |
| error={cameraError} | |
| onRetry={handleStart} | |
| > | |
| <FaceOverlayCanvas | |
| videoRef={videoRef} | |
| subscribe={subscribe} | |
| active={isTracking && isLoaded} | |
| emotion={currentEmotion} | |
| faceDetected={faceDetected} | |
| /> | |
| {settings.showScannerGrid && isTracking && ( | |
| <div | |
| aria-hidden="true" | |
| className="pointer-events-none absolute inset-0 opacity-20" | |
| style={{ | |
| backgroundImage: | |
| "linear-gradient(rgba(96,165,250,.35) 1px, transparent 1px), linear-gradient(90deg, rgba(96,165,250,.35) 1px, transparent 1px)", | |
| backgroundSize: "40px 40px", | |
| }} | |
| /> | |
| )} | |
| </CameraFeed> | |
| <TrackingControls | |
| isTracking={isTracking} | |
| canStart={isLoaded && cameraState !== "requesting"} | |
| starting={cameraState === "requesting"} | |
| onStart={handleStart} | |
| onStop={handleStop} | |
| syncIntervalMs={settings.syncIntervalMs} | |
| connectionStatus={connectionStatus} | |
| queueCount={queueCount} | |
| /> | |
| {loadError && ( | |
| <p role="alert" className="text-danger text-sm"> | |
| {loadError} | |
| </p> | |
| )} | |
| {/* Surfaced rather than silently dropped: the previous root component | |
| destructured logError and never rendered it. */} | |
| {logError && ( | |
| <p role="status" className="text-warning text-sm"> | |
| {logError} | |
| </p> | |
| )} | |
| </div> | |
| <div className="flex flex-col gap-4"> | |
| <EmotionDisplay emotion={currentEmotion} faceDetected={faceDetected} /> | |
| <StatsPanel subscribe={subscribe} fps={fps} /> | |
| <Card padding="sm"> | |
| <CardHeader className="mb-2"> | |
| <CardTitle className="text-sm">Last entry</CardTitle> | |
| </CardHeader> | |
| {lastLog ? ( | |
| <div className="flex flex-col gap-1"> | |
| <p className="text-content-primary text-sm font-medium">{lastLog.dominant_emotion}</p> | |
| <p className="text-content-muted text-xs"> | |
| {formatDuration(lastLog.duration_seconds)} · {formatTime(lastLog.queued_at)} | |
| </p> | |
| </div> | |
| ) : ( | |
| <p className="text-content-muted text-xs">Nothing logged yet.</p> | |
| )} | |
| </Card> | |
| {settings.showTerminal && <TelemetryConsole lines={lines} />} | |
| </div> | |
| </div> | |
| ); | |
| } | |