Initial FRIDAY Voice Console scaffold
This commit is contained in:
+98
@@ -0,0 +1,98 @@
|
||||
const recordButton = document.getElementById('recordButton');
|
||||
const sendTextButton = document.getElementById('sendTextButton');
|
||||
const textInput = document.getElementById('textInput');
|
||||
const statusEl = document.getElementById('status');
|
||||
const transcriptEl = document.getElementById('transcript');
|
||||
const replyEl = document.getElementById('reply');
|
||||
const audioReply = document.getElementById('audioReply');
|
||||
|
||||
let recorder;
|
||||
let chunks = [];
|
||||
|
||||
function setStatus(text) {
|
||||
statusEl.textContent = text;
|
||||
}
|
||||
|
||||
async function askFriday(text) {
|
||||
setStatus('Asking FRIDAY...');
|
||||
const response = await fetch('/api/ask', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text, speaker_id: null, confidence: 0 })
|
||||
});
|
||||
if (!response.ok) throw new Error(await response.text());
|
||||
const data = await response.json();
|
||||
replyEl.textContent = data.reply;
|
||||
setStatus(`Authorization: ${data.authorization_level}; ${data.reason}`);
|
||||
await speak(data.reply);
|
||||
}
|
||||
|
||||
async function speak(text) {
|
||||
const response = await fetch('/api/speak', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text })
|
||||
});
|
||||
if (!response.ok) return;
|
||||
const blob = await response.blob();
|
||||
audioReply.src = URL.createObjectURL(blob);
|
||||
await audioReply.play().catch(() => {});
|
||||
}
|
||||
|
||||
async function transcribe(blob) {
|
||||
setStatus('Transcribing...');
|
||||
const form = new FormData();
|
||||
form.append('audio', blob, 'utterance.webm');
|
||||
const response = await fetch('/api/transcribe', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'audio/webm' },
|
||||
body: blob
|
||||
});
|
||||
if (!response.ok) throw new Error(await response.text());
|
||||
const data = await response.json();
|
||||
transcriptEl.textContent = data.text;
|
||||
textInput.value = data.text;
|
||||
return data.text;
|
||||
}
|
||||
|
||||
async function startRecording() {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
chunks = [];
|
||||
recorder = new MediaRecorder(stream, { mimeType: 'audio/webm' });
|
||||
recorder.addEventListener('dataavailable', event => {
|
||||
if (event.data.size > 0) chunks.push(event.data);
|
||||
});
|
||||
recorder.addEventListener('stop', async () => {
|
||||
stream.getTracks().forEach(track => track.stop());
|
||||
recordButton.classList.remove('recording');
|
||||
recordButton.textContent = 'Hold to Talk';
|
||||
try {
|
||||
const text = await transcribe(new Blob(chunks, { type: 'audio/webm' }));
|
||||
if (text.trim()) await askFriday(text);
|
||||
} catch (error) {
|
||||
setStatus(`Error: ${error.message}`);
|
||||
}
|
||||
});
|
||||
recorder.start();
|
||||
recordButton.classList.add('recording');
|
||||
recordButton.textContent = 'Listening... release to send';
|
||||
setStatus('Listening...');
|
||||
}
|
||||
|
||||
function stopRecording() {
|
||||
if (recorder && recorder.state === 'recording') recorder.stop();
|
||||
}
|
||||
|
||||
recordButton.addEventListener('pointerdown', startRecording);
|
||||
recordButton.addEventListener('pointerup', stopRecording);
|
||||
recordButton.addEventListener('pointerleave', stopRecording);
|
||||
sendTextButton.addEventListener('click', async () => {
|
||||
const text = textInput.value.trim();
|
||||
if (!text) return;
|
||||
transcriptEl.textContent = text;
|
||||
try {
|
||||
await askFriday(text);
|
||||
} catch (error) {
|
||||
setStatus(`Error: ${error.message}`);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user