diff --git a/package.json b/package.json
index 1198ff6..1caa55d 100644
--- a/package.json
+++ b/package.json
@@ -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": {
diff --git a/public/js/app.js b/public/js/app.js
index cb30b96..e775d0a 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -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() {});
diff --git a/public/js/liveEncounter.js b/public/js/liveEncounter.js
index 52c545b..b770627 100644
--- a/public/js/liveEncounter.js
+++ b/public/js/liveEncounter.js
@@ -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 ? '' + escHtml(interim) + '' : '');
@@ -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() {
diff --git a/public/js/soap.js b/public/js/soap.js
index b33f5e5..f42ce1e 100644
--- a/public/js/soap.js
+++ b/public/js/soap.js
@@ -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 ? '' + escHtml(interim) + '' : '');
diff --git a/public/js/voiceDictation.js b/public/js/voiceDictation.js
index 5e50839..f8c5b68 100644
--- a/public/js/voiceDictation.js
+++ b/public/js/voiceDictation.js
@@ -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 ? '' + escHtml(interim) + '' : '');