VerbaLive / static /js /script.js
koesan's picture
feat: First public release of the VerbaLive app This commit includes the complete application for real-time speech-to-text and translation, with a Flask backend, a JavaScript frontend, and April ASR integration. Includes a Dockerfile for easy deployment on Hugging Face Spaces.
af09686
document.addEventListener('DOMContentLoaded', () => {
const startBtn = document.getElementById('start-mic-btn');
const stopBtn = document.getElementById('stop-mic-btn');
const clearBtn = document.getElementById('clear-btn');
const serviceSelect = document.getElementById('service-select');
const langSelect = document.getElementById('lang-select');
const deeplKeyInput = document.getElementById('deepl-key');
const deeplLabel = document.getElementById('deepl-label');
const englishOutput = document.getElementById('english-output');
const realtimeOutput = document.getElementById('realtime-output');
const detailedOutput = document.getElementById('detailed-output');
const statusLabel = document.getElementById('status-label');
let isRecording = false;
let audioContext;
let processor;
let eventSource = null;
let lastEnglishText = ""; // Tamamlanmış son İngilizce metin
let lastRealtimeText = ""; // Tamamlanmış son çeviri metni
// DeepL API key girişini göster/gizle
serviceSelect.addEventListener('change', (e) => {
if (e.target.value === 'DeepL') {
deeplKeyInput.style.display = 'inline-block';
deeplLabel.style.display = 'inline-block';
} else {
deeplKeyInput.style.display = 'none';
deeplLabel.style.display = 'none';
}
});
// Ses verisini sunucuya gönderme
function sendAudioToServer(data) {
if (!isRecording) return;
fetch('/upload_audio', {
method: 'POST',
body: data,
headers: {
'Content-Type': 'application/octet-stream'
}
}).catch(error => console.error('Error sending audio:', error));
}
// SSE bağlantısını başlat
function startEventSource() {
const params = new URLSearchParams({
target_lang: langSelect.value,
service: serviceSelect.value,
deepl_key: deeplKeyInput.value
});
eventSource = new EventSource(`/stream_results?${params.toString()}`);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
const asrText = data.english;
const asrType = data.type;
const translation = data.translation;
if (asrType === "partial") {
// Anlık İngilizce ve çeviri metnini güncelle
englishOutput.textContent = lastEnglishText + asrText;
realtimeOutput.textContent = lastRealtimeText + translation;
} else if (asrType === "final") {
// Final cümle geldiğinde alt satıra geç ve metinleri sakla
lastEnglishText += asrText + "\n";
lastRealtimeText += translation + "\n";
// Detaylı çeviriye ekle
detailedOutput.textContent += asrText + " --> " + translation + "\n";
// Anlık çeviri kutusunu sıfırla
englishOutput.textContent = lastEnglishText;
realtimeOutput.textContent = lastRealtimeText;
}
englishOutput.scrollTop = englishOutput.scrollHeight;
realtimeOutput.scrollTop = realtimeOutput.scrollHeight;
detailedOutput.scrollTop = detailedOutput.scrollHeight;
};
eventSource.onerror = (err) => {
console.error("EventSource hatası:", err);
statusLabel.textContent = "🔴 Hata: Bağlantı kesildi!";
statusLabel.style.color = "#ff453a";
if (eventSource) eventSource.close();
stopRecording();
};
}
function stopEventSource() {
if (eventSource) {
eventSource.close();
eventSource = null;
}
}
// Olay Dinleyicileri
startBtn.addEventListener('click', async () => {
if (isRecording) return;
isRecording = true;
startBtn.style.display = 'none';
stopBtn.style.display = 'block';
statusLabel.textContent = "🟢 Kaydediyor...";
statusLabel.style.color = "#30d158";
// Metin alanlarını temizle
englishOutput.textContent = "";
realtimeOutput.textContent = "";
detailedOutput.textContent = "";
lastEnglishText = "";
lastRealtimeText = "";
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: { sampleRate: 16000 } });
audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 16000 });
const source = audioContext.createMediaStreamSource(stream);
const bufferSize = 4096;
processor = audioContext.createScriptProcessor(bufferSize, 1, 1);
source.connect(processor);
processor.connect(audioContext.destination);
processor.onaudioprocess = (e) => {
if (!isRecording) return;
const pcmData = e.inputBuffer.getChannelData(0);
const pcm16 = new Int16Array(pcmData.length);
for (let i = 0; i < pcmData.length; i++) {
pcm16[i] = Math.max(-1, Math.min(1, pcmData[i])) * 0x7FFF;
}
sendAudioToServer(pcm16.buffer);
};
await fetch('/start', { method: 'POST' });
startEventSource();
} catch (err) {
console.error('Mikrofon erişimi reddedildi veya hata oluştu:', err);
statusLabel.textContent = "🔴 Hata: Mikrofon erişimi reddedildi!";
statusLabel.style.color = "#ff453a";
isRecording = false;
startBtn.style.display = 'block';
stopBtn.style.display = 'none';
}
});
stopBtn.addEventListener('click', () => {
stopRecording();
});
function stopRecording() {
if (!isRecording) return;
isRecording = false;
if (audioContext) {
audioContext.close();
audioContext = null;
}
startBtn.style.display = 'block';
stopBtn.style.display = 'none';
statusLabel.textContent = "🔴 Hazır - 'Başlat' butonuna basın";
statusLabel.style.color = "#30d158";
stopEventSource();
fetch('/stop', { method: 'POST' });
}
clearBtn.addEventListener('click', () => {
englishOutput.textContent = "";
realtimeOutput.textContent = "";
detailedOutput.textContent = "";
lastEnglishText = "";
lastRealtimeText = "";
statusLabel.textContent = "🟢 Tüm veriler temizlendi.";
});
});