Spaces:
Running
Running
| document.addEventListener('DOMContentLoaded', () => { | |
| // --- Beta Banner Logic --- | |
| const betaBanner = document.getElementById('beta-banner'); | |
| const closeBetaBannerBtn = document.getElementById('close-beta-banner'); | |
| if (betaBanner && closeBetaBannerBtn) { | |
| if (localStorage.getItem('betaBannerDismissed') !== 'true') { | |
| betaBanner.style.display = 'block'; | |
| } | |
| closeBetaBannerBtn.addEventListener('click', () => { | |
| betaBanner.style.display = 'none'; | |
| localStorage.setItem('betaBannerDismissed', 'true'); | |
| }); | |
| } | |
| // --- End Beta Banner Logic --- | |
| const form = document.getElementById('transcribe-form'); | |
| const apiKeyInput = document.getElementById('api-key-input'); | |
| const audioFileInput = document.getElementById('audio-file-input'); | |
| const audioDropZone = document.getElementById('audio-drop-zone'); | |
| const audioFileNameEl = document.getElementById('audio-file-name'); | |
| const modelSelect = document.getElementById('model-select'); | |
| const modelCustomInput = document.getElementById('model-custom-input'); | |
| const formatToggleButtons = document.querySelectorAll('.format-toggle-btn'); | |
| const submitButton = document.getElementById('submit-button'); | |
| const statusContainer = document.getElementById('status-container'); | |
| const statusMessage = document.getElementById('status-message'); | |
| const progressBar = document.getElementById('progress-bar'); | |
| const resultsSection = document.getElementById('results-section'); | |
| const downloadSrtButton = document.getElementById('download-srt-button'); | |
| const downloadTxtButton = document.getElementById('download-txt-button'); | |
| const copyButton = document.getElementById('copy-button'); | |
| const resultsOutput = document.getElementById('results-output'); | |
| const resultsViewToggle = document.getElementById('results-view-toggle'); | |
| const viewSrtBtn = document.getElementById('view-srt-btn'); | |
| const viewTxtBtn = document.getElementById('view-txt-btn'); | |
| const checkScrollbar = () => { | |
| if (resultsOutput.scrollHeight > resultsOutput.clientHeight) { | |
| copyButton.classList.add('has-scrollbar'); | |
| } else { | |
| copyButton.classList.remove('has-scrollbar'); | |
| } | |
| }; | |
| const scrollObserver = new ResizeObserver(checkScrollbar); | |
| scrollObserver.observe(resultsOutput); | |
| let audioFile = null; | |
| let srtContent = ''; | |
| let textContent = ''; | |
| let currentMode = 'plain_text'; | |
| function checkInputs() { | |
| submitButton.disabled = !(apiKeyInput.value.trim() && audioFile); | |
| } | |
| function updateStatus(message, percent, isError = false) { | |
| statusContainer.style.display = 'block'; | |
| statusMessage.textContent = message; | |
| progressBar.style.width = `${percent}%`; | |
| progressBar.classList.toggle('error', isError); | |
| statusMessage.className = isError ? 'error' : 'loading'; | |
| } | |
| function setFormEnabled(enabled) { | |
| submitButton.disabled = !enabled; | |
| audioFileInput.disabled = !enabled; | |
| if (enabled) { | |
| audioDropZone.classList.remove('disabled'); | |
| checkInputs(); | |
| } else { | |
| audioDropZone.classList.add('disabled'); | |
| } | |
| } | |
| function updateResultsView(mode) { | |
| resultsViewToggle.style.display = 'flex'; | |
| if (mode === 'srt') { | |
| downloadSrtButton.classList.add('btn-primary'); | |
| downloadSrtButton.classList.remove('btn-secondary'); | |
| downloadTxtButton.classList.add('btn-secondary'); | |
| downloadTxtButton.classList.remove('btn-primary'); | |
| } else { // plain_text | |
| downloadTxtButton.classList.add('btn-primary'); | |
| downloadTxtButton.classList.remove('btn-secondary'); | |
| downloadSrtButton.classList.add('btn-secondary'); | |
| downloadSrtButton.classList.remove('btn-primary'); | |
| } | |
| } | |
| function resetUI() { | |
| setFormEnabled(true); | |
| statusContainer.style.display = 'none'; | |
| resultsSection.style.display = 'none'; | |
| resultsViewToggle.style.display = 'none'; | |
| updateStatus("", 0); | |
| } | |
| function loadApiKey() { | |
| const savedKey = localStorage.getItem('geminiApiKey'); | |
| if (savedKey) { apiKeyInput.value = savedKey; checkInputs(); } | |
| } | |
| function saveApiKey() { | |
| localStorage.setItem('geminiApiKey', apiKeyInput.value); | |
| } | |
| loadApiKey(); | |
| apiKeyInput.addEventListener('input', () => { saveApiKey(); checkInputs(); }); | |
| audioFileInput.addEventListener('change', (e) => { | |
| audioFile = e.target.files[0]; | |
| audioFileNameEl.textContent = audioFile ? `קובץ: ${audioFile.name}` : ''; | |
| checkInputs(); | |
| }); | |
| modelSelect.addEventListener('change', () => { | |
| modelCustomInput.style.display = (modelSelect.value === 'custom') ? 'block' : 'none'; | |
| }); | |
| formatToggleButtons.forEach(button => { | |
| button.addEventListener('click', () => { | |
| formatToggleButtons.forEach(btn => btn.classList.remove('active')); | |
| button.classList.add('active'); | |
| currentMode = button.dataset.format; | |
| }); | |
| }); | |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
| audioDropZone.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); }); | |
| }); | |
| audioDropZone.addEventListener('dragover', () => { if (!audioFileInput.disabled) audioDropZone.classList.add('drag-over') }); | |
| audioDropZone.addEventListener('dragleave', () => audioDropZone.classList.remove('drag-over')); | |
| audioDropZone.addEventListener('drop', e => { | |
| if (audioFileInput.disabled) return; | |
| audioDropZone.classList.remove('drag-over'); | |
| const droppedFile = e.dataTransfer.files[0]; | |
| if (droppedFile && (droppedFile.type.startsWith('audio/') || droppedFile.type.startsWith('video/'))) { | |
| audioFile = droppedFile; | |
| audioFileInput.files = e.dataTransfer.files; | |
| audioFileNameEl.textContent = `קובץ: ${audioFile.name}`; | |
| } else { | |
| audioFile = null; | |
| audioFileNameEl.textContent = 'יש לבחור קובץ שמע או וידאו'; | |
| } | |
| checkInputs(); | |
| }); | |
| copyButton.addEventListener('click', () => { | |
| if (!resultsOutput.value) return; | |
| navigator.clipboard.writeText(resultsOutput.value).then(() => { | |
| const originalText = copyButton.innerHTML; | |
| copyButton.innerHTML = `<span class="material-symbols-outlined">check</span>`; | |
| setTimeout(() => { | |
| copyButton.innerHTML = originalText; | |
| }, 2000); | |
| }).catch(err => { | |
| console.error('Failed to copy text: ', err); | |
| alert('ההעתקה נכשלה'); | |
| }); | |
| }); | |
| function downloadContent(content, extension) { | |
| const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); | |
| const url = URL.createObjectURL(blob); | |
| const originalFileName = audioFile ? audioFile.name.split('.').slice(0, -1).join('.') : 'download'; | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `${originalFileName}_transcript.${extension}`; | |
| document.body.appendChild(a); a.click(); document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| } | |
| downloadSrtButton.addEventListener('click', async () => { | |
| if (currentMode === 'srt' || srtContent) { | |
| if (!srtContent) { alert('אין תוכן להורדה.'); return; } | |
| downloadContent(srtContent, 'srt'); | |
| } else { | |
| if (!textContent) { alert('אין תוכן תמלול להמרה.'); return; } | |
| try { | |
| const response = await fetch('/convert-to-srt', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ text_data: textContent }) | |
| }); | |
| if (!response.ok) throw new Error((await response.json()).detail || 'שגיאה בהמרת הקובץ'); | |
| const data = await response.json(); | |
| if (data.srt) { | |
| downloadContent(data.srt, 'srt'); | |
| } else { | |
| throw new Error('השרת לא החזיר תוכן SRT תקני.'); | |
| } | |
| } catch (error) { | |
| alert(`אירעה שגיאה בהמרת הקובץ: ${error.message}`); | |
| } | |
| } | |
| }); | |
| downloadTxtButton.addEventListener('click', async () => { | |
| if (currentMode === 'plain_text' || textContent) { | |
| if (!textContent) { alert('אין תוכן להורדה.'); return; } | |
| downloadContent(textContent.replace(/<!-- DURATION_MS:\d+ -->\s*$/, '').trim(), 'txt'); | |
| } else { | |
| if (!srtContent) { alert('אין תוכן תמלול להמרה.'); return; } | |
| try { | |
| const response = await fetch('/convert-to-text', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ srt_data: srtContent }) | |
| }); | |
| if (!response.ok) throw new Error((await response.json()).detail || 'שגיאה בהמרת הקובץ'); | |
| const data = await response.json(); | |
| downloadContent(data.text, 'txt'); | |
| } catch (error) { alert(`אירעה שגיאה בהמרת הקובץ: ${error.message}`); } | |
| } | |
| }); | |
| viewSrtBtn.addEventListener('click', async () => { | |
| if (viewSrtBtn.classList.contains('active')) return; | |
| viewSrtBtn.classList.add('active'); viewTxtBtn.classList.remove('active'); | |
| if (srtContent) { | |
| resultsOutput.value = srtContent; | |
| checkScrollbar(); | |
| return; | |
| } | |
| if (!textContent) { | |
| resultsOutput.value = ''; | |
| checkScrollbar(); | |
| return; | |
| } | |
| resultsOutput.value = 'ממיר לתצוגת כתוביות...'; | |
| checkScrollbar(); | |
| try { | |
| const response = await fetch('/convert-to-srt', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ text_data: textContent }) | |
| }); | |
| if (!response.ok) throw new Error((await response.json()).detail || 'שגיאה בהמרת הקובץ'); | |
| const data = await response.json(); | |
| srtContent = data.srt; | |
| resultsOutput.value = srtContent; | |
| } catch (error) { | |
| resultsOutput.value = `אירעה שגיאה בהמרת התצוגה: ${error.message}`; | |
| } | |
| checkScrollbar(); | |
| }); | |
| viewTxtBtn.addEventListener('click', async () => { | |
| if (viewTxtBtn.classList.contains('active')) return; | |
| viewTxtBtn.classList.add('active'); viewSrtBtn.classList.remove('active'); | |
| if (textContent) { | |
| resultsOutput.value = textContent.replace(/<!-- DURATION_MS:\d+ -->\s*$/, '').trim(); | |
| checkScrollbar(); | |
| return; | |
| } | |
| if (!srtContent) { | |
| resultsOutput.value = ''; | |
| checkScrollbar(); | |
| return; | |
| } | |
| resultsOutput.value = 'ממיר לתצוגת טקסט...'; | |
| checkScrollbar(); | |
| try { | |
| const response = await fetch('/convert-to-text', { | |
| method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ srt_data: srtContent }) | |
| }); | |
| if (!response.ok) throw new Error((await response.json()).detail || 'שגיאה בהמרת הקובץ'); | |
| const data = await response.json(); | |
| textContent = data.text; | |
| resultsOutput.value = textContent; | |
| } catch (error) { | |
| resultsOutput.value = `אירעה שגיאה בהמרת התצוגה: ${error.message}`; | |
| } | |
| checkScrollbar(); | |
| }); | |
| const userPromptInput = document.getElementById('user-prompt-input'); | |
| function autoGrowTextarea() { | |
| this.style.height = 'auto'; | |
| this.style.height = (this.scrollHeight) + 'px'; | |
| } | |
| userPromptInput.addEventListener('input', autoGrowTextarea, false); | |
| form.addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| if (submitButton.disabled) return; | |
| resultsSection.style.display = 'none'; | |
| setFormEnabled(false); | |
| updateStatus('מתחיל את התהליך...', 0); | |
| let modelName = modelSelect.value === 'custom' ? modelCustomInput.value.trim() : modelSelect.value; | |
| if (modelSelect.value === 'custom' && !modelName) { | |
| updateStatus('יש להזין שם מודל מותאם אישית', 100, true); | |
| setFormEnabled(true); | |
| return; | |
| } | |
| const formData = new FormData(); | |
| formData.append('api_key', apiKeyInput.value); | |
| formData.append('model_name', modelName); | |
| formData.append('output_format', currentMode); | |
| formData.append('user_prompt', document.getElementById('user-prompt-input').value); | |
| formData.append('audio_file', audioFile); | |
| try { | |
| const response = await fetch('/transcribe-stream', { method: 'POST', body: formData }); | |
| if (!response.ok) throw new Error((await response.json()).detail || `שגיאת שרת: ${response.status}`); | |
| const reader = response.body.getReader(); | |
| const decoder = new TextDecoder(); | |
| let buffer = ''; | |
| while (true) { | |
| const { value, done } = await reader.read(); | |
| if (done) break; | |
| buffer += decoder.decode(value, { stream: true }); | |
| const events = buffer.split('\n\n'); | |
| buffer = events.pop(); | |
| for (const eventStr of events) { | |
| if (!eventStr.trim()) continue; | |
| const event = JSON.parse(eventStr); | |
| if (event.type === 'progress') { | |
| updateStatus(event.message, event.percent); | |
| } else if (event.type === 'result') { | |
| updateStatus(event.message, event.percent); | |
| resultsSection.style.display = 'block'; | |
| statusContainer.style.display = 'none'; | |
| if (currentMode === 'srt') { | |
| srtContent = event.data; | |
| textContent = ''; | |
| resultsOutput.value = srtContent; | |
| updateResultsView('srt'); | |
| viewSrtBtn.classList.add('active'); | |
| viewTxtBtn.classList.remove('active'); | |
| } else { | |
| textContent = event.data; | |
| srtContent = ''; | |
| const displayContent = textContent.replace(/<!-- DURATION_MS:\d+ -->\s*$/, '').trim(); | |
| resultsOutput.value = displayContent; | |
| updateResultsView('plain_text'); | |
| viewTxtBtn.classList.add('active'); | |
| viewSrtBtn.classList.remove('active'); | |
| } | |
| checkScrollbar(); | |
| } else if (event.type === 'error') { | |
| throw new Error(event.message); | |
| } | |
| } | |
| } | |
| setFormEnabled(true); | |
| } catch (error) { | |
| console.error('Transcription error:', error); | |
| updateStatus(`אירעה שגיאה: ${error.message}`, 100, true); | |
| setFormEnabled(true); | |
| } | |
| }); | |
| }); |