84 lines
2.4 KiB
TypeScript
84 lines
2.4 KiB
TypeScript
/* eslint-disable */
|
|
// @ts-nocheck
|
|
"use client";
|
|
|
|
import React, { useEffect, useRef } from 'react';
|
|
|
|
// Using dynamic import to avoid SSR issues with TFJS
|
|
type BlazefaceModel = { estimateFaces: (video: HTMLVideoElement, returnTensors: boolean) => Promise<unknown[]> };
|
|
let blazeface: { load: () => Promise<BlazefaceModel> };
|
|
|
|
export function NeuralAttentionEngine({
|
|
videoRef,
|
|
onAttentionChange
|
|
}: {
|
|
videoRef: React.RefObject<HTMLVideoElement | null>;
|
|
onAttentionChange: (isAttentive: boolean) => void;
|
|
}) {
|
|
const rafRef = useRef<number>(0);
|
|
const lastAttentionTimeRef = useRef<number>(0);
|
|
|
|
useEffect(() => {
|
|
let isMounted = true;
|
|
|
|
async function loadModel() {
|
|
try {
|
|
// TAHAP NANO: SOVEREIGN NEURAL ENGINE
|
|
// Menggunakan Native Shape Detection API (OS Level)
|
|
// 100% Air-Gapped, No Google CDN, No TensorFlow Weights!
|
|
let faceDetector: any = null;
|
|
if ('FaceDetector' in window) {
|
|
faceDetector = new (window as any).FaceDetector();
|
|
}
|
|
|
|
if (isMounted) {
|
|
startTracking(faceDetector);
|
|
}
|
|
} catch (err) {
|
|
console.error("Failed to load Neural Attention Engine:", err);
|
|
}
|
|
}
|
|
|
|
async function startTracking(model: any) {
|
|
if (!videoRef.current || videoRef.current.readyState < 2) {
|
|
rafRef.current = requestAnimationFrame(() => startTracking(model));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const now = Date.now();
|
|
if (lastAttentionTimeRef.current === 0) lastAttentionTimeRef.current = now;
|
|
|
|
const faces = model ? await model.detect(videoRef.current) : [];
|
|
|
|
if (faces.length > 0) {
|
|
// Face detected
|
|
lastAttentionTimeRef.current = now;
|
|
onAttentionChange(true);
|
|
} else {
|
|
// If no face detected for more than 3 seconds, user is not attentive
|
|
if (now - lastAttentionTimeRef.current > 3000) {
|
|
onAttentionChange(false);
|
|
}
|
|
}
|
|
} catch {
|
|
// Ignore detection errors
|
|
}
|
|
|
|
// Check again next frame
|
|
rafRef.current = requestAnimationFrame(() => startTracking(model));
|
|
}
|
|
|
|
loadModel();
|
|
|
|
return () => {
|
|
isMounted = false;
|
|
if (rafRef.current) {
|
|
cancelAnimationFrame(rafRef.current);
|
|
}
|
|
};
|
|
}, [videoRef, onAttentionChange]);
|
|
|
|
return null; // This is a headless component
|
|
}
|