Download frontend/index.html from r-vasanthkumar73-dev/Sentinel-Multimodal-Emotion-AI: direct link, hf CLI and curl.
- Browser
- Download file 23.7 kB
-
https://huggingface.co/spaces/r-vasanthkumar73-dev/Sentinel-Multimodal-Emotion-AI/resolve/main/frontend/index.html
- Command line
-
hf download hf://spaces/r-vasanthkumar73-dev/Sentinel-Multimodal-Emotion-AI/frontend/index.html
-
curl -L -o index.html https://huggingface.co/spaces/r-vasanthkumar73-dev/Sentinel-Multimodal-Emotion-AI/resolve/main/frontend/index.html
23.7 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>The Sentinel Interface β AI Engagement Monitor</title> | |
| <meta name="description" | |
| content="Multisource Emotion Detection and Engagement Optimization for E-Learning. Real-time face, speech, and text analysis."> | |
| <link rel="stylesheet" href="/css/styles.css"> | |
| <link | |
| href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700;800&family=Inter:wght@300;400;500;600;700&display=swap" | |
| rel="stylesheet"> | |
| <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" | |
| rel="stylesheet"> | |
| <style> | |
| .hero-section { | |
| position: relative; | |
| padding: 80px 48px 48px; | |
| text-align: center; | |
| overflow: hidden; | |
| } | |
| .hero-title { | |
| font-family: var(--font-headline); | |
| font-size: 3.5rem; | |
| font-weight: 900; | |
| line-height: 1.1; | |
| margin-bottom: 16px; | |
| letter-spacing: -0.03em; | |
| } | |
| .hero-sub { | |
| font-size: 1.1rem; | |
| color: var(--on-surface-variant); | |
| max-width: 600px; | |
| margin: 0 auto 48px; | |
| line-height: 1.7; | |
| } | |
| .hero-glow { | |
| position: absolute; | |
| width: 600px; | |
| height: 600px; | |
| border-radius: 50%; | |
| filter: blur(120px); | |
| opacity: 0.15; | |
| pointer-events: none; | |
| z-index: 0; | |
| } | |
| .hero-glow.cyan { | |
| background: var(--primary-container); | |
| top: -200px; | |
| left: 20%; | |
| } | |
| .hero-glow.purple { | |
| background: var(--secondary-container); | |
| top: -100px; | |
| right: 10%; | |
| } | |
| .hero-glow.gold { | |
| background: var(--tertiary-fixed-dim); | |
| bottom: -300px; | |
| left: 40%; | |
| opacity: 0.08; | |
| } | |
| .cards-section { | |
| padding: 0 48px 48px; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| .stats-section { | |
| padding: 0 48px 48px; | |
| } | |
| .hero-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 6px 16px; | |
| border-radius: 999px; | |
| background: rgba(0, 240, 255, 0.06); | |
| border: 1px solid rgba(0, 240, 255, 0.15); | |
| font-size: 11px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| font-weight: 600; | |
| color: var(--primary-container); | |
| margin-bottom: 24px; | |
| } | |
| .orbit-ring { | |
| position: absolute; | |
| border-radius: 50%; | |
| border: 1px solid rgba(0, 240, 255, 0.05); | |
| animation: rotate-slow 60s linear infinite; | |
| pointer-events: none; | |
| } | |
| .orbit-ring:nth-child(1) { | |
| width: 400px; | |
| height: 400px; | |
| top: -100px; | |
| left: calc(50% - 200px); | |
| } | |
| .orbit-ring:nth-child(2) { | |
| width: 600px; | |
| height: 600px; | |
| top: -200px; | |
| left: calc(50% - 300px); | |
| animation-duration: 90s; | |
| animation-direction: reverse; | |
| } | |
| .orbit-ring:nth-child(3) { | |
| width: 800px; | |
| height: 800px; | |
| top: -300px; | |
| left: calc(50% - 400px); | |
| animation-duration: 120s; | |
| } | |
| .orbit-dot { | |
| position: absolute; | |
| width: 4px; | |
| height: 4px; | |
| background: var(--primary-container); | |
| border-radius: 50%; | |
| box-shadow: 0 0 8px var(--primary-container); | |
| } | |
| .system-status { | |
| display: flex; | |
| align-items: center; | |
| gap: 24px; | |
| padding: 20px 28px; | |
| border-radius: 16px; | |
| background: rgba(26, 31, 47, 0.5); | |
| border: 1px solid rgba(59, 73, 75, 0.1); | |
| } | |
| .system-status-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: 12px; | |
| color: var(--on-surface-variant); | |
| } | |
| .system-status-dot { | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| } | |
| .system-status-dot.online { | |
| background: #34c759; | |
| box-shadow: 0 0 6px #34c759; | |
| } | |
| .system-status-dot.loading { | |
| background: var(--tertiary-fixed-dim); | |
| animation: pulse-glow 1.5s infinite; | |
| } | |
| .feature-grid-container { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 24px; | |
| margin-bottom: 32px; | |
| } | |
| .bottom-grid { | |
| display: grid; | |
| grid-template-columns: 2fr 1fr; | |
| gap: 24px; | |
| } | |
| @media (max-width: 1200px) { | |
| .feature-grid-container { | |
| grid-template-columns: 1fr; | |
| } | |
| .bottom-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <canvas id="particles-canvas"></canvas> | |
| <!-- Sidebar --> | |
| <aside class="sidebar"> | |
| <div class="sidebar-logo">S</div> | |
| <nav class="sidebar-nav"> | |
| <a href="/" class="sidebar-link active" data-tooltip="Dashboard"> | |
| <span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1;">dashboard</span> | |
| <span class="label">Home</span> | |
| </a> | |
| <a href="./live.html" class="sidebar-link" data-tooltip="Live Analysis"> | |
| <span class="material-symbols-outlined">sensors</span> | |
| <span class="label">Live</span> | |
| </a> | |
| <a href="./scan.html" class="sidebar-link" data-tooltip="Deep Scan"> | |
| <span class="material-symbols-outlined">analytics</span> | |
| <span class="label">Scan</span> | |
| </a> | |
| <a href="./stats.html" class="sidebar-link" data-tooltip="Performance"> | |
| <span class="material-symbols-outlined">school</span> | |
| <span class="label">Stats</span> | |
| </a> | |
| </nav> | |
| </aside> | |
| <!-- Top Bar --> | |
| <header class="topbar"> | |
| <div style="display:flex;align-items:center;gap:16px;"> | |
| <h1 class="topbar-title gradient-text">The Sentinel Interface</h1> | |
| <div class="status-pill"> | |
| <span class="status-dot"></span> | |
| <span class="status-text">System Online</span> | |
| </div> | |
| </div> | |
| <div style="display:flex;align-items:center;gap:16px;"> | |
| <button class="btn-primary" onclick="window.location.href='/live'" id="start-monitoring-btn"> | |
| <span class="material-symbols-outlined" style="font-size:18px;">sensors</span> | |
| Start Monitoring | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="main-content"> | |
| <!-- Hero --> | |
| <section class="hero-section"> | |
| <div class="hero-glow cyan"></div> | |
| <div class="hero-glow purple"></div> | |
| <div class="hero-glow gold"></div> | |
| <div class="orbit-ring"> | |
| <div class="orbit-dot" style="top:0;left:50%;"></div> | |
| </div> | |
| <div class="orbit-ring"> | |
| <div class="orbit-dot" style="top:50%;right:0;"></div> | |
| </div> | |
| <div class="orbit-ring"> | |
| <div class="orbit-dot" style="bottom:0;left:30%;"></div> | |
| </div> | |
| <div style="position:relative;z-index:1;"> | |
| <div class="hero-badge"> | |
| <span class="material-symbols-outlined" style="font-size:14px;">neurology</span> | |
| Multisource AI Emotion Engine | |
| </div> | |
| <h2 class="hero-title animate-fade-in"> | |
| <span class="gradient-text">Multisource Emotion</span><br> | |
| <span style="color:var(--on-surface);"> Recognition System</span> | |
| </h2> | |
| <p class="hero-sub animate-fade-in" style="animation-delay:0.2s;"> | |
| Real-time multimodal emotion detection using face recognition, speech prosody analysis, and text | |
| sentiment. | |
| </p> | |
| <div | |
| style="display:flex;gap:16px;justify-content:center;animation:fade-in 0.6s ease-out 0.4s forwards;opacity:0;"> | |
| <button class="btn-primary" onclick="window.location.href='/live'"> | |
| <span class="material-symbols-outlined" style="font-size:18px;">play_arrow</span> | |
| Launch Live Pulse | |
| </button> | |
| <button class="btn-secondary" onclick="window.location.href='/scan'"> | |
| <span class="material-symbols-outlined" style="font-size:18px;">folder_open</span> | |
| Deep Archive Scan | |
| </button> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Feature Cards --> | |
| <section class="cards-section stagger-children"> | |
| <div class="feature-grid-container"> | |
| <!-- Card 1: Sentinel Live Pulse --> | |
| <div class="feature-card" onclick="window.location.href='/live'"> | |
| <div class="feature-card-inner"> | |
| <div class="feature-icon primary"> | |
| <span class="material-symbols-outlined" | |
| style="font-variation-settings:'FILL' 1;">sensors</span> | |
| </div> | |
| <h3 class="feature-title" style="color:var(--primary);">Sentinel Live Pulse</h3> | |
| <p class="feature-desc"> | |
| Real-time multimodal analysis engine. Live webcam with 468-point face mesh visualization, | |
| audio frequency waveform recording, and instant text sentiment β all fused into a unified | |
| engagement score. | |
| </p> | |
| <div class="feature-footer"> | |
| <span class="feature-tag">Real-time</span> | |
| <span class="material-symbols-outlined" | |
| style="color:var(--primary-container);font-size:20px;">arrow_forward</span> | |
| </div> | |
| <!-- Decorative scan line --> | |
| <div | |
| style="position:absolute;left:0;width:100%;height:2px;background:linear-gradient(90deg,transparent,var(--primary-container),transparent);animation:scan-line 3s linear infinite;opacity:0.3;"> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Card 2: Deep Archive Scan --> | |
| <div class="feature-card secondary" onclick="window.location.href='/scan'"> | |
| <div class="feature-card-inner"> | |
| <div class="feature-icon secondary"> | |
| <span class="material-symbols-outlined" | |
| style="font-variation-settings:'FILL' 1;">query_stats</span> | |
| </div> | |
| <h3 class="feature-title" style="color:var(--secondary);">Deep Archive Scan</h3> | |
| <p class="feature-desc"> | |
| Upload and analyze existing recordings. Process face images, audio clips, and text documents | |
| through our AI pipeline for offline emotion detection and engagement scoring. | |
| </p> | |
| <div class="feature-footer"> | |
| <span class="feature-tag secondary">Upload & Analyze</span> | |
| <span class="material-symbols-outlined" | |
| style="color:var(--secondary);font-size:20px;">arrow_forward</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Card 3: Instructor Nexus --> | |
| <div class="feature-card tertiary" onclick="window.location.href='/performance'"> | |
| <div class="feature-card-inner"> | |
| <div class="feature-icon tertiary"> | |
| <span class="material-symbols-outlined" | |
| style="font-variation-settings:'FILL' 1;">school</span> | |
| </div> | |
| <h3 class="feature-title" style="color:var(--tertiary-fixed-dim);">Instructor Nexus</h3> | |
| <p class="feature-desc"> | |
| Teacher control center for student performance. View current session metrics, historical | |
| engagement trends, | |
| emotion distribution charts, and per-student analytics. | |
| </p> | |
| <div class="feature-footer"> | |
| <span class="feature-tag tertiary">Performance Hub</span> | |
| <span class="material-symbols-outlined" | |
| style="color:var(--tertiary-fixed-dim);font-size:20px;">arrow_forward</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Bottom Row --> | |
| <div class="bottom-grid"> | |
| <!-- System Status Card --> | |
| <div class="glass-card" style="padding:28px;"> | |
| <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;"> | |
| <div> | |
| <div | |
| style="font-size:10px;text-transform:uppercase;letter-spacing:0.1em;color:var(--outline);font-weight:600;"> | |
| System Status</div> | |
| <div style="font-family:var(--font-headline);font-size:1.25rem;font-weight:700;">AI Models & | |
| Services</div> | |
| </div> | |
| <span class="material-symbols-outlined" style="color:var(--primary-container);">memory</span> | |
| </div> | |
| <div style="display:flex;flex-direction:column;gap:12px;" id="system-status-container"> | |
| <div class="system-status"> | |
| <div class="system-status-dot loading"></div> | |
| <span style="flex:1;">Checking system status...</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Quick Stats --> | |
| <div class="glass-card" style="padding:28px;"> | |
| <div | |
| style="font-size:10px;text-transform:uppercase;letter-spacing:0.1em;color:var(--outline);font-weight:600;margin-bottom:12px;"> | |
| Quick Stats</div> | |
| <div style="font-family:var(--font-headline);font-size:1.25rem;font-weight:700;margin-bottom:20px;"> | |
| Today's Overview</div> | |
| <div style="display:flex;flex-direction:column;gap:12px;" id="quick-stats"> | |
| <div class="stat-card"> | |
| <div class="stat-label">Total Sessions</div> | |
| <div class="stat-value" style="color:var(--primary-container);" id="stat-sessions">0</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-label">Avg Engagement</div> | |
| <div class="stat-value" style="color:var(--secondary);" id="stat-engagement">β</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| <script> | |
| // ββ Particle Background ββββββββββββββββββββββββββββββ | |
| (function () { | |
| const canvas = document.getElementById('particles-canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| let particles = []; | |
| const PARTICLE_COUNT = 60; | |
| function resize() { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| } | |
| window.addEventListener('resize', resize); | |
| resize(); | |
| class Particle { | |
| constructor() { this.reset(); } | |
| reset() { | |
| this.x = Math.random() * canvas.width; | |
| this.y = Math.random() * canvas.height; | |
| this.vx = (Math.random() - 0.5) * 0.3; | |
| this.vy = (Math.random() - 0.5) * 0.3; | |
| this.radius = Math.random() * 1.5 + 0.5; | |
| this.opacity = Math.random() * 0.3 + 0.1; | |
| } | |
| update() { | |
| this.x += this.vx; | |
| this.y += this.vy; | |
| if (this.x < 0 || this.x > canvas.width) this.vx *= -1; | |
| if (this.y < 0 || this.y > canvas.height) this.vy *= -1; | |
| } | |
| draw() { | |
| const themeColor = document.documentElement.getAttribute('data-theme') === 'light' ? '2, 132, 199' : '0, 240, 255'; | |
| ctx.beginPath(); | |
| ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); | |
| ctx.fillStyle = `rgba(${themeColor}, ${this.opacity})`; | |
| ctx.fill(); | |
| } | |
| } | |
| for (let i = 0; i < PARTICLE_COUNT; i++) particles.push(new Particle()); | |
| function animate() { | |
| const themeColor = document.documentElement.getAttribute('data-theme') === 'light' ? '2, 132, 199' : '0, 240, 255'; | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| particles.forEach(p => { p.update(); p.draw(); }); | |
| // Draw connections | |
| for (let i = 0; i < particles.length; i++) { | |
| for (let j = i + 1; j < particles.length; j++) { | |
| const dx = particles[i].x - particles[j].x; | |
| const dy = particles[i].y - particles[j].y; | |
| const dist = Math.sqrt(dx * dx + dy * dy); | |
| if (dist < 120) { | |
| ctx.beginPath(); | |
| ctx.moveTo(particles[i].x, particles[i].y); | |
| ctx.lineTo(particles[j].x, particles[j].y); | |
| ctx.strokeStyle = `rgba(${themeColor}, ${0.03 * (1 - dist / 120)})`; | |
| ctx.lineWidth = 0.5; | |
| ctx.stroke(); | |
| } | |
| } | |
| } | |
| requestAnimationFrame(animate); | |
| } | |
| animate(); | |
| })(); | |
| // ββ 3D Tilt Effect on Feature Cards ββββββββββββββββββ | |
| document.querySelectorAll('.feature-card').forEach(card => { | |
| const inner = card.querySelector('.feature-card-inner'); | |
| card.addEventListener('mousemove', (e) => { | |
| const rect = card.getBoundingClientRect(); | |
| const x = (e.clientX - rect.left) / rect.width; | |
| const y = (e.clientY - rect.top) / rect.height; | |
| const rotateX = (0.5 - y) * 8; | |
| const rotateY = (x - 0.5) * 8; | |
| inner.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) translateY(-4px)`; | |
| inner.style.setProperty('--mouse-x', (x * 100) + '%'); | |
| inner.style.setProperty('--mouse-y', (y * 100) + '%'); | |
| }); | |
| card.addEventListener('mouseleave', () => { | |
| inner.style.transform = ''; | |
| }); | |
| }); | |
| // ββ System Health Check ββββββββββββββββββββββββββββββ | |
| async function checkSystemStatus() { | |
| const container = document.getElementById('system-status-container'); | |
| try { | |
| const res = await fetch('/api/health'); | |
| const data = await res.json(); | |
| container.innerHTML = ` | |
| <div class="system-status"> | |
| <div class="system-status-dot online"></div> | |
| <span style="flex:1;font-weight:600;">API Server</span> | |
| <span style="font-size:11px;color:var(--primary-container);">Online</span> | |
| </div> | |
| <div class="system-status"> | |
| <div class="system-status-dot ${data.models_loaded?.face ? 'online' : 'loading'}"></div> | |
| <span style="flex:1;">Face CNN Model</span> | |
| <span style="font-size:11px;color:${data.models_loaded?.face ? 'var(--primary-container)' : 'var(--tertiary-fixed-dim)'};">${data.models_loaded?.face ? 'Loaded' : 'Standby'}</span> | |
| </div> | |
| <div class="system-status"> | |
| <div class="system-status-dot ${data.models_loaded?.text ? 'online' : 'loading'}"></div> | |
| <span style="flex:1;">Text Sentiment (DistilBERT)</span> | |
| <span style="font-size:11px;color:${data.models_loaded?.text ? 'var(--primary-container)' : 'var(--tertiary-fixed-dim)'};">${data.models_loaded?.text ? 'Loaded' : 'Standby'}</span> | |
| </div> | |
| <div class="system-status"> | |
| <div class="system-status-dot ${data.models_loaded?.speech ? 'online' : 'loading'}"></div> | |
| <span style="flex:1;">Speech Analyzer (librosa)</span> | |
| <span style="font-size:11px;color:${data.models_loaded?.speech ? 'var(--primary-container)' : 'var(--tertiary-fixed-dim)'};">${data.models_loaded?.speech ? 'Loaded' : 'Standby'}</span> | |
| </div> | |
| `; | |
| } catch (e) { | |
| container.innerHTML = ` | |
| <div class="system-status"> | |
| <div class="system-status-dot" style="background:#ff3b30;"></div> | |
| <span style="flex:1;color:var(--error);">Backend Offline</span> | |
| <span style="font-size:11px;color:var(--error);">Start server first</span> | |
| </div> | |
| `; | |
| } | |
| } | |
| // ββ Load Quick Stats βββββββββββββββββββββββββββββββββ | |
| async function loadStats() { | |
| try { | |
| const res = await fetch('/api/performance/default'); | |
| const data = await res.json(); | |
| document.getElementById('stat-sessions').textContent = data.overall_stats?.total_sessions || 0; | |
| const avg = data.overall_stats?.avg_engagement; | |
| document.getElementById('stat-engagement').textContent = avg ? avg.toFixed(1) + '%' : 'β'; | |
| } catch (e) { | |
| // Backend not running | |
| } | |
| } | |
| checkSystemStatus(); | |
| loadStats(); | |
| setInterval(checkSystemStatus, 15000); | |
| </script> | |
| </body> | |
| </html> |