Archived
353 lines
13 KiB
JavaScript
353 lines
13 KiB
JavaScript
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, '<br>'), '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, '<br>'), 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); |