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"
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -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, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
cleanupOldLocalBackups();
|
||||
})();
|
||||
cleanupOldLocalBackups();
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in a new issue