import React, { useState, useEffect } from 'react';
import CameraFeed from './components/CameraFeed';
import DashboardStats from './components/DashboardStats';
import FaceAnalyzerGame from './components/FaceAnalyzerGame';
import { Shield, ShieldAlert, Monitor, Gamepad2, Info, Settings, RefreshCw } from 'lucide-react';
function App() {
const [activeTab, setActiveTab] = useState('monitor');
const [peopleCount, setPeopleCount] = useState(0);
const [objects, setObjects] = useState([]);
const [alerts, setAlerts] = useState([]);
const [faceAnalyses, setFaceAnalyses] = useState({});
const [gameState, setGameState] = useState({ active: false, score: 0, high_score: 0 });
const [logs, setLogs] = useState([]);
const [timeStr, setTimeStr] = useState('');
// Settings State
const [showSettings, setShowSettings] = useState(false);
const [wsUrlInput, setWsUrlInput] = useState('');
const [wsUrl, setWsUrl] = useState('');
// Keep track of previously detected objects to log when they appear/disappear
const [prevObjects, setPrevObjects] = useState([]);
// Load and calculate default WebSocket URL
useEffect(() => {
const serverIp = window.location.hostname || "localhost";
// Check if we have a saved URL in localStorage
const savedUrl = localStorage.getItem('laring_ws_url');
let defaultUrl = `ws://${serverIp}:8000/ws`;
const envWsUrl = import.meta.env.VITE_WS_URL;
if (envWsUrl) {
if (envWsUrl.startsWith('http://') || envWsUrl.startsWith('https://')) {
defaultUrl = envWsUrl.replace(/^http/, 'ws').replace(/\/$/, '') + '/ws';
} else {
// If it's just a hostname, use wss://
defaultUrl = `wss://${envWsUrl}/ws`;
}
}
const activeUrl = savedUrl || defaultUrl;
setWsUrl(activeUrl);
setWsUrlInput(activeUrl);
}, []);
// Fetch historical alerts from Supabase via backend API
useEffect(() => {
if (!wsUrl) return;
// Derive HTTP base URL from WebSocket URL
let httpBaseUrl = wsUrl.replace(/^ws/, 'http').replace(/\/ws$/, '');
fetch(`${httpBaseUrl}/api/history/alerts`)
.then(res => res.json())
.then(data => {
if (Array.isArray(data)) {
// Format historical logs to match the local logs format
const formattedLogs = data.map(item => {
const date = new Date(item.created_at);
const time = date.toTimeString().split(' ')[0];
return {
time,
text: translateAlert(item.message),
timestamp: date.getTime()
};
});
// Sort by timestamp ascending (reverse of DESC) so they append properly
setLogs(formattedLogs.reverse());
}
})
.catch(err => console.error("Error loading historical logs:", err));
}, [wsUrl]);
const translateObject = (name) => {
const dict = {
'person': 'شخص',
'ruler': 'مسطرة',
'lighter': 'ولاعة',
'cell phone': 'هاتف محمول',
'bottle': 'زجاجة',
'cup': 'كوب',
'pen': 'قلم',
'scissors': 'مقص',
'book': 'كتاب'
};
return dict[name.toLowerCase()] || name;
};
const translateAlert = (msg) => {
let res = msg;
res = res.replace(/Person (\d+) hit Person (\d+)! \(Sudden velocity: ([\d.]+)px\/fr\)/, 'قام الشخص $1 بضرب الشخص $2! (السرعة المفاجئة: $3 بكسل/إطار)');
res = res.replace(/Physical contact: Person (\d+) touching Person (\d+)/, 'تلامس جسدي: الشخص $1 يلمس الشخص $2');
return res;
};
const handleMetadataReceived = (data) => {
const timestamp = new Date().toTimeString().split(' ')[0];
// 1. Update general stats
if (data.people_count !== undefined) {
if (data.people_count !== peopleCount) {
addLog(timestamp, `تغيير في الحضور: يوجد حالياً ${data.people_count} أشخاص في الغرفة`);
setPeopleCount(data.people_count);
}
}
if (data.objects !== undefined) {
setObjects(data.objects);
// Log new objects detected
const newItems = data.objects.filter(x => !prevObjects.includes(x));
newItems.forEach(item => {
const hLabel = translateObject(item);
const isFlagged = ["lighter", "ruler"].includes(item.toLowerCase());
const typeStr = isFlagged ? "تحذير: أداة مشبوهة" : "أداة عادية";
addLog(timestamp, `[رصد أداة] تم كشف ${typeStr} '${hLabel}' في الغرفة`);
});
setPrevObjects(data.objects);
}
if (data.alerts !== undefined) {
setAlerts(data.alerts);
// Log critical security events
data.alerts.forEach(alert => {
const translatedMsg = translateAlert(alert.message);
const alreadyLogged = logs.some(l => l.text === translatedMsg && (Date.now() - l.timestamp) < 5000);
if (!alreadyLogged) {
const typePrefix = alert.type === "aggression" ? "[إنذار عنف]" : "[تلامس جسدي]";
addLog(timestamp, `${typePrefix} ${translatedMsg}`);
}
});
}
if (data.analyses !== undefined) {
setFaceAnalyses(data.analyses);
}
if (data.game !== undefined) {
setGameState(data.game);
}
};
const addLog = (time, text) => {
setLogs((prev) => {
const updated = [...prev, { time, text, timestamp: Date.now() }];
if (updated.length > 80) {
updated.shift();
}
return updated;
});
};
const saveSettings = () => {
localStorage.setItem('laring_ws_url', wsUrlInput);
setWsUrl(wsUrlInput);
setShowSettings(false);
addLog(new Date().toTimeString().split(' ')[0], `[نظام] تم حفظ رابط الخادم الجديد: ${wsUrlInput}`);
// Force page reload to reconnect with new websocket
setTimeout(() => {
window.location.reload();
}, 500);
};
const resetSettings = () => {
const serverIp = window.location.hostname || "localhost";
let defaultUrl = `ws://${serverIp}:8000/ws`;
const envWsUrl = import.meta.env.VITE_WS_URL;
if (envWsUrl) {
if (envWsUrl.startsWith('http://') || envWsUrl.startsWith('https://')) {
defaultUrl = envWsUrl.replace(/^http/, 'ws').replace(/\/$/, '') + '/ws';
} else {
defaultUrl = `wss://${envWsUrl}/ws`;
}
}
localStorage.removeItem('laring_ws_url');
setWsUrlInput(defaultUrl);
setWsUrl(defaultUrl);
setShowSettings(false);
addLog(new Date().toTimeString().split(' ')[0], `[نظام] تم استعادة عنوان الاتصال الافتراضي`);
setTimeout(() => {
window.location.reload();
}, 500);
};
const hasAggression = alerts && alerts.some(a => a.type === "aggression");
return (
{/* Header */}
{hasAggression ? : }
حارس Laring AI
نظام المراقبة العصبية والمسح البيومتري للوجه والجسد
{/* Navigation & Telemetry Clock */}
ساعة النظام: {timeStr}
{/* Settings slide panel */}
{showSettings && (
إعدادات خادم المعالجة الذكي
إذا قمت برفع المشروع على Netlify، ستحتاج لاستخدام نفق اتصال آمن (Localtunnel / Ngrok) لخادم بايثون المحتوي على بروتوكول مشفر (wss://).
setWsUrlInput(e.target.value)}
className="cyber-font"
style={{
flex: 1,
minWidth: '260px',
backgroundColor: 'rgba(0,0,0,0.6)',
border: '1px solid var(--neon-magenta)',
borderRadius: '6px',
padding: '8px 12px',
color: 'var(--neon-cyan)',
outline: 'none'
}}
placeholder="مثال: wss://your-tunnel.loca.lt/ws"
/>
)}
{/* Main Body */}
{activeTab === 'monitor' ? (
) : (
تعليمات الاستخدام
تأكد من تشغيل الكاميرا في تبويب 'المراقبة الحية' لتفعيل التحليل البيومتري للأشخاص ورصد الانتهاكات في الخلفية.
تقوم لعبة محاكاة المشاعر بمطابقة إحداثيات ملامح وجهك وعضلات العينين والفم مع النماذج العصبية للرصد.
)}
{/* Footer */}
);
}
export default App;