Feat: restore ElevenLabs Adam voice for read aloud with browser TTS fallback; add client-side error logging
This commit is contained in:
parent
e74c0ccb0c
commit
93338567e8
3 changed files with 78 additions and 10 deletions
|
|
@ -1,6 +1,6 @@
|
|||
services:
|
||||
pediatric-scribe:
|
||||
image: danielonyejesi/pediatric-ai-scribe-v3:v1.8
|
||||
image: danielonyejesi/pediatric-ai-scribe-v3:v1.9
|
||||
ports:
|
||||
- "3552:3000"
|
||||
env_file:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,28 @@
|
|||
// APP.JS — Core utilities, tabs, model selector, helpers
|
||||
// ============================================================
|
||||
|
||||
// ── Client-side error logging ──────────────────────────────
|
||||
(function() {
|
||||
function sendError(data) {
|
||||
try {
|
||||
var token = localStorage.getItem('ped_scribe_token') || '';
|
||||
navigator.sendBeacon('/api/logs/client-error', new Blob(
|
||||
[JSON.stringify(data)], { type: 'application/json' }
|
||||
));
|
||||
} catch(e) {}
|
||||
}
|
||||
window.onerror = function(msg, src, line, col, err) {
|
||||
// Ignore errors from browser extensions
|
||||
if (src && src.indexOf('moz-extension') !== -1) return;
|
||||
if (src && src.indexOf('chrome-extension') !== -1) return;
|
||||
sendError({ type: 'uncaught', message: String(msg), source: src, line: line, col: col, stack: err && err.stack });
|
||||
};
|
||||
window.addEventListener('unhandledrejection', function(e) {
|
||||
var msg = e.reason ? (e.reason.message || String(e.reason)) : 'Unhandled promise rejection';
|
||||
sendError({ type: 'unhandledrejection', message: msg, stack: e.reason && e.reason.stack });
|
||||
});
|
||||
})();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// --- TAB NAVIGATION ---
|
||||
|
|
@ -179,17 +201,44 @@ function speakText(elementId) {
|
|||
if (!el) return;
|
||||
var text = (el.innerText || el.textContent).trim();
|
||||
if (!text) { showToast('Nothing to read', 'error'); return; }
|
||||
if (!('speechSynthesis' in window)) { showToast('TTS not supported', 'error'); return; }
|
||||
window.speechSynthesis.cancel();
|
||||
var utter = new SpeechSynthesisUtterance(text);
|
||||
utter.rate = 0.9;
|
||||
utter.onend = function() { stopReading(); };
|
||||
utter.onerror = function() { stopReading(); };
|
||||
|
||||
currentlyReadingId = elementId;
|
||||
var btn = findReadButton(elementId);
|
||||
if (btn) { btn.classList.add('btn-reading'); btn.innerHTML = '<i class="fas fa-stop"></i> Stop'; }
|
||||
window.speechSynthesis.speak(utter);
|
||||
showToast('Reading — click Stop to end', 'info');
|
||||
if (btn) { btn.classList.add('btn-reading'); btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Loading...'; }
|
||||
|
||||
fetch('/api/text-to-speech', {
|
||||
method: 'POST',
|
||||
headers: window.getAuthHeaders ? window.getAuthHeaders() : { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: text })
|
||||
})
|
||||
.then(function(r) {
|
||||
if (!r.ok) throw new Error('TTS request failed (' + r.status + ')');
|
||||
return r.blob();
|
||||
})
|
||||
.then(function(blob) {
|
||||
var url = URL.createObjectURL(blob);
|
||||
currentAudio = new Audio(url);
|
||||
currentAudio.onended = function() { URL.revokeObjectURL(url); stopReading(); };
|
||||
currentAudio.onerror = function() { URL.revokeObjectURL(url); stopReading(); showToast('Audio playback error', 'error'); };
|
||||
currentAudio.play();
|
||||
if (btn) { btn.classList.add('btn-reading'); btn.innerHTML = '<i class="fas fa-stop"></i> Stop'; }
|
||||
showToast('Reading aloud (Adam/ElevenLabs)', 'info');
|
||||
})
|
||||
.catch(function(err) {
|
||||
stopReading();
|
||||
// Fallback to browser TTS if ElevenLabs fails
|
||||
if ('speechSynthesis' in window) {
|
||||
var utter = new SpeechSynthesisUtterance(text);
|
||||
utter.rate = 0.9;
|
||||
utter.onend = function() { stopReading(); };
|
||||
currentlyReadingId = elementId;
|
||||
if (btn) { btn.classList.add('btn-reading'); btn.innerHTML = '<i class="fas fa-stop"></i> Stop'; }
|
||||
window.speechSynthesis.speak(utter);
|
||||
showToast('ElevenLabs unavailable — using browser voice', 'info');
|
||||
} else {
|
||||
showToast('Read aloud failed: ' + err.message, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function stopReading() {
|
||||
|
|
|
|||
|
|
@ -32,4 +32,23 @@ router.get('/logs/access', authMiddleware, async function(req, res) {
|
|||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
// Client-side error receiver — no auth required (sendBeacon can't send auth headers)
|
||||
router.post('/logs/client-error', function(req, res) {
|
||||
try {
|
||||
var e = req.body || {};
|
||||
console.error('[CLIENT ERROR]', JSON.stringify({
|
||||
type: e.type,
|
||||
message: e.message,
|
||||
source: e.source,
|
||||
line: e.line,
|
||||
col: e.col,
|
||||
stack: e.stack,
|
||||
ip: req.ip,
|
||||
ua: req.headers['user-agent'],
|
||||
time: new Date().toISOString()
|
||||
}));
|
||||
res.status(204).end();
|
||||
} catch(err) { res.status(204).end(); }
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
|||
Loading…
Reference in a new issue