convert speech helpers to modules

This commit is contained in:
Daniel 2026-05-08 08:09:38 +02:00
parent ea52890908
commit 04e736eb2e
4 changed files with 153 additions and 157 deletions

View file

@ -475,8 +475,8 @@
integrity="sha384-JUh163oCRItcbPme8pYnROHQMC6fNKTBWtRG3I3I0erJkzNgL7uxKlNwcrcFKeqF"
crossorigin="anonymous" referrerpolicy="no-referrer" defer></script>
<script defer src="/js/milestonesData.js"></script>
<script defer src="/js/audioBackup.js"></script>
<script defer src="/js/speechRecognition.js"></script>
<script type="module" src="/js/audioBackup.js"></script>
<script type="module" src="/js/speechRecognition.js"></script>
<script type="module" src="/js/transcriptionSettings.js"></script>
<script type="module" src="/js/voicePreferences.js"></script>
<script defer src="/js/app.js?v=7.1.3"></script>

View file

@ -4,13 +4,12 @@
// Falls back to IndexedDB if server save fails.
// ============================================================
(function() {
var DB_NAME = 'PedScribeAudioBackup';
var STORE_NAME = 'recordings';
var DB_VERSION = 1;
var MAX_AGE_MS = 24 * 60 * 60 * 1000;
var DB_NAME = 'PedScribeAudioBackup';
var STORE_NAME = 'recordings';
var DB_VERSION = 1;
var MAX_AGE_MS = 24 * 60 * 60 * 1000;
var _db = null;
var _db = null;
function openDB() {
if (_db) return Promise.resolve(_db);
@ -293,5 +292,4 @@
return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
cleanupOldLocalBackups();
})();
cleanupOldLocalBackups();

View file

@ -5,156 +5,152 @@
// Only use if you accept this trade-off for real-time streaming
// ============================================================
(function() {
var STORAGE_ENABLED = 'ped_web_speech_enabled';
var STORAGE_ENABLED = 'ped_web_speech_enabled';
var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
var _recognition = null;
var _transcript = '';
var _isListening = false;
var _onPartialCallback = null;
var _onFinalCallback = null;
var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
var _recognition = null;
var _transcript = '';
var _isListening = false;
var _onPartialCallback = null;
var _onFinalCallback = null;
window.WebSpeechRecognition = {
window.WebSpeechRecognition = {
isSupported: function() {
return typeof SpeechRecognition !== 'undefined';
},
isSupported: function() {
return typeof SpeechRecognition !== 'undefined';
},
isEnabled: function() {
try {
return localStorage.getItem(STORAGE_ENABLED) === '1';
} catch(e) {
return false;
}
},
setEnabled: function(val) {
try {
localStorage.setItem(STORAGE_ENABLED, val ? '1' : '0');
} catch(e) {}
},
startListening: function(options) {
if (!this.isSupported()) {
return Promise.reject(new Error('Web Speech API not supported'));
}
if (!this.isEnabled()) {
return Promise.reject(new Error('Web Speech Recognition not enabled'));
}
_transcript = '';
_isListening = true;
var opts = options || {};
_onPartialCallback = opts.onPartial || function() {};
_onFinalCallback = opts.onFinal || function() {};
_recognition = new SpeechRecognition();
_recognition.continuous = true; // Keep listening
_recognition.interimResults = true; // Show partial results
_recognition.lang = opts.language || 'en-US';
_recognition.maxAlternatives = 1;
return new Promise(function(resolve, reject) {
_recognition.onstart = function() {
console.log('[WebSpeech] Started listening');
};
_recognition.onresult = function(event) {
var interim = '';
var final = '';
for (var i = event.resultIndex; i < event.results.length; i++) {
var transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
final += transcript + ' ';
_transcript += transcript + ' ';
} else {
interim += transcript;
}
}
// Callback with partial results (shown in real-time)
if (interim && _onPartialCallback) {
_onPartialCallback(interim);
}
// Callback with final results (confirmed words)
if (final && _onFinalCallback) {
_onFinalCallback(final.trim());
}
};
_recognition.onerror = function(event) {
console.error('[WebSpeech] Error:', event.error);
_isListening = false;
if (event.error === 'no-speech') {
reject(new Error('No speech detected'));
} else if (event.error === 'not-allowed') {
reject(new Error('Microphone permission denied'));
} else {
reject(new Error('Speech recognition error: ' + event.error));
}
};
_recognition.onend = function() {
_isListening = false;
console.log('[WebSpeech] Stopped listening');
resolve(_transcript.trim());
};
try {
_recognition.start();
} catch (e) {
reject(new Error('Failed to start recognition: ' + e.message));
}
});
},
stopListening: function() {
if (_recognition && _isListening) {
_recognition.stop();
}
},
isListening: function() {
return _isListening;
},
getCurrentTranscript: function() {
return _transcript.trim();
},
// Check if browser likely sends to cloud
getPrivacyInfo: function() {
var isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
var isEdge = /Edg/.test(navigator.userAgent);
if (isChrome || isEdge) {
return {
provider: 'Google Cloud Speech',
privacy: 'Audio sent to Google servers',
warning: 'NOT HIPAA-compliant'
};
} else if (/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent)) {
return {
provider: 'Apple Speech Recognition',
privacy: 'May process on-device or Apple servers',
warning: 'Check Apple privacy policy'
};
} else {
return {
provider: 'Unknown',
privacy: 'May send audio to cloud servers',
warning: 'Privacy unknown'
};
}
isEnabled: function() {
try {
return localStorage.getItem(STORAGE_ENABLED) === '1';
} catch(e) {
return false;
}
};
},
})();
setEnabled: function(val) {
try {
localStorage.setItem(STORAGE_ENABLED, val ? '1' : '0');
} catch(e) {}
},
startListening: function(options) {
if (!this.isSupported()) {
return Promise.reject(new Error('Web Speech API not supported'));
}
if (!this.isEnabled()) {
return Promise.reject(new Error('Web Speech Recognition not enabled'));
}
_transcript = '';
_isListening = true;
var opts = options || {};
_onPartialCallback = opts.onPartial || function() {};
_onFinalCallback = opts.onFinal || function() {};
_recognition = new SpeechRecognition();
_recognition.continuous = true; // Keep listening
_recognition.interimResults = true; // Show partial results
_recognition.lang = opts.language || 'en-US';
_recognition.maxAlternatives = 1;
return new Promise(function(resolve, reject) {
_recognition.onstart = function() {
console.log('[WebSpeech] Started listening');
};
_recognition.onresult = function(event) {
var interim = '';
var final = '';
for (var i = event.resultIndex; i < event.results.length; i++) {
var transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
final += transcript + ' ';
_transcript += transcript + ' ';
} else {
interim += transcript;
}
}
// Callback with partial results (shown in real-time)
if (interim && _onPartialCallback) {
_onPartialCallback(interim);
}
// Callback with final results (confirmed words)
if (final && _onFinalCallback) {
_onFinalCallback(final.trim());
}
};
_recognition.onerror = function(event) {
console.error('[WebSpeech] Error:', event.error);
_isListening = false;
if (event.error === 'no-speech') {
reject(new Error('No speech detected'));
} else if (event.error === 'not-allowed') {
reject(new Error('Microphone permission denied'));
} else {
reject(new Error('Speech recognition error: ' + event.error));
}
};
_recognition.onend = function() {
_isListening = false;
console.log('[WebSpeech] Stopped listening');
resolve(_transcript.trim());
};
try {
_recognition.start();
} catch (e) {
reject(new Error('Failed to start recognition: ' + e.message));
}
});
},
stopListening: function() {
if (_recognition && _isListening) {
_recognition.stop();
}
},
isListening: function() {
return _isListening;
},
getCurrentTranscript: function() {
return _transcript.trim();
},
// Check if browser likely sends to cloud
getPrivacyInfo: function() {
var isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
var isEdge = /Edg/.test(navigator.userAgent);
if (isChrome || isEdge) {
return {
provider: 'Google Cloud Speech',
privacy: 'Audio sent to Google servers',
warning: 'NOT HIPAA-compliant'
};
} else if (/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent)) {
return {
provider: 'Apple Speech Recognition',
privacy: 'May process on-device or Apple servers',
warning: 'Check Apple privacy policy'
};
} else {
return {
provider: 'Unknown',
privacy: 'May send audio to cloud servers',
warning: 'Privacy unknown'
};
}
}
};

View file

@ -9,6 +9,7 @@ const moduleEntrypoints = [
'public/js/clinicalAssistant.js',
'public/js/admin.js',
'public/js/admin-docs.js',
'public/js/audioBackup.js',
'public/js/authFetch.js',
'public/js/diagrams.js',
'public/js/drugs-loader.js',
@ -22,6 +23,7 @@ const moduleEntrypoints = [
'public/js/sickVisit.js',
'public/js/soap.js',
'public/js/secureStorage.js',
'public/js/speechRecognition.js',
'public/js/transcriptionSettings.js',
'public/js/ui-state.js',
'public/js/wellVisit.js',