// ==================== API CONFIGURATION ==================== const API_URL = ''; const TOKEN_STORAGE_KEY = 'video_transcriptor_api_token'; // Token management let apiToken = localStorage.getItem(TOKEN_STORAGE_KEY) || ''; // Helper: Get API headers with token function getHeaders(additionalHeaders = {}) { const headers = { ...additionalHeaders }; if (apiToken) { headers['X-API-Key'] = apiToken; } return headers; } // Helper: Authenticated fetch async function apiFetch(url, options = {}) { const defaultHeaders = getHeaders(options.headers || {}); const response = await fetch(url, { ...options, headers: defaultHeaders, }); // Check for auth errors if (response.status === 401 || response.status === 403) { showResult('api-error', false, `

⚠️ Authentication Error

${response.status === 401 ? 'API token required' : 'Invalid API token'}

Please configure your API token in the configuration panel above.

`); throw new Error('Authentication failed'); } return response; } // Helper: Format YouTube enhanced errors function formatYouTubeError(errorData) { // Check if it's an enhanced YouTube bot detection error if (errorData.error === 'YouTube Bot Detection' || errorData.solution) { return `

🚫 ${errorData.error || 'YouTube Error'}

${errorData.message}

${errorData.reason ? `
Technical Details
${errorData.reason}
` : ''}

💡 Solution

${errorData.solution.quick}

    ${errorData.solution.steps.map(step => `
  1. ${step}
  2. `).join('')}
${errorData.solution.alternative ? `

Alternative: ${errorData.solution.alternative}

` : ''} ${errorData.solution.documentation ? `

📚 ${errorData.solution.documentation}

` : ''}
${errorData.currentConfig ? `
Current Configuration
` : ''}
`; } // Generic YouTube error if (errorData.error && errorData.error.includes('YouTube')) { return `

❌ ${errorData.error}

${errorData.message}

${errorData.solution ? `

${errorData.solution}

` : ''} `; } // Default error return `

Error

${errorData.message || errorData.error || 'Unknown error'}

`; } // API Configuration Panel const configPanel = document.getElementById('api-config-panel'); const configHeader = document.querySelector('.config-header'); const configContent = document.getElementById('config-content'); const toggleConfigBtn = document.getElementById('toggle-config'); const apiTokenInput = document.getElementById('api-token'); const toggleVisibilityBtn = document.getElementById('toggle-token-visibility'); const apiConfigForm = document.getElementById('api-config-form'); const clearTokenBtn = document.getElementById('clear-token'); const configStatus = document.getElementById('config-status'); // Load token on page load if (apiToken) { apiTokenInput.value = apiToken; updateConnectionStatus(true); } // Toggle configuration panel function toggleConfigPanel() { const isExpanded = configContent.style.display !== 'none'; configContent.style.display = isExpanded ? 'none' : 'block'; configHeader.classList.toggle('expanded', !isExpanded); } configHeader.addEventListener('click', (e) => { if (e.target.closest('.btn-toggle') || e.target === configHeader) { toggleConfigPanel(); } }); // Toggle token visibility toggleVisibilityBtn.addEventListener('click', () => { const isPassword = apiTokenInput.type === 'password'; apiTokenInput.type = isPassword ? 'text' : 'password'; const eyeIcon = document.getElementById('eye-icon'); if (isPassword) { eyeIcon.innerHTML = ` `; } else { eyeIcon.innerHTML = ` `; } }); // Update connection status UI function updateConnectionStatus(connected, message = '') { const indicator = configStatus.querySelector('.status-indicator'); const text = configStatus.querySelector('.status-text'); if (connected) { indicator.className = 'status-indicator status-connected'; text.textContent = message || 'Connected ✓'; } else { indicator.className = 'status-indicator status-disconnected'; text.textContent = message || 'Not configured'; } } // Test API connection async function testApiConnection(token) { try { const tempToken = apiToken; apiToken = token; // Temporarily set token for test const response = await apiFetch(`${API_URL}/health`); const data = await response.json(); if (data.status === 'ok') { apiToken = tempToken; // Restore original return { success: true, message: 'Connected ✓' }; } else { apiToken = tempToken; return { success: false, message: 'API error' }; } } catch (error) { return { success: false, message: error.message }; } } // Save and test token apiConfigForm.addEventListener('submit', async (e) => { e.preventDefault(); const token = apiTokenInput.value.trim(); if (!token) { updateConnectionStatus(false, 'Please enter a token'); return; } const submitBtn = apiConfigForm.querySelector('button[type="submit"]'); const originalText = submitBtn.innerHTML; submitBtn.innerHTML = 'Testing...'; submitBtn.disabled = true; try { const result = await testApiConnection(token); if (result.success) { // Save token localStorage.setItem(TOKEN_STORAGE_KEY, token); apiToken = token; updateConnectionStatus(true, result.message); // Show success message showNotification('✓ API token saved successfully!', 'success'); // Collapse panel after 2 seconds setTimeout(() => { toggleConfigPanel(); }, 2000); } else { updateConnectionStatus(false, 'Connection failed'); showNotification('✗ Failed to connect to API. Check your token.', 'error'); } } catch (error) { updateConnectionStatus(false, 'Connection error'); showNotification('✗ Error testing connection: ' + error.message, 'error'); } finally { submitBtn.innerHTML = originalText; submitBtn.disabled = false; } }); // Clear token clearTokenBtn.addEventListener('click', () => { localStorage.removeItem(TOKEN_STORAGE_KEY); apiToken = ''; apiTokenInput.value = ''; updateConnectionStatus(false, 'Token cleared'); showNotification('Token removed', 'info'); }); // Helper: Show notification toast function showNotification(message, type = 'info') { const toast = document.createElement('div'); toast.className = `notification notification-${type}`; toast.textContent = message; toast.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 1rem 1.5rem; background: ${type === 'success' ? 'rgba(16, 185, 129, 0.9)' : type === 'error' ? 'rgba(239, 68, 68, 0.9)' : 'rgba(59, 130, 246, 0.9)'}; color: white; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); z-index: 10000; animation: slideIn 0.3s ease; font-weight: 500; `; document.body.appendChild(toast); setTimeout(() => { toast.style.animation = 'slideOut 0.3s ease'; setTimeout(() => toast.remove(), 300); }, 3000); } // Add animations to document if (!document.getElementById('notification-styles')) { const style = document.createElement('style'); style.id = 'notification-styles'; style.textContent = ` @keyframes slideIn { from { transform: translateX(400px); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(400px); opacity: 0; } } `; document.head.appendChild(style); } // ==================== TAB SWITCHING ==================== // 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 errorHtml = '

