let isCalibrating = false; let calibrationData = []; let calibrationInterval; let audioContext; let analyser; let microphone; document.addEventListener('DOMContentLoaded', () => { // Get all form elements const elements = { openaiKey: document.getElementById('openai-key'), elevenlabsKey: document.getElementById('elevenlabs-key'), whisperModel: document.getElementById('whisper-model'), gpuAcceleration: document.getElementById('gpu-acceleration'), audioThreshold: document.getElementById('audio-threshold'), thresholdValue: document.getElementById('threshold-value'), chunkSize: document.getElementById('chunk-size'), defaultBackend: document.getElementById('default-backend'), textDuration: document.getElementById('text-duration'), textColor: document.getElementById('text-color'), bgColor: document.getElementById('bg-color'), fontFamily: document.getElementById('font-family'), fontSize: document.getElementById('font-size'), fontSizeValue: document.getElementById('font-size-value'), serverPort: document.getElementById('server-port'), // Calibration elements startCalibration: document.getElementById('start-calibration'), stopCalibration: document.getElementById('stop-calibration'), calibrationProgress: document.getElementById('calibration-progress'), ambientLevel: document.getElementById('ambient-level'), calibrationStatus: document.getElementById('calibration-status'), // Action buttons saveSettings: document.getElementById('save-settings'), resetSettings: document.getElementById('reset-settings'), testConnection: document.getElementById('test-connection'), detectGpu: document.getElementById('detect-gpu'), statusMessage: document.getElementById('status-message') }; // Update threshold value display elements.audioThreshold.addEventListener('input', (e) => { elements.thresholdValue.textContent = parseFloat(e.target.value).toFixed(3); }); // Update font size value display elements.fontSize.addEventListener('input', (e) => { elements.fontSizeValue.textContent = e.target.value + 'px'; }); // Load settings on page load loadSettings(); // Save settings elements.saveSettings.addEventListener('click', saveSettings); // Reset settings elements.resetSettings.addEventListener('click', resetSettings); // Test API connections elements.testConnection.addEventListener('click', testApiConnections); // Detect GPU elements.detectGpu.addEventListener('click', detectGpu); // Calibration controls elements.startCalibration.addEventListener('click', startCalibration); elements.stopCalibration.addEventListener('click', stopCalibration); async function loadSettings() { try { const response = await fetch('http://localhost:3000/api/settings'); const settings = await response.json(); // Populate form fields elements.openaiKey.value = settings.openaiApiKey || ''; elements.elevenlabsKey.value = settings.elevenlabsApiKey || ''; elements.whisperModel.value = settings.whisperModel || 'base'; elements.gpuAcceleration.value = settings.gpuAcceleration || 'none'; elements.audioThreshold.value = settings.audioThreshold || 0.01; elements.thresholdValue.textContent = (settings.audioThreshold || 0.01).toFixed(3); elements.chunkSize.value = settings.chunkSize || 2000; elements.defaultBackend.value = settings.selectedBackend || 'whisper-local'; elements.textDuration.value = settings.textDuration || 5; elements.textColor.value = settings.textColor || '#ffffff'; elements.bgColor.value = settings.backgroundColor || '#000000'; elements.fontFamily.value = settings.fontFamily || 'Arial, sans-serif'; elements.fontSize.value = settings.fontSize || 32; elements.fontSizeValue.textContent = (settings.fontSize || 32) + 'px'; elements.serverPort.value = settings.serverPort || 3000; // Update ambient noise level display if (settings.ambientNoiseLevel !== null) { elements.ambientLevel.textContent = `Current: ${settings.ambientNoiseLevel.toFixed(4)}`; } showStatus('Settings loaded successfully', 'success'); } catch (error) { console.error('Error loading settings:', error); showStatus('Error loading settings', 'error'); } } async function saveSettings() { try { const settings = { openaiApiKey: elements.openaiKey.value.trim(), elevenlabsApiKey: elements.elevenlabsKey.value.trim(), whisperModel: elements.whisperModel.value, gpuAcceleration: elements.gpuAcceleration.value, audioThreshold: parseFloat(elements.audioThreshold.value), chunkSize: parseInt(elements.chunkSize.value), selectedBackend: elements.defaultBackend.value, textDuration: parseInt(elements.textDuration.value), textColor: elements.textColor.value, backgroundColor: elements.bgColor.value, fontFamily: elements.fontFamily.value, fontSize: parseInt(elements.fontSize.value), googleFont: elements.fontFamily.options[elements.fontFamily.selectedIndex].getAttribute('data-google'), serverPort: parseInt(elements.serverPort.value) }; const response = await fetch('http://localhost:3000/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(settings) }); if (response.ok) { showStatus('Settings saved successfully! Some changes may require a restart.', 'success'); } else { throw new Error('Failed to save settings'); } } catch (error) { console.error('Error saving settings:', error); showStatus('Error saving settings', 'error'); } } async function resetSettings() { if (!confirm('Are you sure you want to reset all settings to defaults? This cannot be undone.')) { return; } try { const response = await fetch('http://localhost:3000/api/settings/reset', { method: 'POST' }); if (response.ok) { await loadSettings(); showStatus('Settings reset to defaults', 'success'); } else { throw new Error('Failed to reset settings'); } } catch (error) { console.error('Error resetting settings:', error); showStatus('Error resetting settings', 'error'); } } async function detectGpu() { showStatus('Detecting GPU...', 'info'); try { const response = await fetch('http://localhost:3000/api/detect-gpu', { method: 'POST' }); const result = await response.json(); let message = 'GPU Detection Results:\n'; message += `NVIDIA CUDA: ${result.cuda ? 'Available' : 'Not found'}\n`; message += `AMD ROCm: ${result.rocm ? 'Available' : 'Not found'}\n`; message += `Intel OpenVINO: ${result.openvino ? 'Available' : 'Not found'}\n`; if (result.recommended) { message += `\nRecommended: ${result.recommended}`; elements.gpuAcceleration.value = result.recommended; } showStatus(message.replace(/\n/g, '
'), 'success'); } catch (error) { console.error('Error detecting GPU:', error); showStatus('Error detecting GPU', 'error'); } } async function testApiConnections() { showStatus('Testing API connections...', 'info'); try { const response = await fetch('http://localhost:3000/api/test-connections', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ openaiApiKey: elements.openaiKey.value.trim(), elevenlabsApiKey: elements.elevenlabsKey.value.trim() }) }); const results = await response.json(); let message = 'API Connection Test Results:\n'; if (results.openai) { message += `✓ OpenAI: ${results.openai.success ? 'Connected' : 'Failed - ' + results.openai.error}\n`; } if (results.elevenlabs) { message += `✓ ElevenLabs: ${results.elevenlabs.success ? 'Connected' : 'Failed - ' + results.elevenlabs.error}\n`; } showStatus(message.replace(/\n/g, '
'), results.openai?.success || results.elevenlabs?.success ? 'success' : 'error'); } catch (error) { console.error('Error testing connections:', error); showStatus('Error testing API connections', 'error'); } } async function startCalibration() { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); audioContext = new (window.AudioContext || window.webkitAudioContext)(); analyser = audioContext.createAnalyser(); microphone = audioContext.createMediaStreamSource(stream); analyser.fftSize = 512; analyser.smoothingTimeConstant = 0.3; const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); microphone.connect(analyser); isCalibrating = true; calibrationData = []; elements.startCalibration.disabled = true; elements.stopCalibration.disabled = false; elements.calibrationStatus.textContent = 'Calibrating... Stay quiet for 10 seconds.'; let progress = 0; const calibrationDuration = 10000; // 10 seconds const updateInterval = 100; // Update every 100ms const totalUpdates = calibrationDuration / updateInterval; calibrationInterval = setInterval(() => { if (!isCalibrating) return; analyser.getByteTimeDomainData(dataArray); // Calculate RMS let sum = 0; for (let i = 0; i < bufferLength; i++) { const sample = (dataArray[i] - 128) / 128; sum += sample * sample; } const rms = Math.sqrt(sum / bufferLength); calibrationData.push(rms); progress++; const progressPercent = (progress / totalUpdates) * 100; elements.calibrationProgress.style.width = `${progressPercent}%`; if (progress >= totalUpdates) { finishCalibration(); } }, updateInterval); } catch (error) { console.error('Error starting calibration:', error); showStatus('Error accessing microphone for calibration', 'error'); stopCalibration(); } } function stopCalibration() { isCalibrating = false; if (calibrationInterval) { clearInterval(calibrationInterval); calibrationInterval = null; } if (audioContext) { audioContext.close(); audioContext = null; } elements.startCalibration.disabled = false; elements.stopCalibration.disabled = true; elements.calibrationProgress.style.width = '0%'; elements.calibrationStatus.textContent = 'Calibration stopped.'; } async function finishCalibration() { if (calibrationData.length === 0) { stopCalibration(); return; } // Calculate average ambient noise level const avgLevel = calibrationData.reduce((a, b) => a + b, 0) / calibrationData.length; // Set threshold slightly above ambient level const suggestedThreshold = Math.max(avgLevel * 3, 0.005); try { const response = await fetch('http://localhost:3000/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ambientNoiseLevel: avgLevel, audioThreshold: suggestedThreshold }) }); if (response.ok) { elements.ambientLevel.textContent = `Current: ${avgLevel.toFixed(4)}`; elements.audioThreshold.value = suggestedThreshold; elements.thresholdValue.textContent = suggestedThreshold.toFixed(3); elements.calibrationStatus.textContent = `Calibration complete! New threshold: ${suggestedThreshold.toFixed(3)}`; showStatus('Ambient noise calibration completed successfully', 'success'); } } catch (error) { console.error('Error saving calibration:', error); showStatus('Error saving calibration results', 'error'); } stopCalibration(); } function showStatus(message, type) { elements.statusMessage.textContent = message; elements.statusMessage.innerHTML = message; // Allow HTML for test results elements.statusMessage.className = `status-${type}`; elements.statusMessage.style.display = 'block'; // Auto-hide after 5 seconds setTimeout(() => { elements.statusMessage.style.display = 'none'; }, 5000); } }); // Add status message styles const style = document.createElement('style'); style.textContent = ` .status-success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .status-error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .status-info { background: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; } `; document.head.appendChild(style);