convert speech helpers to modules
This commit is contained in:
parent
ea52890908
commit
04e736eb2e
4 changed files with 153 additions and 157 deletions
|
|
@ -475,8 +475,8 @@
|
||||||
integrity="sha384-JUh163oCRItcbPme8pYnROHQMC6fNKTBWtRG3I3I0erJkzNgL7uxKlNwcrcFKeqF"
|
integrity="sha384-JUh163oCRItcbPme8pYnROHQMC6fNKTBWtRG3I3I0erJkzNgL7uxKlNwcrcFKeqF"
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer" defer></script>
|
crossorigin="anonymous" referrerpolicy="no-referrer" defer></script>
|
||||||
<script defer src="/js/milestonesData.js"></script>
|
<script defer src="/js/milestonesData.js"></script>
|
||||||
<script defer src="/js/audioBackup.js"></script>
|
<script type="module" src="/js/audioBackup.js"></script>
|
||||||
<script defer src="/js/speechRecognition.js"></script>
|
<script type="module" src="/js/speechRecognition.js"></script>
|
||||||
<script type="module" src="/js/transcriptionSettings.js"></script>
|
<script type="module" src="/js/transcriptionSettings.js"></script>
|
||||||
<script type="module" src="/js/voicePreferences.js"></script>
|
<script type="module" src="/js/voicePreferences.js"></script>
|
||||||
<script defer src="/js/app.js?v=7.1.3"></script>
|
<script defer src="/js/app.js?v=7.1.3"></script>
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,12 @@
|
||||||
// Falls back to IndexedDB if server save fails.
|
// Falls back to IndexedDB if server save fails.
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
(function() {
|
var DB_NAME = 'PedScribeAudioBackup';
|
||||||
var DB_NAME = 'PedScribeAudioBackup';
|
var STORE_NAME = 'recordings';
|
||||||
var STORE_NAME = 'recordings';
|
var DB_VERSION = 1;
|
||||||
var DB_VERSION = 1;
|
var MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
||||||
var MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
||||||
|
|
||||||
var _db = null;
|
var _db = null;
|
||||||
|
|
||||||
function openDB() {
|
function openDB() {
|
||||||
if (_db) return Promise.resolve(_db);
|
if (_db) return Promise.resolve(_db);
|
||||||
|
|
@ -293,5 +292,4 @@
|
||||||
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanupOldLocalBackups();
|
cleanupOldLocalBackups();
|
||||||
})();
|
|
||||||
|
|
|
||||||
|
|
@ -5,156 +5,152 @@
|
||||||
// Only use if you accept this trade-off for real-time streaming
|
// 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;
|
window.WebSpeechRecognition = {
|
||||||
var _recognition = null;
|
|
||||||
var _transcript = '';
|
|
||||||
var _isListening = false;
|
|
||||||
var _onPartialCallback = null;
|
|
||||||
var _onFinalCallback = null;
|
|
||||||
|
|
||||||
window.WebSpeechRecognition = {
|
isSupported: function() {
|
||||||
|
return typeof SpeechRecognition !== 'undefined';
|
||||||
|
},
|
||||||
|
|
||||||
isSupported: function() {
|
isEnabled: function() {
|
||||||
return typeof SpeechRecognition !== 'undefined';
|
try {
|
||||||
},
|
return localStorage.getItem(STORAGE_ENABLED) === '1';
|
||||||
|
} catch(e) {
|
||||||
isEnabled: function() {
|
return false;
|
||||||
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'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
|
||||||
})();
|
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'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ const moduleEntrypoints = [
|
||||||
'public/js/clinicalAssistant.js',
|
'public/js/clinicalAssistant.js',
|
||||||
'public/js/admin.js',
|
'public/js/admin.js',
|
||||||
'public/js/admin-docs.js',
|
'public/js/admin-docs.js',
|
||||||
|
'public/js/audioBackup.js',
|
||||||
'public/js/authFetch.js',
|
'public/js/authFetch.js',
|
||||||
'public/js/diagrams.js',
|
'public/js/diagrams.js',
|
||||||
'public/js/drugs-loader.js',
|
'public/js/drugs-loader.js',
|
||||||
|
|
@ -22,6 +23,7 @@ const moduleEntrypoints = [
|
||||||
'public/js/sickVisit.js',
|
'public/js/sickVisit.js',
|
||||||
'public/js/soap.js',
|
'public/js/soap.js',
|
||||||
'public/js/secureStorage.js',
|
'public/js/secureStorage.js',
|
||||||
|
'public/js/speechRecognition.js',
|
||||||
'public/js/transcriptionSettings.js',
|
'public/js/transcriptionSettings.js',
|
||||||
'public/js/ui-state.js',
|
'public/js/ui-state.js',
|
||||||
'public/js/wellVisit.js',
|
'public/js/wellVisit.js',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue