Add recording diagnostics and refresh music list on play

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-06 01:00:41 -07:00
parent 0412f4487f
commit b0643d6082
2 changed files with 145 additions and 13 deletions

View File

@@ -16,6 +16,21 @@ let tracks = [];
let sounds = [];
// --- Safe JSON parsing ---
async function safeFetch(url, options = {}) {
const res = await fetch(url, options);
if (!res.ok) {
const text = await res.text();
let detail = text;
try { detail = JSON.parse(text).detail || text; } catch {}
throw new Error(detail);
}
const text = await res.text();
if (!text) return {};
return JSON.parse(text);
}
// --- Init ---
document.addEventListener('DOMContentLoaded', async () => {
console.log('AI Radio Show initializing...');
@@ -108,6 +123,15 @@ function initEventListeners() {
log('Real caller disconnected');
});
// AI caller hangup (small button in AI caller panel)
document.getElementById('hangup-ai-btn')?.addEventListener('click', hangup);
// AI respond button (manual trigger)
document.getElementById('ai-respond-btn')?.addEventListener('click', triggerAiRespond);
// Start conversation update polling
startConversationPolling();
// AI respond mode toggle
document.getElementById('mode-manual')?.addEventListener('click', () => {
document.getElementById('mode-manual')?.classList.add('active');
@@ -123,7 +147,6 @@ function initEventListeners() {
document.getElementById('mode-auto')?.addEventListener('click', () => {
document.getElementById('mode-auto')?.classList.add('active');
document.getElementById('mode-manual')?.classList.remove('active');
document.getElementById('ai-respond-btn')?.classList.add('hidden');
fetch('/api/session/ai-mode', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -362,6 +385,7 @@ async function newSession() {
}
await fetch('/api/session/reset', { method: 'POST' });
conversationSince = 0;
// Hide caller background
const bgEl = document.getElementById('caller-background');
@@ -434,8 +458,7 @@ async function stopRecording() {
try {
// Stop recording and get transcription
const res = await fetch('/api/record/stop', { method: 'POST' });
const data = await res.json();
const data = await safeFetch('/api/record/stop', { method: 'POST' });
if (!data.text) {
log('(No speech detected)');
@@ -449,12 +472,11 @@ async function stopRecording() {
// Chat
showStatus(`${currentCaller.name} is thinking...`);
const chatRes = await fetch('/api/chat', {
const chatData = await safeFetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: data.text })
});
const chatData = await chatRes.json();
addMessage(chatData.caller, chatData.text);
@@ -462,7 +484,7 @@ async function stopRecording() {
if (chatData.text && chatData.text.trim()) {
showStatus(`${currentCaller.name} is speaking...`);
await fetch('/api/tts', {
await safeFetch('/api/tts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -496,12 +518,11 @@ async function sendTypedMessage() {
try {
showStatus(`${currentCaller.name} is thinking...`);
const chatRes = await fetch('/api/chat', {
const chatData = await safeFetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
});
const chatData = await chatRes.json();
addMessage(chatData.caller, chatData.text);
@@ -509,7 +530,7 @@ async function sendTypedMessage() {
if (chatData.text && chatData.text.trim()) {
showStatus(`${currentCaller.name} is speaking...`);
await fetch('/api/tts', {
await safeFetch('/api/tts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -538,6 +559,8 @@ async function loadMusic() {
const select = document.getElementById('track-select');
if (!select) return;
const previousValue = select.value;
select.innerHTML = '';
tracks.forEach((track, i) => {
@@ -546,6 +569,12 @@ async function loadMusic() {
option.textContent = track.name;
select.appendChild(option);
});
// Restore previous selection if it still exists
if (previousValue && [...select.options].some(o => o.value === previousValue)) {
select.value = previousValue;
}
console.log('Loaded', tracks.length, 'tracks');
} catch (err) {
console.error('loadMusic error:', err);
@@ -554,6 +583,7 @@ async function loadMusic() {
async function playMusic() {
await loadMusic();
const select = document.getElementById('track-select');
const track = select?.value;
if (!track) return;
@@ -894,6 +924,19 @@ async function takeCall(callerId) {
if (data.status === 'on_air') {
showRealCaller(data.caller);
log(`${data.caller.phone} is on air — Channel ${data.caller.channel}`);
// Auto-select an AI caller if none is active
if (!currentCaller) {
const callerBtns = document.querySelectorAll('.caller-btn');
if (callerBtns.length > 0) {
const randomIdx = Math.floor(Math.random() * callerBtns.length);
const btn = callerBtns[randomIdx];
const key = btn.dataset.key;
const name = btn.textContent;
log(`Auto-selecting ${name} as AI caller`);
await startCall(key, name);
}
}
}
} catch (err) {
log('Failed to take call: ' + err.message);
@@ -962,6 +1005,66 @@ function hideRealCaller() {
}
// --- AI Respond (manual trigger) ---
async function triggerAiRespond() {
if (!currentCaller) {
log('No AI caller active — click a caller first');
return;
}
const btn = document.getElementById('ai-respond-btn');
if (btn) { btn.disabled = true; btn.textContent = 'Thinking...'; }
showStatus(`${currentCaller.name} is thinking...`);
try {
const data = await safeFetch('/api/ai-respond', { method: 'POST' });
if (data.text) {
addMessage(data.caller, data.text);
showStatus(`${data.caller} is speaking...`);
const duration = data.text.length * 60;
setTimeout(hideStatus, Math.min(duration, 15000));
}
} catch (err) {
log('AI respond error: ' + err.message);
}
if (btn) { btn.disabled = false; btn.textContent = 'Let them respond'; }
}
// --- Conversation Update Polling ---
let conversationSince = 0;
function startConversationPolling() {
setInterval(fetchConversationUpdates, 1000);
}
async function fetchConversationUpdates() {
try {
const res = await fetch(`/api/conversation/updates?since=${conversationSince}`);
const data = await res.json();
if (data.messages && data.messages.length > 0) {
for (const msg of data.messages) {
conversationSince = msg.id + 1;
if (msg.type === 'caller_disconnected') {
hideRealCaller();
log(`${msg.phone} disconnected (${msg.reason})`);
} else if (msg.type === 'chat') {
addMessage(msg.sender, msg.text);
} else if (msg.type === 'ai_status') {
showStatus(msg.text);
} else if (msg.type === 'ai_done') {
hideStatus();
} else if (msg.type === 'caller_queued') {
// Queue poll will pick this up, just ensure it refreshes
fetchQueue();
}
}
}
} catch (err) {}
}
async function stopServer() {
if (!confirm('Stop the server? You will need to restart it manually.')) return;