Prathamesh Sarjerao Vaidya
made some changes
0d2733f
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Multilingual Audio Intelligence System</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<style>
.upload-area {
border: 2px dashed #cbd5e1;
transition: all 0.3s ease;
}
.upload-area:hover {
border-color: #3b82f6;
background-color: #f8fafc;
}
.upload-area.dragover {
border-color: #2563eb;
background-color: #eff6ff;
}
.progress-bar {
background: linear-gradient(90deg, #3b82f6 0%, #1d4ed8 100%);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.page-section {
display: none;
}
.page-section.active {
display: block;
}
.loading {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hero-pattern {
background-image: radial-gradient(circle at 1px 1px, rgba(59, 130, 246, 0.15) 1px, transparent 0);
background-size: 20px 20px;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<!-- Header -->
<header class="bg-white shadow-sm border-b">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center py-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<h1 class="text-2xl font-bold text-gray-900 cursor-pointer" id="home-link">Audio Intelligence System</h1>
</div>
</div>
<div class="flex items-center space-x-4">
<button id="demo-mode-btn" class="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<i class="fas fa-play-circle mr-2"></i>
Demo Mode
</button>
<button id="processing-mode-btn" class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<i class="fas fa-cog mr-2"></i>
Full Processing
</button>
<!-- <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
<!-- <i class="fas fa-circle w-2 h-2 mr-2"></i> -->
<!-- <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800"> -->
<!-- <i class="fas fa-circle w-2 h-2 mr-1"></i> -->
<!--⬤ Operational
</span> -->
<span id="server-status" class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium">
⬤ Checking...
</span>
<button id="system-info-btn" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-info-circle"></i>
</button>
</div>
</div>
</div>
</header>
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<!-- Home Page Section -->
<div id="home-section" class="page-section active">
<!-- Hero Section -->
<div class="relative bg-white overflow-hidden rounded-lg shadow-lg mb-8">
<div class="hero-pattern absolute inset-0"></div>
<div class="relative px-4 py-16 sm:px-6 sm:py-24 lg:py-32 lg:px-8">
<div class="text-center">
<h1 class="text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl lg:text-6xl">
Multilingual Audio Intelligence
</h1>
<p class="mt-6 max-w-3xl mx-auto text-xl text-gray-500 leading-relaxed">
Advanced AI-powered speaker diarization, transcription, and translation system.
Transform any audio into structured, actionable insights with speaker attribution and cross-lingual understanding.
</p>
<div class="mt-10 flex justify-center space-x-4">
<button id="get-started-btn" class="inline-flex items-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors">
<i class="fas fa-rocket mr-2"></i>
Get Started
</button>
<button id="try-demo-btn" class="inline-flex items-center px-8 py-3 border border-gray-300 text-base font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors">
<i class="fas fa-play mr-2"></i>
Try Demo
</button>
</div>
</div>
</div>
</div>
<!-- Features Grid -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 mb-12">
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<i class="fas fa-users text-2xl text-blue-600"></i>
</div>
<div class="ml-4">
<h3 class="text-lg font-medium text-gray-900">Speaker Diarization</h3>
<p class="text-sm text-gray-500 mt-1">Identify who spoke when with 95%+ accuracy</p>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<i class="fas fa-language text-2xl text-green-600"></i>
</div>
<div class="ml-4">
<h3 class="text-lg font-medium text-gray-900">Multilingual Recognition</h3>
<p class="text-sm text-gray-500 mt-1">Support for 99+ languages with auto-detection</p>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<i class="fas fa-exchange-alt text-2xl text-purple-600"></i>
</div>
<div class="ml-4">
<h3 class="text-lg font-medium text-gray-900">Neural Translation</h3>
<p class="text-sm text-gray-500 mt-1">High-quality translation to multiple languages</p>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<i class="fas fa-chart-line text-2xl text-red-600"></i>
</div>
<div class="ml-4">
<h3 class="text-lg font-medium text-gray-900">Interactive Visualization</h3>
<p class="text-sm text-gray-500 mt-1">Real-time waveform analysis and insights</p>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<i class="fas fa-download text-2xl text-yellow-600"></i>
</div>
<div class="ml-4">
<h3 class="text-lg font-medium text-gray-900">Multiple Formats</h3>
<p class="text-sm text-gray-500 mt-1">Export as JSON, SRT, TXT, or CSV</p>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-6">
<div class="flex items-center">
<div class="flex-shrink-0">
<i class="fas fa-bolt text-2xl text-orange-600"></i>
</div>
<div class="ml-4">
<h3 class="text-lg font-medium text-gray-900">Fast Processing</h3>
<p class="text-sm text-gray-500 mt-1">14x real-time processing speed</p>
</div>
</div>
</div>
</div>
</div>
<!-- Technical Details -->
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Technical Specifications</h3>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<h4 class="text-sm font-medium text-gray-700 mb-2">Supported Audio Formats</h4>
<div class="flex flex-wrap gap-2">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">WAV</span>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">MP3</span>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">OGG</span>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">FLAC</span>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">M4A</span>
</div>
</div>
<div>
<h4 class="text-sm font-medium text-gray-700 mb-2">Performance</h4>
<ul class="text-sm text-gray-600 space-y-1">
<li>• Processing: 2-14x real-time</li>
<li>• Maximum file size: 100MB</li>
<li>• Recommended duration: Under 30 minutes</li>
<li>• CPU optimized (no GPU required)</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Processing Section -->
<div id="processing-section" class="page-section">
<div class="px-4 py-6 sm:px-0">
<div class="text-center mb-8">
<h2 class="text-3xl font-extrabold text-gray-900 sm:text-4xl">
Process Audio File
</h2>
<p class="mt-4 max-w-2xl mx-auto text-xl text-gray-500">
Upload your audio file and select processing options to get comprehensive analysis.
</p>
<div class="mt-4">
<span id="processing-mode-indicator" class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
<i class="fas fa-cog mr-2"></i>
Full Processing Mode
</span>
</div>
</div>
</div>
<!-- Upload Section -->
<div class="px-4 sm:px-0">
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Upload Audio File</h3>
<form id="upload-form" enctype="multipart/form-data">
<!-- Demo Mode Section -->
<div id="demo-mode-section" class="mb-6 hidden">
<h4 class="text-lg font-medium text-gray-900 mb-4">Select Demo Audio File</h4>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="demo-file-option border-2 border-gray-200 rounded-lg p-4 cursor-pointer hover:border-blue-500 transition-colors" data-demo-id="yuri_kizaki">
<div class="flex items-start">
<div class="flex-shrink-0">
<i class="fas fa-microphone text-2xl text-blue-600"></i>
</div>
<div class="ml-3">
<h5 class="text-sm font-medium text-gray-900">Yuri Kizaki - Japanese Audio</h5>
<p class="text-sm text-gray-500 mt-1">Audio message about website communication enhancement</p>
<div class="flex items-center mt-2">
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800">Japanese</span>
</div>
</div>
</div>
</div>
<div class="demo-file-option border-2 border-gray-200 rounded-lg p-4 cursor-pointer hover:border-blue-500 transition-colors" data-demo-id="film_podcast">
<div class="flex items-start">
<div class="flex-shrink-0">
<i class="fas fa-podcast text-2xl text-green-600"></i>
</div>
<div class="ml-3">
<h5 class="text-sm font-medium text-gray-900">French Film Podcast</h5>
<p class="text-sm text-gray-500 mt-1">Discussion about recent movies including Social Network</p>
<div class="flex items-center mt-2">
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">French</span>
</div>
</div>
</div>
</div>
</div>
<input type="hidden" id="selected-demo-file" name="demo_file_id" value="">
</div>
<!-- File Upload Area (Full Processing Mode) -->
<div id="file-upload-section" class="mb-6">
<div class="upload-area rounded-lg p-6 text-center mb-6" id="upload-area">
<input type="file" id="file-input" name="file" class="hidden" accept=".wav,.mp3,.ogg,.flac,.m4a">
<div id="upload-prompt">
<i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-4"></i>
<p class="text-lg text-gray-600 mb-2">Click to upload or drag and drop</p>
<p class="text-sm text-gray-500">WAV, MP3, OGG, FLAC, or M4A files up to 100MB</p>
</div>
<div id="file-info" class="hidden">
<i class="fas fa-file-audio text-4xl text-blue-500 mb-4"></i>
<p id="file-name" class="text-lg text-gray-800 mb-2"></p>
<p id="file-size" class="text-sm text-gray-500"></p>
</div>
</div>
</div>
<!-- Audio Preview Section -->
<div id="audio-preview" class="mb-6 hidden">
<label class="block text-sm font-medium text-gray-700 mb-2">Audio Preview</label>
<div class="bg-gray-50 p-4 rounded-lg border">
<audio id="audio-player" controls class="w-full mb-4">
Your browser does not support the audio element.
</audio>
<!-- Waveform Visualization -->
<div id="waveform-container" class="mt-4">
<canvas id="waveform-canvas" class="w-full h-20 bg-gray-100 rounded"></canvas>
</div>
</div>
</div>
<!-- Configuration Options -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 mb-6">
<div>
<label for="whisper-model" class="block text-sm font-medium text-gray-700">Model Size</label>
<select id="whisper-model" name="whisper_model" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md">
<option value="tiny">Tiny (Fast, Lower Accuracy)</option>
<option value="small" selected>Small (Balanced)</option>
<option value="medium">Medium (Better Accuracy)</option>
<option value="large">Large (Best Accuracy, Slower)</option>
</select>
</div>
<div>
<label for="target-language" class="block text-sm font-medium text-gray-700">Target Language</label>
<select id="target-language" name="target_language" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md">
<option value="en" selected>English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
<option value="de">German</option>
<option value="it">Italian</option>
<option value="pt">Portuguese</option>
<option value="zh">Chinese</option>
<option value="ja">Japanese</option>
<option value="ko">Korean</option>
<option value="ar">Arabic</option>
</select>
</div>
</div>
<!-- Submit Button -->
<div class="flex justify-center">
<button type="submit" id="process-btn" class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed">
<i class="fas fa-play mr-2"></i>
Process Audio
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Progress Section -->
<div id="progress-section" class="px-4 sm:px-0 mt-6 hidden">
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Processing Status</h3>
<div class="mb-4">
<div class="flex justify-between text-sm text-gray-600 mb-1">
<span id="progress-text">Initializing...</span>
<span id="progress-percent">0%</span>
</div>
<div class="bg-gray-200 rounded-full h-2">
<div id="progress-bar" class="progress-bar h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
</div>
</div>
<p id="progress-detail" class="text-sm text-gray-500">Please wait while we process your audio file...</p>
</div>
</div>
</div>
<!-- Results Section -->
<div id="results-section" class="px-4 sm:px-0 mt-6 hidden">
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="flex justify-between items-center mb-6">
<h3 class="text-lg font-medium text-gray-900">Analysis Results</h3>
<div class="flex space-x-2">
<button id="download-json" class="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<i class="fas fa-download mr-2"></i>JSON
</button>
<button id="download-srt" class="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<i class="fas fa-download mr-2"></i>SRT
</button>
<button id="download-txt" class="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<i class="fas fa-download mr-2"></i>Text
</button>
</div>
</div>
<!-- Tabs -->
<div class="border-b border-gray-200 mb-6">
<nav class="-mb-px flex space-x-8">
<button class="tab-btn whitespace-nowrap py-2 px-1 border-b-2 border-blue-500 font-medium text-sm text-blue-600" data-tab="transcript">
Transcript & Translation
</button>
<button class="tab-btn whitespace-nowrap py-2 px-1 border-b-2 border-transparent font-medium text-sm text-gray-500 hover:text-gray-700 hover:border-gray-300" data-tab="visualization">
Analytics & Insights
</button>
<button class="tab-btn whitespace-nowrap py-2 px-1 border-b-2 border-transparent font-medium text-sm text-gray-500 hover:text-gray-700 hover:border-gray-300" data-tab="summary">
Summary
</button>
</nav>
</div>
<!-- Tab Content -->
<div id="transcript-tab" class="tab-content active">
<div id="transcript-content">
<!-- Transcript and translation will be populated here -->
</div>
</div>
<div id="visualization-tab" class="tab-content">
<div class="grid grid-cols-1 gap-6">
<div id="language-chart" style="width:100%;height:300px;"></div>
<div id="speaker-timeline" style="width:100%;height:300px;"></div>
</div>
</div>
<div id="summary-tab" class="tab-content">
<div id="summary-content">
<!-- Summary will be populated here -->
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- System Info Modal -->
<div id="system-info-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden">
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
<div class="mt-3">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-900">System Information</h3>
<button id="close-modal" class="text-gray-400 hover:text-gray-600">
<i class="fas fa-times"></i>
</button>
</div>
<div id="system-info-content">
<div class="loading text-center py-4">
<i class="fas fa-spinner text-2xl text-blue-500"></i>
</div>
<p class="mt-2 text-gray-600">Loading system information...</p>
</div>
</div>
</div>
</div>
<script>
// Global variables
let currentTaskId = null;
let progressInterval = null;
let isDemoMode = false;
// DOM elements
const homeSection = document.getElementById('home-section');
const processingSection = document.getElementById('processing-section');
const uploadArea = document.getElementById('upload-area');
const fileInput = document.getElementById('file-input');
const uploadForm = document.getElementById('upload-form');
const processBtn = document.getElementById('process-btn');
const progressSection = document.getElementById('progress-section');
const resultsSection = document.getElementById('results-section');
const systemInfoBtn = document.getElementById('system-info-btn');
const systemInfoModal = document.getElementById('system-info-modal');
const closeModal = document.getElementById('close-modal');
// Navigation elements
const homeLink = document.getElementById('home-link');
const getStartedBtn = document.getElementById('get-started-btn');
const tryDemoBtn = document.getElementById('try-demo-btn');
const demoModeBtn = document.getElementById('demo-mode-btn');
const processingModeBtn = document.getElementById('processing-mode-btn');
const processingModeIndicator = document.getElementById('processing-mode-indicator');
async function updateServerStatus() {
const el = document.getElementById("server-status");
try {
const res = await fetch("/health"); // or your FastAPI health endpoint
if (!res.ok) throw new Error("Bad response");
el.textContent = "⬤ Live";
el.className = "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800";
} catch (err) {
// Could be error or down
fetch("/").catch(() => {
el.textContent = "⬤ Server Down";
el.className = "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800";
});
el.textContent = "⬤ Error";
el.className = "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800";
}
}
// setInterval(updateServerStatus, 5000);
// updateServerStatus();
document.addEventListener("DOMContentLoaded", updateServerStatus);
// Navigation handling
function showHome() {
homeSection.classList.add('active');
processingSection.classList.remove('active');
resetProcessing();
}
function showProcessing(demoMode = false) {
homeSection.classList.remove('active');
processingSection.classList.add('active');
isDemoMode = demoMode;
updateProcessingMode();
resetProcessing();
}
function updateProcessingMode() {
if (isDemoMode) {
processingModeIndicator.innerHTML = '<i class="fas fa-play-circle mr-2"></i>Demo Mode';
processingModeIndicator.className = 'inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800';
demoModeBtn.className = 'inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500';
processingModeBtn.className = 'inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500';
// Show demo section, hide file upload
document.getElementById('demo-mode-section').classList.remove('hidden');
document.getElementById('file-upload-section').classList.add('hidden');
} else {
processingModeIndicator.innerHTML = '<i class="fas fa-cog mr-2"></i>Full Processing Mode';
processingModeIndicator.className = 'inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800';
demoModeBtn.className = 'inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500';
processingModeBtn.className = 'inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500';
// Hide demo section, show file upload
document.getElementById('demo-mode-section').classList.add('hidden');
document.getElementById('file-upload-section').classList.remove('hidden');
}
}
function resetProcessing() {
progressSection.classList.add('hidden');
resultsSection.classList.add('hidden');
if (progressInterval) {
clearInterval(progressInterval);
progressInterval = null;
}
currentTaskId = null;
// Reset form
document.getElementById('upload-prompt').classList.remove('hidden');
document.getElementById('file-info').classList.add('hidden');
document.getElementById('audio-preview').classList.add('hidden');
// Reset demo selection
document.querySelectorAll('.demo-file-option').forEach(opt => {
opt.classList.remove('border-blue-500', 'bg-blue-50');
opt.classList.add('border-gray-200');
});
document.getElementById('selected-demo-file').value = '';
uploadForm.reset();
}
// Demo file selection handling
document.querySelectorAll('.demo-file-option').forEach(option => {
option.addEventListener('click', () => {
// Remove selection from all options
document.querySelectorAll('.demo-file-option').forEach(opt => {
opt.classList.remove('border-blue-500', 'bg-blue-50');
opt.classList.add('border-gray-200');
});
// Select clicked option
option.classList.add('border-blue-500', 'bg-blue-50');
option.classList.remove('border-gray-200');
// Set selected demo file ID
const demoId = option.dataset.demoId;
document.getElementById('selected-demo-file').value = demoId;
// Load demo audio preview
loadDemoAudioPreview(demoId);
});
});
async function loadDemoAudioPreview(demoId) {
try {
// For demo purposes, we'll show a placeholder waveform
const audioPreview = document.getElementById('audio-preview');
const audioPlayer = document.getElementById('audio-player');
// Set demo audio source (if files are available locally)
const demoConfig = {
'yuri_kizaki': {
name: 'Yuri Kizaki - Japanese Audio',
filename: 'Yuri_Kizaki.mp3',
duration: 23.0
},
'film_podcast': {
name: 'French Film Podcast',
filename: 'Film_Podcast.mp3',
duration: 25.0
}
};
if (demoConfig[demoId]) {
// Try to load demo file if available
try {
// audioPlayer.src = `/demo_audio/${demoConfig[demoId].name.replace(' - ', ' - ').replace('Japanese Audio', '03.mp3').replace('French Film Podcast', 'film-podcast.mp3')}`;
audioPlayer.src = `/demo_audio/${demoConfig[demoId].filename}`;
audioPlayer.load();
// 🔹 Enable live waveform updates
audioPlayer.addEventListener('loadedmetadata', () => {
generateWaveformFromAudio(audioPlayer);
});
} catch (e) {
console.log('Demo audio file not directly accessible, will be processed on server');
}
// Generate demo waveform
// generateDemoWaveform(demoConfig[demoId].duration);
audioPreview.classList.remove('hidden');
}
} catch (error) {
console.error('Error loading demo preview:', error);
}
}
function generateDemoWaveform(duration) {
const canvas = document.getElementById('waveform-canvas');
const ctx = canvas.getContext('2d');
// Set canvas size
canvas.width = canvas.offsetWidth * window.devicePixelRatio;
canvas.height = 80 * window.devicePixelRatio;
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
// Clear canvas
ctx.clearRect(0, 0, canvas.offsetWidth, 80);
// Generate sample waveform data
const samples = 200;
const barWidth = canvas.offsetWidth / samples;
ctx.fillStyle = '#3B82F6';
for (let i = 0; i < samples; i++) {
// Generate realistic waveform pattern
const amplitude = Math.sin(i * 0.1) * Math.random() * 0.8 + 0.2;
const height = amplitude * 60;
const x = i * barWidth;
const y = (80 - height) / 2;
ctx.fillRect(x, y, barWidth - 1, height);
}
}
function handleFileSelect() {
const file = fileInput.files[0];
if (file) {
document.getElementById('upload-prompt').classList.add('hidden');
document.getElementById('file-info').classList.remove('hidden');
document.getElementById('file-name').textContent = file.name;
document.getElementById('file-size').textContent = formatFileSize(file.size);
// Show audio preview with waveform
const audioPreview = document.getElementById('audio-preview');
const audioPlayer = document.getElementById('audio-player');
if (file.type.startsWith('audio/')) {
const url = URL.createObjectURL(file);
audioPlayer.src = url;
audioPreview.classList.remove('hidden');
// Generate waveform when audio loads
audioPlayer.addEventListener('loadedmetadata', () => {
generateWaveformFromAudio(audioPlayer);
});
}
}
}
function generateWaveformFromAudio(audioElement) {
try {
// Create AudioContext for waveform generation
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const source = audioContext.createMediaElementSource(audioElement);
const analyser = audioContext.createAnalyser();
source.connect(analyser);
analyser.connect(audioContext.destination);
analyser.fftSize = 512;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const canvas = document.getElementById('waveform-canvas');
const ctx = canvas.getContext('2d');
function draw() {
analyser.getByteFrequencyData(dataArray);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#3B82F6';
const barWidth = canvas.offsetWidth / bufferLength;
for (let i = 0; i < bufferLength; i++) {
const barHeight = (dataArray[i] / 255) * 60;
const x = i * barWidth;
const y = (80 - barHeight) / 2;
ctx.fillRect(x, y, barWidth - 1, barHeight);
}
if (!audioElement.paused) {
requestAnimationFrame(draw);
}
}
// Initial static waveform
generateDemoWaveform(audioElement.duration || 30);
// Dynamic waveform when playing
audioElement.addEventListener('play', () => {
if (audioContext.state === 'suspended') {
audioContext.resume();
}
draw();
});
} catch (error) {
console.log('Web Audio API not available, showing static waveform');
generateDemoWaveform(audioElement.duration || 30);
}
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Event listeners for navigation
homeLink.addEventListener('click', showHome);
getStartedBtn.addEventListener('click', () => showProcessing(false));
tryDemoBtn.addEventListener('click', () => showProcessing(true));
demoModeBtn.addEventListener('click', () => showProcessing(true));
processingModeBtn.addEventListener('click', () => showProcessing(false));
// File upload handling
uploadArea.addEventListener('click', () => fileInput.click());
uploadArea.addEventListener('dragover', handleDragOver);
uploadArea.addEventListener('dragleave', handleDragLeave);
uploadArea.addEventListener('drop', handleDrop);
fileInput.addEventListener('change', handleFileSelect);
function handleDragOver(e) {
e.preventDefault();
uploadArea.classList.add('dragover');
}
function handleDragLeave(e) {
e.preventDefault();
uploadArea.classList.remove('dragover');
}
function handleDrop(e) {
e.preventDefault();
uploadArea.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
fileInput.files = files;
handleFileSelect();
}
}
// Form submission
uploadForm.addEventListener('submit', async (e) => {
e.preventDefault();
// Validate based on mode
if (isDemoMode) {
const selectedDemo = document.getElementById('selected-demo-file').value;
if (!selectedDemo) {
alert('Please select a demo audio file.');
return;
}
} else {
if (!fileInput.files[0]) {
alert('Please select a file to upload.');
return;
}
}
const formData = new FormData();
// Add form data based on mode
if (isDemoMode) {
formData.append('demo_file_id', document.getElementById('selected-demo-file').value);
formData.append('whisper_model', document.getElementById('whisper-model').value);
formData.append('target_language', document.getElementById('target-language').value);
} else {
formData.append('file', fileInput.files[0]);
formData.append('whisper_model', document.getElementById('whisper-model').value);
formData.append('target_language', document.getElementById('target-language').value);
}
try {
processBtn.disabled = true;
processBtn.innerHTML = '<i class="fas fa-spinner loading mr-2"></i>Starting...';
// Choose endpoint based on mode
const endpoint = isDemoMode ? '/api/demo-process' : '/api/upload';
const response = await fetch(endpoint, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.status === 'complete') {
// Demo mode returns immediate results
showResults(result.results);
} else {
// Real processing mode - handle async processing
currentTaskId = result.task_id;
showProgress();
startProgressPolling();
}
} catch (error) {
console.error('Upload error:', error);
alert('Error processing request: ' + error.message);
} finally {
processBtn.disabled = false;
processBtn.innerHTML = '<i class="fas fa-play mr-2"></i>Process Audio';
}
});
function showProgress() {
progressSection.classList.remove('hidden');
resultsSection.classList.add('hidden');
}
function startProgressPolling() {
if (!currentTaskId) return;
progressInterval = setInterval(async () => {
try {
const response = await fetch(`/api/status/${currentTaskId}`);
const status = await response.json();
updateProgress(status);
if (status.status === 'complete') {
clearInterval(progressInterval);
const resultsResponse = await fetch(`/api/results/${currentTaskId}`);
const results = await resultsResponse.json();
showResults(results.results);
} else if (status.status === 'error') {
clearInterval(progressInterval);
alert('Processing error: ' + status.error);
progressSection.classList.add('hidden');
}
} catch (error) {
console.error('Status polling error:', error);
}
}, 1000);
}
function updateProgress(status) {
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
const progressPercent = document.getElementById('progress-percent');
const progressDetail = document.getElementById('progress-detail');
const progress = status.progress || 0;
progressBar.style.width = `${progress}%`;
progressPercent.textContent = `${progress}%`;
const statusMessages = {
'initializing': 'Initializing processing pipeline...',
'processing': 'Analyzing audio and identifying speakers...',
'generating_outputs': 'Generating transcripts and translations...',
'complete': 'Processing complete!'
};
progressText.textContent = statusMessages[status.status] || 'Processing...';
progressDetail.textContent = isDemoMode ?
'Demo mode - results will be shown shortly.' :
'This may take a few minutes depending on audio length.';
}
function showResults(results) {
progressSection.classList.add('hidden');
resultsSection.classList.remove('hidden');
// Populate transcript
populateTranscript(results.segments);
// Populate visualizations
populateVisualizations(results.segments);
// Populate summary
populateSummary(results.summary);
// Setup download buttons
setupDownloadButtons();
}
function populateVisualizations(segments) {
// Language Distribution Chart
createLanguageChart(segments);
// Speaker Timeline
createSpeakerTimeline(segments);
}
function createLanguageChart(segments) {
const languages = {};
const languageDurations = {};
segments.forEach(seg => {
const lang = seg.language.toUpperCase();
const duration = seg.end_time - seg.start_time;
languages[lang] = (languages[lang] || 0) + 1;
languageDurations[lang] = (languageDurations[lang] || 0) + duration;
});
const data = [{
values: Object.values(languages),
labels: Object.keys(languages),
type: 'pie',
marker: {
colors: ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6']
},
textinfo: 'label+percent',
textposition: 'auto'
}];
const layout = {
title: {
text: '🌍 Language Distribution',
font: { size: 18, family: 'Arial, sans-serif' }
},
showlegend: true,
height: 300,
margin: { t: 50, b: 20, l: 20, r: 20 }
};
Plotly.newPlot('language-chart', data, layout, {responsive: true});
}
function createSpeakerTimeline(segments) {
const speakers = [...new Set(segments.map(seg => seg.speaker))];
const colors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6'];
const data = speakers.map((speaker, index) => {
const speakerSegments = segments.filter(seg => seg.speaker === speaker);
return {
x: speakerSegments.map(seg => seg.start_time),
y: speakerSegments.map(() => speaker),
mode: 'markers',
type: 'scatter',
marker: {
size: speakerSegments.map(seg => (seg.end_time - seg.start_time) * 5),
color: colors[index % colors.length],
opacity: 0.7
},
name: speaker,
text: speakerSegments.map(seg => `${seg.text.substring(0, 50)}...`),
hovertemplate: '%{text}<br>Time: %{x:.1f}s<extra></extra>'
};
});
const layout = {
title: {
text: '👥 Speaker Activity Timeline',
font: { size: 18, family: 'Arial, sans-serif' }
},
xaxis: { title: 'Time (seconds)' },
yaxis: { title: 'Speakers' },
height: 300,
margin: { t: 50, b: 50, l: 100, r: 20 }
};
Plotly.newPlot('speaker-timeline', data, layout, {responsive: true});
}
function populateTranscript(segments) {
const transcriptContent = document.getElementById('transcript-content');
transcriptContent.innerHTML = '';
segments.forEach((segment, index) => {
const segmentDiv = document.createElement('div');
segmentDiv.className = 'mb-6 p-4 border border-gray-200 rounded-lg bg-white shadow-sm';
segmentDiv.innerHTML = `
<div class="flex justify-between items-start mb-3">
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
${segment.speaker}
</span>
<span class="text-sm text-gray-500">
${formatTime(segment.start_time)} - ${formatTime(segment.end_time)}
</span>
</div>
<div class="space-y-3">
<div class="bg-gray-50 p-3 rounded-lg">
<div class="flex items-center mb-2">
<i class="fas fa-microphone text-gray-600 mr-2"></i>
<span class="text-sm font-medium text-gray-700">Original (${segment.language.toUpperCase()})</span>
</div>
<p class="text-gray-800 leading-relaxed">${segment.text}</p>
</div>
${segment.translated_text && segment.translated_text !== segment.text && segment.language !== 'en' ? `
<div class="bg-blue-50 p-3 rounded-lg">
<div class="flex items-center mb-2">
<i class="fas fa-language text-blue-600 mr-2"></i>
<span class="text-sm font-medium text-blue-700">English Translation</span>
</div>
<p class="text-blue-800 leading-relaxed italic">${segment.translated_text}</p>
</div>
` : ''}
</div>
`;
transcriptContent.appendChild(segmentDiv);
});
}
function populateSummary(summary) {
const summaryContent = document.getElementById('summary-content');
summaryContent.innerHTML = `
<div class="grid grid-cols-2 gap-4">
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="text-sm font-medium text-gray-700">Total Duration</h4>
<p class="text-2xl font-bold text-gray-900">${formatTime(summary.total_duration)}</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="text-sm font-medium text-gray-700">Speakers Detected</h4>
<p class="text-2xl font-bold text-gray-900">${summary.num_speakers}</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="text-sm font-medium text-gray-700">Speech Segments</h4>
<p class="text-2xl font-bold text-gray-900">${summary.num_segments}</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="text-sm font-medium text-gray-700">Processing Time</h4>
<p class="text-2xl font-bold text-gray-900">${summary.processing_time}s</p>
</div>
</div>
<div class="mt-4">
<h4 class="text-sm font-medium text-gray-700 mb-2">Languages Detected</h4>
<div class="flex flex-wrap gap-2">
${summary.languages.map(lang =>
`<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">${lang}</span>`
).join('')}
</div>
</div>
`;
}
function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
function setupDownloadButtons() {
document.getElementById('download-json').onclick = () => downloadFile('json');
document.getElementById('download-srt').onclick = () => downloadFile('srt');
document.getElementById('download-txt').onclick = () => downloadFile('txt');
}
function downloadFile(format) {
if (currentTaskId) {
window.open(`/api/download/${currentTaskId}/${format}`, '_blank');
}
}
// Tab handling
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const tabName = e.target.dataset.tab;
// Update tab buttons
document.querySelectorAll('.tab-btn').forEach(b => {
b.classList.remove('border-blue-500', 'text-blue-600');
b.classList.add('border-transparent', 'text-gray-500');
});
e.target.classList.add('border-blue-500', 'text-blue-600');
e.target.classList.remove('border-transparent', 'text-gray-500');
// Update tab content
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
document.getElementById(`${tabName}-tab`).classList.add('active');
});
});
// System info modal
systemInfoBtn.addEventListener('click', async () => {
systemInfoModal.classList.remove('hidden');
const content = document.getElementById('system-info-content');
content.innerHTML = `
<div class="loading text-center py-4">
<i class="fas fa-spinner text-2xl text-blue-500 animate-spin"></i>
<p class="mt-2 text-gray-600">Loading system information...</p>
</div>
`;
try {
const response = await fetch('/api/system-info');
const info = await response.json();
const statusColors = {
green: "bg-green-100 text-green-800",
yellow: "bg-yellow-100 text-yellow-800",
red: "bg-red-100 text-red-800",
gray: "bg-gray-100 text-gray-800"
};
const colorClass = statusColors[info.statusColor] || statusColors.gray;
content.innerHTML = `
<div class="space-y-3">
<div>
<span class="font-medium">Status:</span>
<span class="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${colorClass}">
${info.status}
</span>
</div>
<div>
<span class="font-medium">Version:</span>
<span class="ml-2 text-gray-600">${info.version}</span>
</div>
<div>
<span class="font-medium">Features:</span>
<div class="mt-2 flex flex-wrap gap-1">
${info.features.map(feature =>
`<span class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-blue-100 text-blue-800">${feature}</span>`
).join('')}
</div>
</div>
</div>
`;
} catch (error) {
content.innerHTML = `<p class="text-red-600">Error loading system information</p>`;
}
});
closeModal.addEventListener('click', () => {
systemInfoModal.classList.add('hidden');
});
// Close modal when clicking outside
systemInfoModal.addEventListener('click', (e) => {
if (e.target === systemInfoModal) {
systemInfoModal.classList.add('hidden');
}
});
// Initialize page
updateProcessingMode();
</script>
</body>
</html>