Error

Download failed

'; try { const errorData = JSON.parse(e.data); errorHtml = formatYouTubeError(errorData); } catch { // Keep default error } eventSource.close(); progressContainer.style.display = 'none'; showResult('download-result', false, errorHtml); setLoading(button, false); }); eventSource.onerror = () => { eventSource.close(); progressContainer.style.display = 'none'; showResult('download-result', false, `

Error

Connection lost

`); setLoading(button, false); }; }); // ==================== CONVERT TAB (Video to MP3) ==================== let convertSelectedFiles = []; const convertDropZone = document.getElementById('convert-drop-zone'); const convertFileInput = document.getElementById('convert-file-input'); const convertSelectedFilesDiv = document.getElementById('convert-selected-files'); const convertFilesList = document.getElementById('convert-files-list'); const convertBtn = document.getElementById('convert-btn'); const convertClearFilesBtn = document.getElementById('convert-clear-files'); function updateConvertFilesList() { if (convertSelectedFiles.length === 0) { convertSelectedFilesDiv.style.display = 'none'; convertBtn.disabled = true; return; } convertSelectedFilesDiv.style.display = 'block'; convertBtn.disabled = false; convertFilesList.innerHTML = convertSelectedFiles.map((file, index) => `
  • ${file.name} ${formatSize(file.size)}
  • `).join(''); // Add remove handlers convertFilesList.querySelectorAll('.remove-file').forEach(btn => { btn.addEventListener('click', () => { convertSelectedFiles.splice(parseInt(btn.dataset.index), 1); updateConvertFilesList(); }); }); } function addConvertFiles(files) { const videoAudioFiles = Array.from(files).filter(f => f.type.startsWith('video/') || f.type.startsWith('audio/') || f.name.match(/\.(mp4|avi|mkv|mov|m4a|wav|flac|ogg|webm|wmv|flv)$/i) ); convertSelectedFiles = [...convertSelectedFiles, ...videoAudioFiles]; updateConvertFilesList(); } // Drag & Drop events for convert convertDropZone.addEventListener('click', () => convertFileInput.click()); convertDropZone.addEventListener('dragover', (e) => { e.preventDefault(); convertDropZone.classList.add('drag-over'); }); convertDropZone.addEventListener('dragleave', () => { convertDropZone.classList.remove('drag-over'); }); convertDropZone.addEventListener('drop', (e) => { e.preventDefault(); convertDropZone.classList.remove('drag-over'); addConvertFiles(e.dataTransfer.files); }); convertFileInput.addEventListener('change', () => { addConvertFiles(convertFileInput.files); convertFileInput.value = ''; }); convertClearFilesBtn.addEventListener('click', () => { convertSelectedFiles = []; updateConvertFilesList(); }); // Convert form submit document.getElementById('convert-form').addEventListener('submit', async (e) => { e.preventDefault(); if (convertSelectedFiles.length === 0) return; const button = convertBtn; const bitrate = document.getElementById('convert-bitrate').value; const quality = document.getElementById('convert-quality').value; const convertProgress = document.getElementById('convert-progress'); const convertProgressFill = document.getElementById('convert-progress-fill'); const convertProgressTitle = document.getElementById('convert-progress-title'); const convertProgressPercent = document.getElementById('convert-progress-percent'); const convertProgressInfo = document.getElementById('convert-progress-info'); const convertProgressCurrent = document.getElementById('convert-progress-current'); setLoading(button, true); convertProgress.style.display = 'block'; convertProgressFill.style.width = '0%'; convertProgressTitle.textContent = 'Converting to MP3...'; convertProgressPercent.textContent = '0%'; convertProgressInfo.textContent = `0/${convertSelectedFiles.length} files`; document.getElementById('convert-result').classList.remove('show'); const formData = new FormData(); convertSelectedFiles.forEach(file => formData.append('files', file)); formData.append('bitrate', bitrate); formData.append('quality', quality); try { const response = await fetch(`${API_URL}/convert-to-mp3`, { method: 'POST', body: formData }); const data = await response.json(); if (!response.ok) throw new Error(data.error || 'Conversion failed'); convertProgressFill.style.width = '100%'; convertProgressPercent.textContent = '100%'; convertProgressTitle.textContent = 'Conversion Complete!'; convertProgressInfo.textContent = `${data.successCount}/${data.totalFiles} files`; showResult('convert-result', true, `

    Conversion Complete!

    ${data.successCount}/${data.totalFiles} files converted to MP3

    ${data.results.map(r => r.success ? `
    ${r.fileName} ${r.size}
    Download MP3
    ` : `
    ${r.fileName} ${r.error}
    `).join('')}
    `); // Clear selected files after successful conversion convertSelectedFiles = []; updateConvertFilesList(); } catch (error) { showResult('convert-result', false, `

    Error

    ${error.message}

    `); } finally { setLoading(button, false); setTimeout(() => { convertProgress.style.display = 'none'; }, 1000); } }); // ==================== 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 errorHtml = '

    Error

    Processing failed

    '; try { const errorData = JSON.parse(e.data); errorHtml = formatYouTubeError(errorData); } catch { // Keep default error } eventSource.close(); processProgress.style.display = 'none'; showResult('process-result', false, errorHtml); 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); } }); // ==================== SUMMARIZE TAB ==================== // Mode switching const summarizeModeBtns = document.querySelectorAll('.summarize-mode-selector .mode-btn'); const summarizeTextMode = document.getElementById('summarize-text-mode'); const summarizeFileMode = document.getElementById('summarize-file-mode'); const summarizeLinkMode = document.getElementById('summarize-link-mode'); summarizeModeBtns.forEach(btn => { btn.addEventListener('click', () => { summarizeModeBtns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); summarizeTextMode.style.display = 'none'; summarizeFileMode.style.display = 'none'; if (summarizeLinkMode) summarizeLinkMode.style.display = 'none'; if (btn.dataset.mode === 'text') { summarizeTextMode.style.display = 'block'; } else if (btn.dataset.mode === 'file') { summarizeFileMode.style.display = 'block'; } else if (btn.dataset.mode === 'link' && summarizeLinkMode) { summarizeLinkMode.style.display = 'block'; } }); }); // Text summarization form document.getElementById('summarize-text-form').addEventListener('submit', async (e) => { e.preventDefault(); const button = document.getElementById('summarize-text-btn'); const text = document.getElementById('summarize-input').value; const style = document.getElementById('summarize-style').value; const language = document.getElementById('summarize-language').value; if (!text.trim()) { showResult('summarize-text-result', false, '

    Error

    Please enter text to summarize

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

    Summary Complete!

    Model: ${data.model} | Style: ${data.style} | Chunks: ${data.chunks}

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

    Error

    ${error.message}

    `); } finally { setLoading(button, false); } }); // File summarization - Drag & Drop let summarizeSelectedFiles = []; const summarizeDropZone = document.getElementById('summarize-drop-zone'); const summarizeFileInput = document.getElementById('summarize-file-input'); const summarizeSelectedFilesDiv = document.getElementById('summarize-selected-files'); const summarizeFilesList = document.getElementById('summarize-files-list'); const summarizeFileBtn = document.getElementById('summarize-file-btn'); const summarizeClearFilesBtn = document.getElementById('summarize-clear-files'); function updateSummarizeFilesList() { if (!summarizeSelectedFilesDiv || !summarizeFileBtn || !summarizeFilesList) return; if (summarizeSelectedFiles.length === 0) { summarizeSelectedFilesDiv.style.display = 'none'; summarizeFileBtn.disabled = true; return; } summarizeSelectedFilesDiv.style.display = 'block'; summarizeFileBtn.disabled = false; summarizeFilesList.innerHTML = summarizeSelectedFiles.map((file, index) => `
  • ${file.name} ${formatSize(file.size)}
  • `).join(''); summarizeFilesList.querySelectorAll('.remove-file').forEach(btn => { btn.addEventListener('click', () => { summarizeSelectedFiles.splice(parseInt(btn.dataset.index), 1); updateSummarizeFilesList(); }); }); } function addSummarizeFiles(files) { console.log('Adding files:', files); const textFiles = Array.from(files).filter(f => f.type === 'text/plain' || f.name.endsWith('.txt') ); console.log('Filtered text files:', textFiles); summarizeSelectedFiles = [...summarizeSelectedFiles, ...textFiles]; updateSummarizeFilesList(); } if (summarizeDropZone) { summarizeDropZone.addEventListener('click', () => { console.log('Drop zone clicked'); summarizeFileInput.click(); }); summarizeDropZone.addEventListener('dragover', (e) => { e.preventDefault(); e.stopPropagation(); summarizeDropZone.classList.add('drag-over'); }); summarizeDropZone.addEventListener('dragleave', (e) => { e.preventDefault(); e.stopPropagation(); summarizeDropZone.classList.remove('drag-over'); }); summarizeDropZone.addEventListener('drop', (e) => { e.preventDefault(); e.stopPropagation(); console.log('Files dropped:', e.dataTransfer.files); summarizeDropZone.classList.remove('drag-over'); addSummarizeFiles(e.dataTransfer.files); }); } if (summarizeFileInput) { summarizeFileInput.addEventListener('change', () => { console.log('File input changed:', summarizeFileInput.files); addSummarizeFiles(summarizeFileInput.files); summarizeFileInput.value = ''; }); } if (summarizeClearFilesBtn) { summarizeClearFilesBtn.addEventListener('click', () => { summarizeSelectedFiles = []; updateSummarizeFilesList(); }); } // File summarization form submit document.getElementById('summarize-file-form').addEventListener('submit', async (e) => { e.preventDefault(); if (summarizeSelectedFiles.length === 0) return; const button = summarizeFileBtn; const style = document.getElementById('summarize-file-style').value; const language = document.getElementById('summarize-file-language').value; setLoading(button, true); document.getElementById('summarize-file-result').classList.remove('show'); const formData = new FormData(); summarizeSelectedFiles.forEach(file => formData.append('files', file)); formData.append('style', style); formData.append('language', language); try { const response = await fetch(`${API_URL}/summarize-file`, { method: 'POST', body: formData }); const data = await response.json(); if (!response.ok) throw new Error(data.error || 'Summarization failed'); showResult('summarize-file-result', true, `

    Summarization Complete!

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

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

    Preview (first file):

    ${data.results[0].summary.substring(0, 1000)}${data.results[0].summary.length > 1000 ? '...' : ''}
    ` : ''} `); summarizeSelectedFiles = []; updateSummarizeFilesList(); } catch (error) { showResult('summarize-file-result', false, `

    Error

    ${error.message}

    `); } finally { setLoading(button, false); } }); // ==================== SUMMARIZE LINK MODE (Full Pipeline) ==================== const summarizeLinkForm = document.getElementById('summarize-link-form'); if (summarizeLinkForm) { const linkProgress = document.getElementById('summarize-link-progress'); const linkProgressFill = document.getElementById('summarize-link-progress-fill'); const linkProgressTitle = document.getElementById('summarize-link-progress-title'); const linkProgressPercent = document.getElementById('summarize-link-progress-percent'); const linkProgressPhase = document.getElementById('summarize-link-progress-phase'); const linkProgressSpeed = document.getElementById('summarize-link-progress-speed'); const linkProgressCurrent = document.getElementById('summarize-link-progress-current'); const linkProgressEta = document.getElementById('summarize-link-progress-eta'); function resetLinkProgress() { if (linkProgressFill) linkProgressFill.style.width = '0%'; if (linkProgressPercent) linkProgressPercent.textContent = '0%'; if (linkProgressTitle) linkProgressTitle.textContent = 'Processing...'; if (linkProgressPhase) linkProgressPhase.textContent = ''; if (linkProgressSpeed) linkProgressSpeed.textContent = ''; if (linkProgressCurrent) linkProgressCurrent.textContent = ''; if (linkProgressEta) linkProgressEta.textContent = ''; } summarizeLinkForm.addEventListener('submit', async (e) => { e.preventDefault(); const button = document.getElementById('summarize-link-btn'); const url = document.getElementById('summarize-url').value; const style = document.getElementById('summarize-link-style').value; const language = document.getElementById('summarize-link-language').value; const resultDiv = document.getElementById('summarize-link-result'); setLoading(button, true); resetLinkProgress(); linkProgress.style.display = 'block'; resultDiv.classList.remove('show'); const params = new URLSearchParams({ url, style, language }); const eventSource = new EventSource(`${API_URL}/summarize-stream?${params}`); eventSource.addEventListener('status', (e) => { const data = JSON.parse(e.data); linkProgressTitle.textContent = data.message; if (data.percent !== undefined) { linkProgressFill.style.width = `${data.percent}%`; linkProgressPercent.textContent = `${Math.round(data.percent)}%`; } }); eventSource.addEventListener('info', (e) => { const data = JSON.parse(e.data); linkProgressTitle.textContent = data.totalVideos > 1 ? `Processing playlist: ${data.playlistTitle} (${data.totalVideos} videos)` : `Processing: ${data.title}`; }); eventSource.addEventListener('progress', (e) => { const data = JSON.parse(e.data); linkProgressFill.style.width = `${data.percent}%`; linkProgressPercent.textContent = `${Math.round(data.percent)}%`; linkProgressPhase.textContent = data.phaseLabel || ''; if (data.speed) linkProgressSpeed.textContent = data.speed; if (data.title) { linkProgressCurrent.innerHTML = `${data.phaseLabel}: ${data.title}`; } if (data.totalVideos > 1) { linkProgressCurrent.innerHTML += ` (${data.currentVideo}/${data.totalVideos})`; } }); eventSource.addEventListener('video-complete', (e) => { const data = JSON.parse(e.data); linkProgressCurrent.innerHTML = `Downloaded: ${data.title}`; }); eventSource.addEventListener('transcribe-complete', (e) => { const data = JSON.parse(e.data); linkProgressCurrent.innerHTML = `Transcribed: ${data.title}`; }); eventSource.addEventListener('summarize-complete', (e) => { const data = JSON.parse(e.data); linkProgressCurrent.innerHTML = `Summarized: ${data.title}`; }); eventSource.addEventListener('complete', (e) => { const data = JSON.parse(e.data); eventSource.close(); linkProgressFill.style.width = '100%'; linkProgressPercent.textContent = '100%'; linkProgressTitle.textContent = 'Complete!'; linkProgressPhase.textContent = ''; linkProgressEta.textContent = `Total: ${formatTime(data.totalTime)}`; showResult('summarize-link-result', true, `

    Pipeline Complete!

    ${data.playlistTitle ? `

    Playlist: ${data.playlistTitle}

    ` : ''}

    Downloaded: ${data.downloadedCount}/${data.totalVideos} | Transcribed: ${data.transcribedCount} | Summarized: ${data.summarizedCount}

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

    Summary Preview:

    ${data.results[0].summary.substring(0, 2000)}${data.results[0].summary.length > 2000 ? '...' : ''}
    ` : ''} `); setLoading(button, false); }); eventSource.addEventListener('error', (e) => { let errorHtml = '

    Error

    Processing failed

    '; try { const errorData = JSON.parse(e.data); errorHtml = formatYouTubeError(errorData); } catch { // Keep default error } eventSource.close(); linkProgress.style.display = 'none'; showResult('summarize-link-result', false, errorHtml); setLoading(button, false); }); eventSource.onerror = () => { eventSource.close(); linkProgress.style.display = 'none'; showResult('summarize-link-result', false, `

    Error

    Connection lost

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