FaceRecognitionAPI / static /index.html
MinaNasser's picture
initialcommit
12d0de7
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>FaceRecognition - Live Detection</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 15px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 { font-size: 2.5em; margin-bottom: 10px; }
.content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
padding: 30px;
}
.video-section {
display: flex;
flex-direction: column;
gap: 15px;
}
.video-container {
position: relative;
width: 100%;
background: #000;
border-radius: 10px;
overflow: hidden;
aspect-ratio: 4/3;
}
video {
width: 100%;
height: 100%;
object-fit: cover;
}
.controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
button {
flex: 1;
min-width: 120px;
padding: 12px 20px;
border: none;
border-radius: 8px;
font-size: 1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
button:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0,0,0,0.2); }
button:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-detect { background: #4CAF50; color: white; }
.btn-start { background: #2196F3; color: white; }
.btn-stop { background: #f44336; color: white; }
.btn-embed { background: #FF9800; color: white; }
.output-section {
display: flex;
flex-direction: column;
gap: 15px;
}
.output-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.status-badge {
padding: 8px 15px;
border-radius: 20px;
font-weight: 600;
font-size: 0.9em;
}
.status-connected { background: #4CAF50; color: white; }
.status-disconnected { background: #999; color: white; }
.status-idle { background: #2196F3; color: white; }
#output {
flex: 1;
background: #f5f5f5;
border: 2px solid #ddd;
border-radius: 8px;
padding: 15px;
font-family: 'Courier New', monospace;
font-size: 0.9em;
overflow: auto;
max-height: 400px;
white-space: pre-wrap;
word-wrap: break-word;
}
.input-group {
display: flex;
gap: 10px;
}
input[type="text"] {
flex: 1;
padding: 10px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 1em;
}
.stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-top: 10px;
}
.stat-box {
background: #f0f0f0;
padding: 10px;
border-radius: 8px;
text-align: center;
}
.stat-label { font-size: 0.8em; color: #666; }
.stat-value { font-size: 1.5em; font-weight: bold; color: #333; }
@media (max-width: 768px) {
.content { grid-template-columns: 1fr; }
.header h1 { font-size: 1.8em; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>πŸ” FaceRecognition - Live Detection</h1>
<p>Real-time face detection and recognition via WebSocket</p>
</div>
<div class="content">
<div class="video-section">
<div class="video-container">
<video id="video" autoplay muted playsinline></video>
</div>
<div class="controls">
<button id="snap" class="btn-detect">πŸ“Έ Single Frame</button>
<button id="startStream" class="btn-start">▢️ Start Stream</button>
<button id="stopStream" class="btn-stop" disabled>⏹️ Stop Stream</button>
</div>
<div class="input-group">
<input type="text" id="userId" placeholder="Enter user ID (optional)" />
<button id="embedBtn" class="btn-embed">βž• Embed</button>
</div>
</div>
<div class="output-section">
<div class="output-header">
<h3>πŸ“Š Results</h3>
<span id="status" class="status-badge status-idle">βšͺ Idle</span>
</div>
<div id="output">Waiting for input...</div>
<div class="stats">
<div class="stat-box">
<div class="stat-label">Frames Sent</div>
<div class="stat-value" id="frameCount">0</div>
</div>
<div class="stat-box">
<div class="stat-label">Faces Detected</div>
<div class="stat-value" id="faceCount">0</div>
</div>
<div class="stat-box">
<div class="stat-label">Connection</div>
<div class="stat-value" id="connStatus">β€”</div>
</div>
<div class="stat-box">
<div class="stat-label">FPS</div>
<div class="stat-value" id="fpsCount">0</div>
</div>
</div>
</div>
</div>
</div>
<script>
const pathParts = location.pathname.split('/').filter(p => p.length > 0);
let variant = 'v1';
if (pathParts.length >= 2 && pathParts[0].toLowerCase().includes('autoproctor')) {
variant = pathParts[1] || variant;
}
const video = document.getElementById('video');
const snapBtn = document.getElementById('snap');
const startBtn = document.getElementById('startStream');
const stopBtn = document.getElementById('stopStream');
const embedBtn = document.getElementById('embedBtn');
const userIdInput = document.getElementById('userId');
const output = document.getElementById('output');
const statusBadge = document.getElementById('status');
const frameCountEl = document.getElementById('frameCount');
const faceCountEl = document.getElementById('faceCount');
const connStatusEl = document.getElementById('connStatus');
const fpsCountEl = document.getElementById('fpsCount');
let ws = null;
let streamRunning = false;
let frameCount = 0;
let totalFaceCount = 0;
let lastFrameTime = 0;
function updateStatus(text, cssClass) {
statusBadge.textContent = text;
statusBadge.className = `status-badge ${cssClass}`;
}
function logOutput(msg) {
output.textContent = typeof msg === 'string' ? msg : JSON.stringify(msg, null, 2);
output.scrollTop = output.scrollHeight;
}
navigator.mediaDevices.getUserMedia({ video: true, audio: false })
.then(s => { video.srcObject = s; updateStatus('βœ“ Camera Ready', 'status-idle'); })
.catch(e => { logOutput('❌ Camera error: ' + e); updateStatus('βœ— Camera Failed', 'status-disconnected'); });
function canvasToBase64() {
const c = document.createElement('canvas');
c.width = video.videoWidth || 640;
c.height = video.videoHeight || 480;
const ctx = c.getContext('2d');
ctx.drawImage(video, 0, 0, c.width, c.height);
return c.toDataURL('image/jpeg', 0.85).split(',')[1]; // Return only base64 part
}
snapBtn.onclick = async () => {
try {
updateStatus('Processing...', 'status-idle');
const base64 = canvasToBase64();
const blob = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
const fd = new FormData();
fd.append('file', new Blob([blob], { type: 'image/jpeg' }), 'frame.jpg');
const res = await fetch(`/AutoProctor/${variant}/data/detect/frame`, { method: 'POST', body: fd });
const j = await res.json();
logOutput(j);
const facesDetected = j.results ? j.results.ids?.length || 0 : 0;
totalFaceCount += facesDetected;
faceCountEl.textContent = totalFaceCount;
updateStatus('βœ“ Result Ready', 'status-connected');
} catch (e) {
logOutput('❌ Error: ' + e.message);
updateStatus('βœ— Error', 'status-disconnected');
}
};
startBtn.onclick = async () => {
if (ws) { ws.close(); ws = null; }
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
const url = `${protocol}://${location.host}/AutoProctor/${variant}/data/detect/stream`;
console.log('Connecting to:', url);
ws = new WebSocket(url);
ws.onopen = () => {
logOutput('🟒 WebSocket Connected');
updateStatus('🟒 Connected', 'status-connected');
connStatusEl.textContent = 'Online';
startBtn.disabled = true;
stopBtn.disabled = false;
frameCount = 0;
lastFrameTime = Date.now();
streamRunning = true;
sendFrames();
};
ws.onmessage = (ev) => {
try {
const d = JSON.parse(ev.data);
logOutput(d);
if (d.faces_detected) {
totalFaceCount += d.faces_detected;
faceCountEl.textContent = totalFaceCount;
}
// Update FPS
const now = Date.now();
if (lastFrameTime > 0) {
const fps = Math.round(1000 / (now - lastFrameTime));
fpsCountEl.textContent = fps;
}
lastFrameTime = now;
} catch (e) {
logOutput('⚠️ ' + ev.data);
}
};
ws.onerror = (err) => {
logOutput('❌ WebSocket error: ' + err);
updateStatus('βœ— Error', 'status-disconnected');
};
ws.onclose = () => {
logOutput('πŸ”΄ WebSocket Closed');
updateStatus('βšͺ Disconnected', 'status-disconnected');
connStatusEl.textContent = 'Offline';
startBtn.disabled = false;
stopBtn.disabled = true;
streamRunning = false;
};
};
async function sendFrames() {
while (streamRunning && ws && ws.readyState === 1) {
const base64 = canvasToBase64();
frameCount++;
frameCountEl.textContent = frameCount;
try {
ws.send(base64);
} catch (e) {
console.error('Send error:', e);
break;
}
await new Promise(r => setTimeout(r, 2000)); // F P 2S
}
}
stopBtn.onclick = () => {
streamRunning = false;
if (ws) { ws.close(); }
};
embedBtn.onclick = async () => {
const userId = userIdInput.value.trim();
if (!userId) {
logOutput('❌ Please enter a user ID');
return;
}
try {
updateStatus('Embedding...', 'status-idle');
const base64 = canvasToBase64();
const blob = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
const fd = new FormData();
fd.append('file', new Blob([blob], { type: 'image/jpeg' }), 'frame.jpg');
const res = await fetch(`/AutoProctor/${variant}/data/embed/${userId}`, { method: 'POST', body: fd });
const j = await res.json();
logOutput(j);
updateStatus('βœ“ Embedded', 'status-connected');
} catch (e) {
logOutput('❌ Embedding error: ' + e.message);
updateStatus('βœ— Error', 'status-disconnected');
}
};
</script>
</body>
</html>