v8.0.0: Fix speech recognition repeating text, enable AWS Transcribe Medical

- Add deduplication logic to prevent Chrome Speech API from repeating
  sentences during long recording sessions (all 4 recording modules)
- Enable AWS Transcribe Medical with PRIMARYCARE specialty in .env
- Bump version to 8.0.0
This commit is contained in:
Daniel Onyejesi 2026-03-25 17:24:28 -04:00
parent 3da61c455e
commit 70d2f34770
5 changed files with 49 additions and 8 deletions

View file

@ -1,6 +1,6 @@
{
"name": "pediatric-ai-scribe",
"version": "7.0.0",
"version": "8.0.0",
"description": "AI-powered pediatric clinical documentation platform",
"main": "server.js",
"scripts": {

View file

@ -542,9 +542,32 @@ function createSpeechRecognition() {
rec.continuous = true;
rec.interimResults = true;
rec.lang = 'en-US';
rec.maxAlternatives = 1;
return rec;
}
// Deduplicate speech recognition finals — Chrome can repeat text across restarts
function deduplicateFinal(newText, existingText) {
if (!newText || !existingText) return newText;
var trimmed = newText.trim();
if (!trimmed) return '';
// Check if the new text is already at the end of existing text
if (existingText.trimEnd().endsWith(trimmed)) return '';
// Check for partial overlap (last sentence repeated)
var words = trimmed.split(/\s+/);
if (words.length >= 3) {
var tail = existingText.trimEnd().split(/\s+/).slice(-words.length).join(' ');
if (tail === trimmed) return '';
// Check if first half of new text overlaps with end of existing
var half = Math.ceil(words.length / 2);
var firstHalf = words.slice(0, half).join(' ');
if (existingText.trimEnd().endsWith(firstHalf)) {
return words.slice(half).join(' ') + ' ';
}
}
return newText;
}
// PWA Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').catch(function() {});

View file

@ -28,8 +28,13 @@
recognition.onresult = function(e) {
var interim = '';
for (var i = e.resultIndex; i < e.results.length; i++) {
if (e.results[i].isFinal) sessionFinals += e.results[i][0].transcript + ' ';
else interim = e.results[i][0].transcript;
if (e.results[i].isFinal) {
var chunk = e.results[i][0].transcript + ' ';
var deduped = deduplicateFinal(chunk, finalText + sessionFinals);
sessionFinals += deduped;
} else {
interim = e.results[i][0].transcript;
}
}
var combined = finalText + sessionFinals;
transcript.innerHTML = escHtml(combined) + (interim ? '<span style="color:#9ca3af;">' + escHtml(interim) + '</span>' : '');
@ -39,7 +44,10 @@
sessionFinals = '';
if (isRecording && !isPaused) try { recognition.start(); } catch(e) {}
};
recognition.onerror = function() {};
recognition.onerror = function(e) {
if (e.error === 'no-speech' || e.error === 'aborted') return;
console.warn('[SpeechRecognition] error:', e.error);
};
}
recordBtn.addEventListener('click', function() {

View file

@ -26,8 +26,13 @@
recognition.onresult = function(e) {
var interim = '';
for (var i = e.resultIndex; i < e.results.length; i++) {
if (e.results[i].isFinal) sessionFinals += e.results[i][0].transcript + ' ';
else interim = e.results[i][0].transcript;
if (e.results[i].isFinal) {
var chunk = e.results[i][0].transcript + ' ';
var deduped = deduplicateFinal(chunk, finalText + sessionFinals);
sessionFinals += deduped;
} else {
interim = e.results[i][0].transcript;
}
}
var combined = finalText + sessionFinals;
transcript.innerHTML = escHtml(combined) + (interim ? '<span style="color:#9ca3af;">' + escHtml(interim) + '</span>' : '');

View file

@ -28,8 +28,13 @@
recognition.onresult = function(e) {
var interim = '';
for (var i = e.resultIndex; i < e.results.length; i++) {
if (e.results[i].isFinal) sessionFinals += e.results[i][0].transcript + ' ';
else interim = e.results[i][0].transcript;
if (e.results[i].isFinal) {
var chunk = e.results[i][0].transcript + ' ';
var deduped = deduplicateFinal(chunk, finalText + sessionFinals);
sessionFinals += deduped;
} else {
interim = e.results[i][0].transcript;
}
}
var combined = finalText + sessionFinals;
transcript.innerHTML = escHtml(combined) + (interim ? '<span style="color:#9ca3af;">' + escHtml(interim) + '</span>' : '');