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:
|
services:
|
||||||
pediatric-scribe:
|
pediatric-scribe:
|
||||||
image: danielonyejesi/pediatric-ai-scribe-v3:v1.8
|
image: danielonyejesi/pediatric-ai-scribe-v3:v1.9
|
||||||
ports:
|
ports:
|
||||||
- "3552:3000"
|
- "3552:3000"
|
||||||
env_file:
|
env_file:
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,28 @@
|
||||||
// APP.JS — Core utilities, tabs, model selector, helpers
|
// 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() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
// --- TAB NAVIGATION ---
|
// --- TAB NAVIGATION ---
|
||||||
|
|
@ -179,17 +201,44 @@ function speakText(elementId) {
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
var text = (el.innerText || el.textContent).trim();
|
var text = (el.innerText || el.textContent).trim();
|
||||||
if (!text) { showToast('Nothing to read', 'error'); return; }
|
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;
|
currentlyReadingId = elementId;
|
||||||
var btn = findReadButton(elementId);
|
var btn = findReadButton(elementId);
|
||||||
if (btn) { btn.classList.add('btn-reading'); btn.innerHTML = '<i class="fas fa-stop"></i> Stop'; }
|
if (btn) { btn.classList.add('btn-reading'); btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Loading...'; }
|
||||||
window.speechSynthesis.speak(utter);
|
|
||||||
showToast('Reading — click Stop to end', 'info');
|
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() {
|
function stopReading() {
|
||||||
|
|
|
||||||
|
|
@ -32,4 +32,23 @@ router.get('/logs/access', authMiddleware, async function(req, res) {
|
||||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
} 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;
|
module.exports = router;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue