// API Base URL const API_URL = ''; // Tab switching document.querySelectorAll('.tab').forEach(tab => { tab.addEventListener('click', () => { document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); tab.classList.add('active'); document.getElementById(tab.dataset.tab).classList.add('active'); }); }); // Helper: Show result function showResult(elementId, success, content) { const el = document.getElementById(elementId); el.className = `result show ${success ? 'success' : 'error'}`; el.innerHTML = content; } // Helper: Set loading state function setLoading(button, loading) { button.disabled = loading; button.classList.toggle('loading', loading); } // Format seconds to MM:SS or HH:MM:SS function formatTime(seconds) { if (!seconds || seconds < 0) return '--:--'; const hrs = Math.floor(seconds / 3600); const mins = Math.floor((seconds % 3600) / 60); const secs = Math.floor(seconds % 60); if (hrs > 0) { return `${hrs}:${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`; } return `${mins}:${String(secs).padStart(2, '0')}`; } // Format file size function formatSize(bytes) { if (!bytes) return ''; const units = ['B', 'KB', 'MB', 'GB']; let i = 0; while (bytes >= 1024 && i < units.length - 1) { bytes /= 1024; i++; } return `${bytes.toFixed(1)} ${units[i]}`; } // ==================== DOWNLOAD TAB ==================== const progressContainer = document.getElementById('download-progress'); const progressFill = document.getElementById('progress-fill'); const progressPercent = document.getElementById('progress-percent'); const progressTitle = document.getElementById('progress-title'); const progressEta = document.getElementById('progress-eta'); const progressInfo = document.getElementById('progress-info'); const progressSpeed = document.getElementById('progress-speed'); const progressCurrent = document.getElementById('progress-current'); function updateDownloadProgress(data) { progressFill.style.width = `${data.percent}%`; progressPercent.textContent = `${data.percent}%`; if (data.totalVideos > 1) { progressInfo.textContent = `Video ${data.currentVideo}/${data.totalVideos}`; } else { progressInfo.textContent = ''; } if (data.speed) progressSpeed.textContent = data.speed; if (data.estimatedRemaining) { progressEta.textContent = `ETA: ${formatTime(data.estimatedRemaining)}`; } else if (data.eta) { progressEta.textContent = `ETA: ${data.eta}`; } if (data.title) { progressCurrent.innerHTML = `Downloading: ${data.title}`; } } function resetDownloadProgress() { progressFill.style.width = '0%'; progressPercent.textContent = '0%'; progressTitle.textContent = 'Downloading...'; progressEta.textContent = ''; progressInfo.textContent = ''; progressSpeed.textContent = ''; progressCurrent.textContent = ''; } document.getElementById('download-form').addEventListener('submit', async (e) => { e.preventDefault(); const button = e.target.querySelector('button[type="submit"]'); const url = document.getElementById('download-url').value; const resultDiv = document.getElementById('download-result'); setLoading(button, true); resetDownloadProgress(); progressContainer.style.display = 'block'; resultDiv.classList.remove('show'); const eventSource = new EventSource(`${API_URL}/download-stream?url=${encodeURIComponent(url)}`); eventSource.addEventListener('status', (e) => { progressTitle.textContent = JSON.parse(e.data).message; }); eventSource.addEventListener('info', (e) => { const data = JSON.parse(e.data); progressTitle.textContent = data.totalVideos > 1 ? `Downloading playlist: ${data.playlistTitle} (${data.totalVideos} videos)` : `Downloading: ${data.title}`; }); eventSource.addEventListener('progress', (e) => updateDownloadProgress(JSON.parse(e.data))); eventSource.addEventListener('video-complete', (e) => { const data = JSON.parse(e.data); progressCurrent.innerHTML = `Completed: ${data.title} (${data.videosCompleted}/${data.totalVideos})`; }); eventSource.addEventListener('complete', (e) => { const data = JSON.parse(e.data); eventSource.close(); progressFill.style.width = '100%'; progressPercent.textContent = '100%'; progressTitle.textContent = 'Download Complete!'; progressEta.textContent = `Total: ${formatTime(data.totalTime)}`; showResult('download-result', true, `

Download Complete!

${data.successCount}/${data.totalVideos} videos downloaded

${data.playlistTitle ? `

Playlist: ${data.playlistTitle}

` : ''} `); setLoading(button, false); }); eventSource.addEventListener('error', (e) => { let errorMsg = 'Download failed'; try { errorMsg = JSON.parse(e.data).message || errorMsg; } catch {} eventSource.close(); progressContainer.style.display = 'none'; showResult('download-result', false, `

Error

${errorMsg}

`); setLoading(button, false); }); eventSource.onerror = () => { eventSource.close(); progressContainer.style.display = 'none'; showResult('download-result', false, `

