VaniAI / frontend /script.js
Susovan85's picture
Upload 11 files
c07919e verified
document.addEventListener('DOMContentLoaded', () => {
// DOM Elements
const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input');
const filePreview = document.getElementById('file-preview');
const fileName = document.getElementById('file-name');
const fileSize = document.getElementById('file-size');
const removeFileBtn = document.getElementById('remove-file-btn');
const transcribeBtn = document.getElementById('transcribe-btn');
const uploadSection = document.getElementById('upload-section');
const processingSection = document.getElementById('processing-section');
const resultSection = document.getElementById('result-section');
const detectedLang = document.getElementById('detected-lang');
const captionsList = document.getElementById('captions-list');
const fullTextContent = document.getElementById('full-text-content');
const copyTextBtn = document.getElementById('copy-text-btn');
const downloadTxtBtn = document.getElementById('download-txt-btn');
const downloadSrtBtn = document.getElementById('download-srt-btn');
const newTranscriptionBtn = document.getElementById('new-transcription-btn');
const tabBtns = document.querySelectorAll('.tab-btn');
const tabContents = document.querySelectorAll('.tab-content');
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toast-message');
// State
let selectedFile = null;
let transcriptionResult = null;
// --- File Upload Logic ---
// Click to upload
dropZone.addEventListener('click', (e) => {
if (e.target !== removeFileBtn && !removeFileBtn.contains(e.target)) {
fileInput.click();
}
});
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleFile(e.target.files[0]);
}
});
// Drag and drop
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('dragover');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('dragover');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
if (e.dataTransfer.files.length > 0) {
handleFile(e.dataTransfer.files[0]);
}
});
function handleFile(file) {
// Validate file type
const validTypes = ['video/mp4', 'video/quicktime', 'video/x-msvideo', 'video/webm', 'video/x-matroska'];
if (!validTypes.includes(file.type) && !file.name.match(/\.(mp4|mov|avi|webm|mkv)$/i)) {
showToast('Invalid file format. Please upload a video.', true);
return;
}
// Validate file size (e.g., 50MB max to prevent huge uploads locally)
const maxSize = 50 * 1024 * 1024;
if (file.size > maxSize) {
showToast('File is too large. Max size is 50MB.', true);
return;
}
selectedFile = file;
fileName.textContent = file.name;
fileSize.textContent = formatBytes(file.size);
dropZone.style.display = 'none';
filePreview.classList.remove('hidden');
transcribeBtn.classList.remove('hidden');
}
removeFileBtn.addEventListener('click', (e) => {
e.stopPropagation();
selectedFile = null;
fileInput.value = '';
dropZone.style.display = 'block';
filePreview.classList.add('hidden');
transcribeBtn.classList.add('hidden');
});
// --- API & Processing ---
transcribeBtn.addEventListener('click', async () => {
if (!selectedFile) return;
// Show processing
uploadSection.classList.add('hidden');
processingSection.classList.remove('hidden');
const formData = new FormData();
formData.append('file', selectedFile);
const apiKeyInput = document.getElementById('api-key-input');
if (apiKeyInput && apiKeyInput.value.trim() !== '') {
formData.append('api_key', apiKeyInput.value.trim());
}
let apiUrl = '/api/transcribe';
// Fallback for local development if opened directly or via Live Server
if (window.location.protocol === 'file:' || window.location.port === '5500' || window.location.hostname === '127.0.0.1' && window.location.port !== '8000') {
apiUrl = 'http://127.0.0.1:8000/api/transcribe';
}
try {
const response = await fetch(apiUrl, {
method: 'POST',
body: formData
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `Server error: ${response.status}`);
}
const data = await response.json();
transcriptionResult = data;
renderResults(data);
processingSection.classList.add('hidden');
resultSection.classList.remove('hidden');
showToast('Transcription successful!');
} catch (error) {
console.error('Transcription error:', error);
showToast(error.message || 'Transcription failed', true);
// Revert UI
processingSection.classList.add('hidden');
uploadSection.classList.remove('hidden');
}
});
// --- Result Rendering ---
function renderResults(data) {
// Language detection
let langName = 'Mixed / Unknown';
if (data.segments && data.segments.length > 0) {
const lang = data.segments[0].lang || 'hi-en';
if (lang === 'en') langName = 'English';
else if (lang === 'hi') langName = 'Hindi';
else if (lang.includes('hi') && lang.includes('en')) langName = 'Hinglish';
else langName = lang.toUpperCase();
}
detectedLang.textContent = `Detected: ${langName}`;
// Render full text
fullTextContent.textContent = data.text;
// Render captions
captionsList.innerHTML = '';
if (data.segments && data.segments.length > 0) {
data.segments.forEach(seg => {
const item = document.createElement('div');
item.className = 'caption-item';
item.innerHTML = `
<div class="caption-ts">${seg.ts}</div>
<div class="caption-text">${seg.text}</div>
`;
captionsList.appendChild(item);
});
} else {
captionsList.innerHTML = '<div style="padding:1rem;text-align:center;color:var(--text-muted)">No timeline data available.</div>';
}
}
// --- Tabs Logic ---
tabBtns.forEach(btn => {
btn.addEventListener('click', () => {
// Remove active classes
tabBtns.forEach(b => b.classList.remove('active'));
tabContents.forEach(c => c.classList.remove('active'));
// Add active to clicked
btn.classList.add('active');
document.getElementById(btn.dataset.target).classList.add('active');
});
});
// --- Actions ---
copyTextBtn.addEventListener('click', () => {
if (!transcriptionResult || !transcriptionResult.text) return;
navigator.clipboard.writeText(transcriptionResult.text).then(() => {
showToast('Text copied to clipboard');
}).catch(err => {
console.error('Copy failed', err);
showToast('Failed to copy text', true);
});
});
downloadTxtBtn.addEventListener('click', () => {
if (!transcriptionResult || !transcriptionResult.text) return;
downloadFile(transcriptionResult.text, 'transcript.txt', 'text/plain');
});
downloadSrtBtn.addEventListener('click', () => {
if (!transcriptionResult || !transcriptionResult.srt) {
showToast('SRT data not available', true);
return;
}
downloadFile(transcriptionResult.srt, 'subtitles.srt', 'text/plain');
});
newTranscriptionBtn.addEventListener('click', () => {
resultSection.classList.add('hidden');
uploadSection.classList.remove('hidden');
// Reset state
removeFileBtn.click();
transcriptionResult = null;
// Reset tabs
tabBtns[0].click();
});
function downloadFile(content, fileName, mimeType) {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// --- Utility ---
function formatBytes(bytes, decimals = 2) {
if (!+bytes) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
}
let toastTimeout;
function showToast(message, isError = false) {
toastMessage.textContent = message;
if (isError) {
toast.classList.add('error');
toast.querySelector('i').className = 'fa-solid fa-circle-exclamation';
} else {
toast.classList.remove('error');
toast.querySelector('i').className = 'fa-solid fa-circle-check';
}
toast.classList.add('show');
clearTimeout(toastTimeout);
toastTimeout = setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
});