r-vasanthkumar73-dev's picture
Deploying backend and frontend folder modules.
099d157 verified
Raw
History Blame Contribute Delete
22.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Deep Archive Scan — Upload & Analyze</title>
<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>
.scan-content { padding: 32px; }
.scan-header { margin-bottom: 32px; }
.scan-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin-bottom: 32px; }
.results-area { margin-top: 32px; }
.result-card { padding: 24px; margin-bottom: 20px; }
.result-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.upload-box { padding: 40px; text-align: center; border: 2px dashed rgba(59,73,75,0.3); border-radius: 16px; transition: all 0.3s; cursor: pointer; }
.upload-box:hover, .upload-box.drag-over { border-color: var(--primary-container); background: rgba(0,240,255,0.04); }
.upload-box .material-symbols-outlined { font-size: 48px; margin-bottom: 12px; }
.upload-box input[type="file"] { display: none; }
.prob-bar-container { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.prob-label { width: 80px; font-size: 12px; font-weight: 600; color: var(--on-surface-variant); }
.prob-bar { flex: 1; height: 8px; background: var(--surface-container-highest); border-radius: 999px; overflow: hidden; }
.prob-fill { height: 100%; border-radius: 999px; transition: width 0.5s; }
.prob-value { width: 50px; text-align: right; font-size: 12px; font-weight: 700; font-family: var(--font-headline); }
.analysis-spinner { display: none; text-align: center; padding: 40px; }
.spinner { width: 40px; height: 40px; border: 3px solid var(--surface-container-highest); border-top-color: var(--primary-container); border-radius: 50%; animation: rotate-slow 1s linear infinite; margin: 0 auto 16px; }
@media (max-width: 1024px) { .scan-grid { grid-template-columns: 1fr; } }
</style>
</head>
<body>
<aside class="sidebar">
<div class="sidebar-logo">S</div>
<nav class="sidebar-nav">
<a href="/" class="sidebar-link"><span class="material-symbols-outlined">dashboard</span><span class="label">Home</span></a>
<a href="/live" class="sidebar-link"><span class="material-symbols-outlined">sensors</span><span class="label">Live</span></a>
<a href="/scan" class="sidebar-link active"><span class="material-symbols-outlined" style="font-variation-settings:'FILL' 1;">analytics</span><span class="label">Scan</span></a>
<a href="/stats" class="sidebar-link"><span class="material-symbols-outlined">school</span><span class="label">Stats</span></a>
</nav>
</aside>
<header class="topbar">
<div style="display:flex;align-items:center;gap:16px;">
<h1 class="topbar-title gradient-text-secondary">Deep Archive Scan</h1>
<div class="status-pill"><span class="status-dot" style="background:var(--secondary);"></span><span class="status-text" style="color:var(--secondary);">Upload Mode</span></div>
</div>
<button class="btn-secondary" onclick="clearResults()"><span class="material-symbols-outlined" style="font-size:18px;">restart_alt</span> Clear Results</button>
</header>
<main class="main-content">
<div class="scan-content">
<div class="scan-header animate-fade-in">
<h2 style="font-family:var(--font-headline);font-size:1.75rem;font-weight:700;margin-bottom:8px;">Analyze Existing Data</h2>
<p style="color:var(--on-surface-variant);font-size:0.9rem;">Upload face images, audio recordings, or paste text for emotion analysis and engagement scoring.</p>
</div>
<!-- Upload Cards -->
<div class="scan-grid stagger-children">
<!-- Face Upload -->
<div class="glass-card" style="padding:8px;">
<div class="upload-box" id="face-upload-zone" onclick="document.getElementById('face-file').click()">
<input type="file" id="face-file" accept="image/*" onchange="handleFaceUpload(this)">
<span class="material-symbols-outlined" style="color:var(--primary-container);">face</span>
<div style="font-family:var(--font-headline);font-weight:700;margin-bottom:4px;">Face Image</div>
<div style="font-size:12px;color:var(--outline);">Drop or click to upload (.jpg, .png)</div>
</div>
<div id="face-preview" style="display:none;padding:16px;">
<img id="face-preview-img" style="width:100%;border-radius:12px;margin-bottom:12px;">
</div>
</div>
<!-- Audio Upload -->
<div class="glass-card" style="padding:8px;">
<div class="upload-box" id="audio-upload-zone" onclick="document.getElementById('audio-file').click()">
<input type="file" id="audio-file" accept="audio/*" onchange="handleAudioUpload(this)">
<span class="material-symbols-outlined" style="color:var(--secondary);">graphic_eq</span>
<div style="font-family:var(--font-headline);font-weight:700;margin-bottom:4px;">Audio File</div>
<div style="font-size:12px;color:var(--outline);">Drop or click to upload (.wav, .mp3, .webm)</div>
</div>
<div id="audio-preview" style="display:none;padding:16px;">
<audio id="audio-preview-player" controls style="width:100%;"></audio>
</div>
</div>
<!-- Text Input -->
<div class="glass-card" style="padding:8px;">
<div style="padding:24px;">
<span class="material-symbols-outlined" style="color:var(--tertiary-fixed-dim);font-size:48px;display:block;text-align:center;margin-bottom:12px;">segment</span>
<div style="font-family:var(--font-headline);font-weight:700;text-align:center;margin-bottom:12px;">Text Analysis</div>
<textarea class="sentinel-input" id="text-input" placeholder="Paste or type text for sentiment analysis..." style="min-height:80px;font-size:13px;"></textarea>
<button class="btn-primary" style="width:100%;margin-top:12px;justify-content:center;" onclick="handleTextAnalysis()">
<span class="material-symbols-outlined" style="font-size:18px;">psychology</span> Analyze Sentiment
</button>
</div>
</div>
</div>
<!-- Loading -->
<div class="analysis-spinner" id="analysis-spinner">
<div class="spinner"></div>
<div style="font-family:var(--font-headline);font-weight:600;">Analyzing...</div>
<div style="font-size:12px;color:var(--outline);margin-top:4px;">AI models are processing your data</div>
</div>
<!-- Results Area -->
<div class="results-area" id="results-area"></div>
</div>
</main>
<script>
// ── Drag & Drop ───────────────────────────────────
function setupDropZone(zoneId, fileInputId) {
const zone = document.getElementById(zoneId);
zone.addEventListener('dragover', (e) => { e.preventDefault(); zone.classList.add('drag-over'); });
zone.addEventListener('dragleave', () => zone.classList.remove('drag-over'));
zone.addEventListener('drop', (e) => {
e.preventDefault(); zone.classList.remove('drag-over');
const input = document.getElementById(fileInputId);
input.files = e.dataTransfer.files;
input.dispatchEvent(new Event('change'));
});
}
setupDropZone('face-upload-zone', 'face-file');
setupDropZone('audio-upload-zone', 'audio-file');
// ── Face Upload Handler ───────────────────────────
async function handleFaceUpload(input) {
if (!input.files || !input.files[0]) return;
const file = input.files[0];
// Show preview
const reader = new FileReader();
reader.onload = (e) => {
document.getElementById('face-preview-img').src = e.target.result;
document.getElementById('face-preview').style.display = 'block';
};
reader.readAsDataURL(file);
// Analyze
showSpinner();
try {
const formData = new FormData();
formData.append('file', file);
const res = await fetch('/api/analyze/face', { method: 'POST', body: formData });
const result = await res.json();
hideSpinner();
showFaceResult(result);
} catch (e) {
hideSpinner();
showError('Face analysis failed: ' + e.message);
}
}
// ── Audio Upload Handler ──────────────────────────
async function handleAudioUpload(input) {
if (!input.files || !input.files[0]) return;
const file = input.files[0];
// Show preview
document.getElementById('audio-preview-player').src = URL.createObjectURL(file);
document.getElementById('audio-preview').style.display = 'block';
showSpinner();
try {
const formData = new FormData();
formData.append('file', file);
const res = await fetch('/api/analyze/speech', { method: 'POST', body: formData });
const result = await res.json();
hideSpinner();
showSpeechResult(result);
} catch (e) {
hideSpinner();
showError('Speech analysis failed: ' + e.message);
}
}
// ── Text Analysis Handler ─────────────────────────
async function handleTextAnalysis() {
const text = document.getElementById('text-input').value.trim();
if (!text) return;
showSpinner();
try {
const res = await fetch('/api/analyze/text', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text }),
});
const result = await res.json();
hideSpinner();
showTextResult(result);
} catch (e) {
hideSpinner();
showError('Text analysis failed: ' + e.message);
}
}
// ── Display Results ───────────────────────────────
function showFaceResult(result) {
const area = document.getElementById('results-area');
const probs = result.probabilities || {};
let barsHtml = '';
Object.entries(probs).sort((a,b) => b[1]-a[1]).forEach(([label, val]) => {
const color = label === result.emotion ? 'var(--primary-container)' : 'rgba(0,240,255,0.3)';
barsHtml += `<div class="prob-bar-container"><span class="prob-label">${label}</span><div class="prob-bar"><div class="prob-fill" style="width:${val}%;background:${color};"></div></div><span class="prob-value" style="color:${color};">${val}%</span></div>`;
});
const html = `
<div class="glass-card result-card animate-fade-in">
<div class="result-header">
<div style="display:flex;align-items:center;gap:12px;">
<div class="feature-icon primary" style="width:44px;height:44px;"><span class="material-symbols-outlined">face</span></div>
<div>
<div style="font-size:10px;text-transform:uppercase;letter-spacing:0.08em;color:var(--outline);font-weight:600;">Face Emotion Analysis</div>
<div style="font-family:var(--font-headline);font-size:1.25rem;font-weight:700;">${result.emotion} — ${result.confidence}%</div>
</div>
</div>
<div class="emotion-chip ${result.emotion.toLowerCase()}">${result.emotion}</div>
</div>
<div style="margin-bottom:16px;">${barsHtml}</div>
<div style="display:flex;gap:16px;">
<div class="stat-card" style="flex:1;padding:12px;"><div class="stat-label">Engagement</div><div style="font-family:var(--font-headline);font-size:1.25rem;font-weight:700;color:var(--primary-container);">${result.engagement_score}%</div></div>
<div class="stat-card" style="flex:1;padding:12px;"><div class="stat-label">Face Detected</div><div style="font-family:var(--font-headline);font-size:1.25rem;font-weight:700;color:${result.face_detected ? 'var(--primary-container)' : 'var(--error)'};">${result.face_detected ? 'Yes' : 'No'}</div></div>
</div>
</div>`;
area.insertAdjacentHTML('afterbegin', html);
}
function showSpeechResult(result) {
const area = document.getElementById('results-area');
const probs = result.probabilities || {};
let barsHtml = '';
Object.entries(probs).sort((a,b) => b[1]-a[1]).forEach(([label, val]) => {
const color = label === result.emotion ? 'var(--secondary)' : 'rgba(209,188,255,0.3)';
barsHtml += `<div class="prob-bar-container"><span class="prob-label">${label}</span><div class="prob-bar"><div class="prob-fill" style="width:${val}%;background:${color};"></div></div><span class="prob-value" style="color:${color};">${val}%</span></div>`;
});
// Frequency bars
const freqBars = result.visualization?.frequency_bars || [];
let freqHtml = '<div style="display:flex;align-items:flex-end;gap:2px;height:60px;margin-bottom:16px;">';
freqBars.forEach(v => {
freqHtml += `<div style="flex:1;border-radius:3px 3px 0 0;background:rgba(209,188,255,${0.3+v*0.7});height:${Math.max(2,v*100)}%;"></div>`;
});
freqHtml += '</div>';
const html = `
<div class="glass-card result-card animate-fade-in">
<div class="result-header">
<div style="display:flex;align-items:center;gap:12px;">
<div class="feature-icon secondary" style="width:44px;height:44px;"><span class="material-symbols-outlined">graphic_eq</span></div>
<div>
<div style="font-size:10px;text-transform:uppercase;letter-spacing:0.08em;color:var(--outline);font-weight:600;">Speech Emotion Analysis</div>
<div style="font-family:var(--font-headline);font-size:1.25rem;font-weight:700;">${result.emotion} — ${result.confidence}%</div>
</div>
</div>
<div class="emotion-chip ${(result.emotion||'neutral').toLowerCase()}">${result.emotion}</div>
</div>
<div style="font-size:10px;text-transform:uppercase;letter-spacing:0.08em;color:var(--outline);font-weight:600;margin-bottom:8px;">Frequency Spectrum</div>
${freqHtml}
<div style="margin-bottom:16px;">${barsHtml}</div>
<div style="display:flex;gap:16px;">
<div class="stat-card" style="flex:1;padding:12px;"><div class="stat-label">Engagement</div><div style="font-family:var(--font-headline);font-size:1.25rem;font-weight:700;color:var(--secondary);">${result.engagement_score}%</div></div>
<div class="stat-card" style="flex:1;padding:12px;"><div class="stat-label">Duration</div><div style="font-family:var(--font-headline);font-size:1.25rem;font-weight:700;color:var(--on-surface-variant);">${result.visualization?.duration || '—'}s</div></div>
</div>
${result.transcript ? '<div style="margin-top:16px;padding:14px 18px;background:rgba(0,0,0,0.25);border-radius:10px;border-left:4px solid var(--secondary);font-family:monospace;font-size:13px;color:var(--on-surface);line-height:1.6;"><span style="color:var(--outline);font-size:11px;text-transform:uppercase;letter-spacing:1px;font-weight:700;">Transcript ▸ </span>' + result.transcript + '</div>' : ''}
</div>`;
area.insertAdjacentHTML('afterbegin', html);
}
function showTextResult(result) {
const area = document.getElementById('results-area');
const emotions = result.emotions || {};
let emotionChips = '';
Object.entries(emotions).sort((a,b) => b[1]-a[1]).slice(0,5).forEach(([label, val]) => {
emotionChips += `<span class="emotion-chip" style="margin:4px;">${label}: ${val}%</span>`;
});
const sentColor = result.sentiment === 'POSITIVE' ? 'var(--primary-container)' : 'var(--error)';
const pct = Math.round(result.sentiment_score * 100);
const html = `
<div class="glass-card result-card animate-fade-in">
<div class="result-header">
<div style="display:flex;align-items:center;gap:12px;">
<div class="feature-icon tertiary" style="width:44px;height:44px;"><span class="material-symbols-outlined">segment</span></div>
<div>
<div style="font-size:10px;text-transform:uppercase;letter-spacing:0.08em;color:var(--outline);font-weight:600;">Text Sentiment Analysis</div>
<div style="font-family:var(--font-headline);font-size:1.25rem;font-weight:700;">${result.sentiment} — ${pct}%</div>
</div>
</div>
<span style="font-size:2rem;">${result.sentiment === 'POSITIVE' ? '😊' : '😟'}</span>
</div>
<div style="margin-bottom:16px;">
<div class="progress-track"><div class="progress-fill" style="width:${pct}%;background:linear-gradient(90deg,var(--error),var(--tertiary-fixed-dim),var(--primary-container));"></div></div>
<div style="display:flex;justify-content:space-between;margin-top:6px;font-size:9px;text-transform:uppercase;font-weight:700;color:var(--outline);"><span>Negative</span><span style="color:${sentColor};">${result.sentiment} (${pct}%)</span></div>
</div>
${emotionChips ? '<div style="margin-bottom:16px;">'+emotionChips+'</div>' : ''}
<div style="display:flex;gap:16px;">
<div class="stat-card" style="flex:1;padding:12px;"><div class="stat-label">Engagement</div><div style="font-family:var(--font-headline);font-size:1.25rem;font-weight:700;color:var(--tertiary-fixed-dim);">${result.engagement_score}%</div></div>
<div class="stat-card" style="flex:1;padding:12px;"><div class="stat-label">Word Count</div><div style="font-family:var(--font-headline);font-size:1.25rem;font-weight:700;color:var(--on-surface-variant);">${result.word_count}</div></div>
<div class="stat-card" style="flex:1;padding:12px;"><div class="stat-label">Subjectivity</div><div style="font-family:var(--font-headline);font-size:1.25rem;font-weight:700;color:var(--on-surface-variant);">${(result.subjectivity * 100).toFixed(0)}%</div></div>
</div>
${result.key_phrases?.length ? '<div style="margin-top:12px;font-size:11px;color:var(--outline);">Key phrases: ' + result.key_phrases.join(', ') + '</div>' : ''}
</div>`;
area.insertAdjacentHTML('afterbegin', html);
}
function showError(msg) {
const area = document.getElementById('results-area');
area.insertAdjacentHTML('afterbegin', `<div class="glass-card result-card animate-fade-in" style="border-color:rgba(255,180,171,0.3);"><div style="color:var(--error);font-weight:600;">${msg}</div></div>`);
}
function showSpinner() { document.getElementById('analysis-spinner').style.display = 'block'; }
function hideSpinner() { document.getElementById('analysis-spinner').style.display = 'none'; }
function clearResults() { document.getElementById('results-area').innerHTML = ''; document.getElementById('face-preview').style.display='none'; document.getElementById('audio-preview').style.display='none'; }
</script>
</body>
</html>