Error

Connection lost

`); setLoading(button, false); }; }); // ==================== TRANSCRIBE TAB (Drag & Drop) ==================== let selectedFiles = []; const dropZone = document.getElementById('drop-zone'); const fileInput = document.getElementById('file-input'); const selectedFilesDiv = document.getElementById('selected-files'); const filesList = document.getElementById('files-list'); const transcribeBtn = document.getElementById('transcribe-btn'); const clearFilesBtn = document.getElementById('clear-files'); function updateFilesList() { if (selectedFiles.length === 0) { selectedFilesDiv.style.display = 'none'; transcribeBtn.disabled = true; return; } selectedFilesDiv.style.display = 'block'; transcribeBtn.disabled = false; filesList.innerHTML = selectedFiles.map((file, index) => `
  • ${file.name} ${formatSize(file.size)}
  • `).join(''); // Add remove handlers filesList.querySelectorAll('.remove-file').forEach(btn => { btn.addEventListener('click', () => { selectedFiles.splice(parseInt(btn.dataset.index), 1); updateFilesList(); }); }); } function addFiles(files) { const audioFiles = Array.from(files).filter(f => f.type.startsWith('audio/') || f.name.match(/\.(mp3|wav|m4a|ogg|flac)$/i) ); selectedFiles = [...selectedFiles, ...audioFiles]; updateFilesList(); } // Drag & Drop events dropZone.addEventListener('click', () => fileInput.click()); dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('drag-over'); }); dropZone.addEventListener('dragleave', () => { dropZone.classList.remove('drag-over'); }); dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.classList.remove('drag-over'); addFiles(e.dataTransfer.files); }); fileInput.addEventListener('change', () => { addFiles(fileInput.files); fileInput.value = ''; }); clearFilesBtn.addEventListener('click', () => { selectedFiles = []; updateFilesList(); }); // Transcribe form submit document.getElementById('transcribe-form').addEventListener('submit', async (e) => { e.preventDefault(); if (selectedFiles.length === 0) return; const button = transcribeBtn; const language = document.getElementById('transcribe-lang').value; const model = document.getElementById('transcribe-model').value; const transcribeProgress = document.getElementById('transcribe-progress'); const transcribeProgressFill = document.getElementById('transcribe-progress-fill'); const transcribeProgressTitle = document.getElementById('transcribe-progress-title'); const transcribeProgressPercent = document.getElementById('transcribe-progress-percent'); const transcribeProgressInfo = document.getElementById('transcribe-progress-info'); const transcribeProgressCurrent = document.getElementById('transcribe-progress-current'); setLoading(button, true); transcribeProgress.style.display = 'block'; transcribeProgressFill.style.width = '0%'; transcribeProgressTitle.textContent = 'Uploading and transcribing...'; transcribeProgressPercent.textContent = '0%'; transcribeProgressInfo.textContent = `0/${selectedFiles.length} files`; document.getElementById('transcribe-result').classList.remove('show'); const formData = new FormData(); selectedFiles.forEach(file => formData.append('files', file)); if (language) formData.append('language', language); formData.append('model', model); try { const response = await fetch(`${API_URL}/upload-transcribe`, { method: 'POST', body: formData }); const data = await response.json(); if (!response.ok) throw new Error(data.error || 'Transcription failed'); transcribeProgressFill.style.width = '100%'; transcribeProgressPercent.textContent = '100%'; transcribeProgressTitle.textContent = 'Transcription Complete!'; transcribeProgressInfo.textContent = `${data.successCount}/${data.totalFiles} files`; showResult('transcribe-result', true, `

    Transcription Complete!

    ${data.successCount}/${data.totalFiles} files transcribed

    ${data.results[0]?.text ? `

    Preview (first file):

    ${data.results[0].text.substring(0, 1000)}${data.results[0].text.length > 1000 ? '...' : ''}
    ` : ''} `); selectedFiles = []; updateFilesList(); } catch (error) { transcribeProgress.style.display = 'none'; showResult('transcribe-result', false, `

    Error

    ${error.message}

    `); } finally { setLoading(button, false); } }); // ==================== PROCESS TAB (Download + Transcribe) ==================== const processProgress = document.getElementById('process-progress'); const processProgressFill = document.getElementById('process-progress-fill'); const processProgressTitle = document.getElementById('process-progress-title'); const processProgressPercent = document.getElementById('process-progress-percent'); const processProgressPhase = document.getElementById('process-progress-phase'); const processProgressSpeed = document.getElementById('process-progress-speed'); const processProgressCurrent = document.getElementById('process-progress-current'); const processProgressEta = document.getElementById('process-progress-eta'); function resetProcessProgress() { processProgressFill.style.width = '0%'; processProgressPercent.textContent = '0%'; processProgressTitle.textContent = 'Processing...'; processProgressPhase.textContent = ''; processProgressSpeed.textContent = ''; processProgressCurrent.textContent = ''; processProgressEta.textContent = ''; } document.getElementById('process-form').addEventListener('submit', async (e) => { e.preventDefault(); const button = e.target.querySelector('button[type="submit"]'); const url = document.getElementById('process-url').value; const language = document.getElementById('process-lang').value; const model = document.getElementById('process-model').value; const resultDiv = document.getElementById('process-result'); setLoading(button, true); resetProcessProgress(); processProgress.style.display = 'block'; resultDiv.classList.remove('show'); const params = new URLSearchParams({ url }); if (language) params.append('language', language); params.append('model', model); const eventSource = new EventSource(`${API_URL}/process-stream?${params}`); eventSource.addEventListener('status', (e) => { const data = JSON.parse(e.data); processProgressTitle.textContent = data.message; if (data.phase === 'transcribing') { processProgressPhase.textContent = 'Transcribing'; } }); eventSource.addEventListener('info', (e) => { const data = JSON.parse(e.data); processProgressTitle.textContent = data.totalVideos > 1 ? `Processing playlist: ${data.playlistTitle} (${data.totalVideos} videos)` : `Processing: ${data.title}`; }); eventSource.addEventListener('progress', (e) => { const data = JSON.parse(e.data); processProgressFill.style.width = `${data.percent}%`; processProgressPercent.textContent = `${Math.round(data.percent)}%`; processProgressPhase.textContent = data.phaseLabel || ''; if (data.speed) processProgressSpeed.textContent = data.speed; if (data.title) { processProgressCurrent.innerHTML = `${data.phaseLabel}: ${data.title}`; } if (data.totalVideos > 1) { processProgressCurrent.innerHTML += ` (${data.currentVideo}/${data.totalVideos})`; } }); eventSource.addEventListener('video-complete', (e) => { const data = JSON.parse(e.data); processProgressCurrent.innerHTML = `Downloaded: ${data.title}`; }); eventSource.addEventListener('transcribe-complete', (e) => { const data = JSON.parse(e.data); processProgressCurrent.innerHTML = `Transcribed: ${data.title} (${data.videosCompleted}/${data.totalFiles})`; }); eventSource.addEventListener('complete', (e) => { const data = JSON.parse(e.data); eventSource.close(); processProgressFill.style.width = '100%'; processProgressPercent.textContent = '100%'; processProgressTitle.textContent = 'Processing Complete!'; processProgressPhase.textContent = ''; processProgressEta.textContent = `Total: ${formatTime(data.totalTime)}`; showResult('process-result', true, `

    Processing Complete!

    ${data.playlistTitle ? `

    Playlist: ${data.playlistTitle}

    ` : ''}

    Downloaded: ${data.downloadedCount}/${data.totalVideos}

    Transcribed: ${data.transcribedCount}/${data.totalVideos}

    ${data.results[0]?.text ? `

    Preview (first file):

    ${data.results[0].text.substring(0, 1000)}${data.results[0].text.length > 1000 ? '...' : ''}
    ` : ''} `); setLoading(button, false); }); eventSource.addEventListener('error', (e) => { let errorMsg = 'Processing failed'; try { errorMsg = JSON.parse(e.data).message || errorMsg; } catch {} eventSource.close(); processProgress.style.display = 'none'; showResult('process-result', false, `

    Error

    ${errorMsg}

    `); setLoading(button, false); }); eventSource.onerror = () => { eventSource.close(); processProgress.style.display = 'none'; showResult('process-result', false, `

    Error

    Connection lost

    `); setLoading(button, false); }; }); // ==================== TRANSLATE CHECKBOXES (Transcribe & Process tabs) ==================== // Transcribe tab checkbox const transcribeTranslateCheckbox = document.getElementById('transcribe-translate'); const transcribeTranslateLang = document.getElementById('transcribe-translate-lang'); transcribeTranslateCheckbox.addEventListener('change', () => { transcribeTranslateLang.disabled = !transcribeTranslateCheckbox.checked; }); // Process tab checkbox const processTranslateCheckbox = document.getElementById('process-translate'); const processTranslateLang = document.getElementById('process-translate-lang'); processTranslateCheckbox.addEventListener('change', () => { processTranslateLang.disabled = !processTranslateCheckbox.checked; }); // ==================== TRANSLATE TAB ==================== // Mode switching const translateModeBtns = document.querySelectorAll('.mode-btn'); const translateTextMode = document.getElementById('translate-text-mode'); const translateFileMode = document.getElementById('translate-file-mode'); translateModeBtns.forEach(btn => { btn.addEventListener('click', () => { translateModeBtns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); if (btn.dataset.mode === 'text') { translateTextMode.style.display = 'block'; translateFileMode.style.display = 'none'; } else { translateTextMode.style.display = 'none'; translateFileMode.style.display = 'block'; } }); }); // Text translation form document.getElementById('translate-text-form').addEventListener('submit', async (e) => { e.preventDefault(); const button = document.getElementById('translate-text-btn'); const text = document.getElementById('translate-input').value; const sourceLang = document.getElementById('translate-source').value; const targetLang = document.getElementById('translate-target').value; if (!text.trim()) { showResult('translate-text-result', false, '

    Error

    Please enter text to translate

    '); return; } setLoading(button, true); document.getElementById('translate-text-result').classList.remove('show'); try { const response = await fetch(`${API_URL}/translate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text, targetLang, sourceLang: sourceLang || null }) }); const data = await response.json(); if (!response.ok) throw new Error(data.error || 'Translation failed'); showResult('translate-text-result', true, `

    Translation Complete!

    From: ${data.sourceLanguage} To: ${data.targetLanguage}

    ${data.translatedText}
    `); } catch (error) { showResult('translate-text-result', false, `

    Error

    ${error.message}

    `); } finally { setLoading(button, false); } }); // File translation - Drag & Drop let translateSelectedFiles = []; const translateDropZone = document.getElementById('translate-drop-zone'); const translateFileInput = document.getElementById('translate-file-input'); const translateSelectedFilesDiv = document.getElementById('translate-selected-files'); const translateFilesList = document.getElementById('translate-files-list'); const translateFileBtn = document.getElementById('translate-file-btn'); const translateClearFilesBtn = document.getElementById('translate-clear-files'); function updateTranslateFilesList() { if (translateSelectedFiles.length === 0) { translateSelectedFilesDiv.style.display = 'none'; translateFileBtn.disabled = true; return; } translateSelectedFilesDiv.style.display = 'block'; translateFileBtn.disabled = false; translateFilesList.innerHTML = translateSelectedFiles.map((file, index) => `
  • ${file.name} ${formatSize(file.size)}
  • `).join(''); translateFilesList.querySelectorAll('.remove-file').forEach(btn => { btn.addEventListener('click', () => { translateSelectedFiles.splice(parseInt(btn.dataset.index), 1); updateTranslateFilesList(); }); }); } function addTranslateFiles(files) { const textFiles = Array.from(files).filter(f => f.type === 'text/plain' || f.name.endsWith('.txt') ); translateSelectedFiles = [...translateSelectedFiles, ...textFiles]; updateTranslateFilesList(); } translateDropZone.addEventListener('click', () => translateFileInput.click()); translateDropZone.addEventListener('dragover', (e) => { e.preventDefault(); translateDropZone.classList.add('drag-over'); }); translateDropZone.addEventListener('dragleave', () => { translateDropZone.classList.remove('drag-over'); }); translateDropZone.addEventListener('drop', (e) => { e.preventDefault(); translateDropZone.classList.remove('drag-over'); addTranslateFiles(e.dataTransfer.files); }); translateFileInput.addEventListener('change', () => { addTranslateFiles(translateFileInput.files); translateFileInput.value = ''; }); translateClearFilesBtn.addEventListener('click', () => { translateSelectedFiles = []; updateTranslateFilesList(); }); // File translation form submit document.getElementById('translate-file-form').addEventListener('submit', async (e) => { e.preventDefault(); if (translateSelectedFiles.length === 0) return; const button = translateFileBtn; const sourceLang = document.getElementById('translate-file-source').value; const targetLang = document.getElementById('translate-file-target').value; setLoading(button, true); document.getElementById('translate-file-result').classList.remove('show'); const formData = new FormData(); translateSelectedFiles.forEach(file => formData.append('files', file)); formData.append('targetLang', targetLang); if (sourceLang) formData.append('sourceLang', sourceLang); try { const response = await fetch(`${API_URL}/translate-file`, { method: 'POST', body: formData }); const data = await response.json(); if (!response.ok) throw new Error(data.error || 'Translation failed'); showResult('translate-file-result', true, `

    Translation Complete!

    ${data.successCount}/${data.totalFiles} files translated

    ${data.results[0]?.translatedText ? `

    Preview (first file):

    ${data.results[0].translatedText.substring(0, 1000)}${data.results[0].translatedText.length > 1000 ? '...' : ''}
    ` : ''} `); translateSelectedFiles = []; updateTranslateFilesList(); } catch (error) { showResult('translate-file-result', false, `

    Error

    ${error.message}

    `); } finally { setLoading(button, false); } });