v7: fix speech recognition repetition, HTML injection, long-session guard
- Fix word repetition: use sessionFinals pattern so each browser SR session starts fresh; no overlap when recognition auto-restarts - Fix HTML injection / '>' parse error: escape < > & in live transcript innerHTML before inserting speech recognition text - Add 24 MB blob guard: fall back to live SR transcript if audio file is too large for Whisper API (long sessions) - Bump version to 7.0.0, update docker-compose image tag to v7
This commit is contained in:
parent
805d0fea55
commit
e0757310c8
5 changed files with 56 additions and 11 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
services:
|
services:
|
||||||
pediatric-scribe:
|
pediatric-scribe:
|
||||||
image: danielonyejesi/pediatric-ai-scribe-v3:v3.1
|
image: danielonyejesi/pediatric-ai-scribe-v3:v7
|
||||||
ports:
|
ports:
|
||||||
- "3552:3000"
|
- "3552:3000"
|
||||||
env_file:
|
env_file:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "pediatric-ai-scribe",
|
"name": "pediatric-ai-scribe",
|
||||||
"version": "3.1.0",
|
"version": "7.0.0",
|
||||||
"description": "AI-powered pediatric clinical documentation platform",
|
"description": "AI-powered pediatric clinical documentation platform",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -20,23 +20,32 @@
|
||||||
var isPaused = false;
|
var isPaused = false;
|
||||||
var recognition = createSpeechRecognition();
|
var recognition = createSpeechRecognition();
|
||||||
var finalText = '';
|
var finalText = '';
|
||||||
|
var sessionFinals = '';
|
||||||
|
|
||||||
|
function escHtml(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
||||||
|
|
||||||
if (recognition) {
|
if (recognition) {
|
||||||
recognition.onresult = function(e) {
|
recognition.onresult = function(e) {
|
||||||
var interim = '';
|
var interim = '';
|
||||||
for (var i = e.resultIndex; i < e.results.length; i++) {
|
for (var i = e.resultIndex; i < e.results.length; i++) {
|
||||||
if (e.results[i].isFinal) finalText += e.results[i][0].transcript + ' ';
|
if (e.results[i].isFinal) sessionFinals += e.results[i][0].transcript + ' ';
|
||||||
else interim = e.results[i][0].transcript;
|
else interim = e.results[i][0].transcript;
|
||||||
}
|
}
|
||||||
transcript.innerHTML = finalText + (interim ? '<span style="color:#9ca3af;">' + interim + '</span>' : '');
|
var combined = finalText + sessionFinals;
|
||||||
|
transcript.innerHTML = escHtml(combined) + (interim ? '<span style="color:#9ca3af;">' + escHtml(interim) + '</span>' : '');
|
||||||
|
};
|
||||||
|
recognition.onend = function() {
|
||||||
|
finalText += sessionFinals;
|
||||||
|
sessionFinals = '';
|
||||||
|
if (isRecording && !isPaused) try { recognition.start(); } catch(e) {}
|
||||||
};
|
};
|
||||||
recognition.onend = function() { if (isRecording && !isPaused) try { recognition.start(); } catch(e) {} };
|
|
||||||
recognition.onerror = function() {};
|
recognition.onerror = function() {};
|
||||||
}
|
}
|
||||||
|
|
||||||
recordBtn.addEventListener('click', function() {
|
recordBtn.addEventListener('click', function() {
|
||||||
if (!isRecording) {
|
if (!isRecording) {
|
||||||
finalText = '';
|
finalText = '';
|
||||||
|
sessionFinals = '';
|
||||||
recorder.start().then(function() {
|
recorder.start().then(function() {
|
||||||
isRecording = true;
|
isRecording = true;
|
||||||
isPaused = false;
|
isPaused = false;
|
||||||
|
|
@ -63,6 +72,12 @@
|
||||||
showLoading('Transcribing...');
|
showLoading('Transcribing...');
|
||||||
recorder.stop().then(function(blob) {
|
recorder.stop().then(function(blob) {
|
||||||
if (!blob || blob.size === 0) { hideLoading(); if (finalText.trim()) transcript.textContent = finalText.trim(); return; }
|
if (!blob || blob.size === 0) { hideLoading(); if (finalText.trim()) transcript.textContent = finalText.trim(); return; }
|
||||||
|
if (blob.size > 24 * 1024 * 1024) {
|
||||||
|
hideLoading();
|
||||||
|
transcript.textContent = finalText.trim();
|
||||||
|
showToast('Recording too large for AI transcription — using live transcript', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
return transcribeAudio(blob).then(function(data) {
|
return transcribeAudio(blob).then(function(data) {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
if (data.success) { transcript.textContent = data.text; showToast('Transcribed ' + dur + 's', 'success'); }
|
if (data.success) { transcript.textContent = data.text; showToast('Transcribed ' + dur + 's', 'success'); }
|
||||||
|
|
|
||||||
|
|
@ -18,22 +18,31 @@
|
||||||
var isRecording = false;
|
var isRecording = false;
|
||||||
var recognition = createSpeechRecognition();
|
var recognition = createSpeechRecognition();
|
||||||
var finalText = '';
|
var finalText = '';
|
||||||
|
var sessionFinals = '';
|
||||||
|
|
||||||
|
function escHtml(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
||||||
|
|
||||||
if (recognition) {
|
if (recognition) {
|
||||||
recognition.onresult = function(e) {
|
recognition.onresult = function(e) {
|
||||||
var interim = '';
|
var interim = '';
|
||||||
for (var i = e.resultIndex; i < e.results.length; i++) {
|
for (var i = e.resultIndex; i < e.results.length; i++) {
|
||||||
if (e.results[i].isFinal) finalText += e.results[i][0].transcript + ' ';
|
if (e.results[i].isFinal) sessionFinals += e.results[i][0].transcript + ' ';
|
||||||
else interim = e.results[i][0].transcript;
|
else interim = e.results[i][0].transcript;
|
||||||
}
|
}
|
||||||
transcript.innerHTML = finalText + (interim ? '<span style="color:#9ca3af;">' + interim + '</span>' : '');
|
var combined = finalText + sessionFinals;
|
||||||
|
transcript.innerHTML = escHtml(combined) + (interim ? '<span style="color:#9ca3af;">' + escHtml(interim) + '</span>' : '');
|
||||||
|
};
|
||||||
|
recognition.onend = function() {
|
||||||
|
finalText += sessionFinals;
|
||||||
|
sessionFinals = '';
|
||||||
|
if (isRecording) try { recognition.start(); } catch(e) {}
|
||||||
};
|
};
|
||||||
recognition.onend = function() { if (isRecording) try { recognition.start(); } catch(e) {} };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
recordBtn.addEventListener('click', function() {
|
recordBtn.addEventListener('click', function() {
|
||||||
if (!isRecording) {
|
if (!isRecording) {
|
||||||
finalText = '';
|
finalText = '';
|
||||||
|
sessionFinals = '';
|
||||||
recorder.start().then(function() {
|
recorder.start().then(function() {
|
||||||
isRecording = true;
|
isRecording = true;
|
||||||
recordBtn.classList.add('recording');
|
recordBtn.classList.add('recording');
|
||||||
|
|
@ -53,6 +62,12 @@
|
||||||
showLoading('Transcribing...');
|
showLoading('Transcribing...');
|
||||||
recorder.stop().then(function(blob) {
|
recorder.stop().then(function(blob) {
|
||||||
if (!blob) { hideLoading(); if (finalText.trim()) transcript.textContent = finalText.trim(); return; }
|
if (!blob) { hideLoading(); if (finalText.trim()) transcript.textContent = finalText.trim(); return; }
|
||||||
|
if (blob.size > 24 * 1024 * 1024) {
|
||||||
|
hideLoading();
|
||||||
|
transcript.textContent = finalText.trim();
|
||||||
|
showToast('Recording too large for AI transcription — using live transcript', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
return transcribeAudio(blob).then(function(data) {
|
return transcribeAudio(blob).then(function(data) {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
if (data.success) transcript.textContent = data.text;
|
if (data.success) transcript.textContent = data.text;
|
||||||
|
|
|
||||||
|
|
@ -20,22 +20,31 @@
|
||||||
var isPaused = false;
|
var isPaused = false;
|
||||||
var recognition = createSpeechRecognition();
|
var recognition = createSpeechRecognition();
|
||||||
var finalText = '';
|
var finalText = '';
|
||||||
|
var sessionFinals = '';
|
||||||
|
|
||||||
|
function escHtml(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
||||||
|
|
||||||
if (recognition) {
|
if (recognition) {
|
||||||
recognition.onresult = function(e) {
|
recognition.onresult = function(e) {
|
||||||
var interim = '';
|
var interim = '';
|
||||||
for (var i = e.resultIndex; i < e.results.length; i++) {
|
for (var i = e.resultIndex; i < e.results.length; i++) {
|
||||||
if (e.results[i].isFinal) finalText += e.results[i][0].transcript + ' ';
|
if (e.results[i].isFinal) sessionFinals += e.results[i][0].transcript + ' ';
|
||||||
else interim = e.results[i][0].transcript;
|
else interim = e.results[i][0].transcript;
|
||||||
}
|
}
|
||||||
transcript.innerHTML = finalText + (interim ? '<span style="color:#9ca3af;">' + interim + '</span>' : '');
|
var combined = finalText + sessionFinals;
|
||||||
|
transcript.innerHTML = escHtml(combined) + (interim ? '<span style="color:#9ca3af;">' + escHtml(interim) + '</span>' : '');
|
||||||
|
};
|
||||||
|
recognition.onend = function() {
|
||||||
|
finalText += sessionFinals;
|
||||||
|
sessionFinals = '';
|
||||||
|
if (isRecording && !isPaused) try { recognition.start(); } catch(e) {}
|
||||||
};
|
};
|
||||||
recognition.onend = function() { if (isRecording && !isPaused) try { recognition.start(); } catch(e) {} };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
recordBtn.addEventListener('click', function() {
|
recordBtn.addEventListener('click', function() {
|
||||||
if (!isRecording) {
|
if (!isRecording) {
|
||||||
finalText = '';
|
finalText = '';
|
||||||
|
sessionFinals = '';
|
||||||
recorder.start().then(function() {
|
recorder.start().then(function() {
|
||||||
isRecording = true;
|
isRecording = true;
|
||||||
isPaused = false;
|
isPaused = false;
|
||||||
|
|
@ -61,6 +70,12 @@
|
||||||
showLoading('Transcribing...');
|
showLoading('Transcribing...');
|
||||||
recorder.stop().then(function(blob) {
|
recorder.stop().then(function(blob) {
|
||||||
if (!blob || blob.size === 0) { hideLoading(); if (finalText.trim()) transcript.textContent = finalText.trim(); return; }
|
if (!blob || blob.size === 0) { hideLoading(); if (finalText.trim()) transcript.textContent = finalText.trim(); return; }
|
||||||
|
if (blob.size > 24 * 1024 * 1024) {
|
||||||
|
hideLoading();
|
||||||
|
transcript.textContent = finalText.trim();
|
||||||
|
showToast('Recording too large for AI transcription — using live transcript', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
return transcribeAudio(blob).then(function(data) {
|
return transcribeAudio(blob).then(function(data) {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
if (data.success) { transcript.textContent = data.text; showToast('Transcribed ' + dur + 's', 'success'); }
|
if (data.success) { transcript.textContent = data.text; showToast('Transcribed ' + dur + 's', 'success'); }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue