Provides two transcription options: 1. Browser Whisper (Offline, Batch) - RECOMMENDED - 100% offline, zero network calls - HIPAA-compliant, audio never leaves device - Highest accuracy (Whisper) - Processes after recording (batch mode) - Models self-hosted, bundled in v2 2. Web Speech API (Real-time, Streaming) - EXPERIMENTAL - Real-time transcription (see words as you speak) - Uses browser's built-in speech recognition - ⚠️ Sends audio to cloud (Chrome/Edge → Google) - ⚠️ NOT HIPAA-compliant - Requires user consent with clear warnings Features: - Settings UI for both options - Clear privacy warnings for Web Speech - Mutual exclusion (only one active at a time) - Browser detection shows which provider is used - Confirmation dialog before enabling Web Speech Use Cases: - Clinical/HIPAA: Use Browser Whisper only - Personal/Non-clinical: Can use Web Speech for real-time feedback - Maximum privacy: Browser Whisper (offline) - Maximum speed: Web Speech (if privacy not required) Implementation: - speechRecognition.js: Web Speech API wrapper - transcriptionSettings.js: Settings UI handler - Privacy info displayed per browser User can choose based on their privacy vs. speed preference.
166 lines
6.5 KiB
JavaScript
166 lines
6.5 KiB
JavaScript
// ============================================================
|
|
// TRANSCRIPTION SETTINGS — Browser Whisper + Web Speech API
|
|
// ============================================================
|
|
|
|
(function() {
|
|
var _inited = false;
|
|
|
|
document.addEventListener('tabChanged', function(e) {
|
|
if (e.detail.tab !== 'settings' || _inited) return;
|
|
_inited = true;
|
|
initTranscriptionSettings();
|
|
});
|
|
|
|
// Also init on direct page load
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
setTimeout(function() {
|
|
if (!_inited && document.getElementById('browser-whisper-enabled')) {
|
|
_inited = true;
|
|
initTranscriptionSettings();
|
|
}
|
|
}, 500);
|
|
});
|
|
} else {
|
|
setTimeout(function() {
|
|
if (!_inited && document.getElementById('browser-whisper-enabled')) {
|
|
_inited = true;
|
|
initTranscriptionSettings();
|
|
}
|
|
}, 500);
|
|
}
|
|
|
|
function initTranscriptionSettings() {
|
|
console.log('[TranscriptionSettings] Initializing...');
|
|
|
|
// ── Browser Whisper ──────────────────────────────────────
|
|
var whisperCheckbox = document.getElementById('browser-whisper-enabled');
|
|
var whisperStatus = document.getElementById('browser-whisper-status');
|
|
var whisperModel = document.getElementById('browser-whisper-model');
|
|
var whisperPreload = document.getElementById('btn-whisper-preload');
|
|
var whisperProgress = document.getElementById('browser-whisper-progress');
|
|
var whisperProgressText = document.getElementById('browser-whisper-progress-text');
|
|
|
|
if (whisperCheckbox && window.BrowserWhisper) {
|
|
whisperCheckbox.checked = BrowserWhisper.isEnabled();
|
|
whisperStatus.textContent = BrowserWhisper.isEnabled() ? 'On — audio stays on device' : 'Off';
|
|
|
|
if (whisperModel) {
|
|
whisperModel.value = BrowserWhisper.getModel();
|
|
}
|
|
|
|
whisperCheckbox.addEventListener('change', function() {
|
|
BrowserWhisper.setEnabled(whisperCheckbox.checked);
|
|
whisperStatus.textContent = whisperCheckbox.checked ? 'On — audio stays on device' : 'Off';
|
|
|
|
if (whisperCheckbox.checked) {
|
|
// Auto-preload on enable
|
|
BrowserWhisper.preload(function(file, pct) {
|
|
if (whisperProgress && whisperProgressText) {
|
|
if (pct >= 100) {
|
|
whisperProgress.style.display = 'none';
|
|
return;
|
|
}
|
|
whisperProgress.style.display = 'block';
|
|
whisperProgressText.textContent = file + (pct > 0 ? ' ' + pct + '%' : '');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
if (whisperModel) {
|
|
whisperModel.addEventListener('change', function() {
|
|
BrowserWhisper.setModel(whisperModel.value);
|
|
});
|
|
}
|
|
|
|
if (whisperPreload) {
|
|
whisperPreload.addEventListener('click', function() {
|
|
if (!BrowserWhisper || !BrowserWhisper.isSupported()) {
|
|
showToast('Browser Whisper not supported', 'error');
|
|
return;
|
|
}
|
|
|
|
whisperPreload.disabled = true;
|
|
whisperPreload.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Downloading...';
|
|
whisperProgress.style.display = 'block';
|
|
whisperProgressText.textContent = 'Initializing...';
|
|
|
|
BrowserWhisper.setEnabled(true);
|
|
whisperCheckbox.checked = true;
|
|
whisperStatus.textContent = 'On — audio stays on device';
|
|
|
|
BrowserWhisper.preload(function(file, pct) {
|
|
if (pct >= 100) {
|
|
whisperProgress.style.display = 'none';
|
|
whisperPreload.disabled = false;
|
|
whisperPreload.innerHTML = '<i class="fas fa-download"></i> Pre-download model';
|
|
showToast('Whisper model ready!', 'success');
|
|
return;
|
|
}
|
|
whisperProgress.style.display = 'block';
|
|
whisperProgressText.textContent = file + (pct > 0 ? ' ' + pct + '%' : '');
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
// ── Web Speech Recognition ───────────────────────────────
|
|
var speechCheckbox = document.getElementById('web-speech-enabled');
|
|
var speechStatus = document.getElementById('web-speech-status');
|
|
var speechBrowserInfo = document.getElementById('web-speech-browser-info');
|
|
|
|
if (speechCheckbox && window.WebSpeechRecognition) {
|
|
speechCheckbox.checked = WebSpeechRecognition.isEnabled();
|
|
speechStatus.textContent = WebSpeechRecognition.isEnabled() ? 'On — real-time streaming' : 'Off';
|
|
|
|
// Show privacy info
|
|
if (speechBrowserInfo) {
|
|
var privacyInfo = WebSpeechRecognition.getPrivacyInfo();
|
|
speechBrowserInfo.innerHTML =
|
|
'<strong>Provider:</strong> ' + privacyInfo.provider + '<br>' +
|
|
'<strong>Privacy:</strong> ' + privacyInfo.privacy + '<br>' +
|
|
'<strong style="color:var(--orange);">⚠️ ' + privacyInfo.warning + '</strong>';
|
|
}
|
|
|
|
speechCheckbox.addEventListener('change', function() {
|
|
if (speechCheckbox.checked) {
|
|
// Show confirmation for privacy warning
|
|
if (!confirm(
|
|
'WARNING: Real-time streaming uses your browser\'s speech recognition, ' +
|
|
'which may send audio to cloud servers (e.g., Google).\n\n' +
|
|
'This is NOT HIPAA-compliant.\n\n' +
|
|
'Only enable if you understand and accept this privacy trade-off.\n\n' +
|
|
'Continue?'
|
|
)) {
|
|
speechCheckbox.checked = false;
|
|
return;
|
|
}
|
|
|
|
// Disable Browser Whisper if enabling Web Speech
|
|
if (whisperCheckbox && whisperCheckbox.checked) {
|
|
whisperCheckbox.checked = false;
|
|
BrowserWhisper.setEnabled(false);
|
|
whisperStatus.textContent = 'Off';
|
|
showToast('Browser Whisper disabled (Web Speech takes priority)', 'info');
|
|
}
|
|
}
|
|
|
|
WebSpeechRecognition.setEnabled(speechCheckbox.checked);
|
|
speechStatus.textContent = speechCheckbox.checked ? 'On — real-time streaming' : 'Off';
|
|
});
|
|
|
|
// If not supported, disable and show message
|
|
if (!WebSpeechRecognition.isSupported()) {
|
|
speechCheckbox.disabled = true;
|
|
speechStatus.textContent = 'Not supported in this browser';
|
|
if (speechBrowserInfo) {
|
|
speechBrowserInfo.innerHTML = '<strong style="color:var(--red);">Not supported</strong> - Web Speech API not available in this browser.';
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log('✅ Transcription settings initialized');
|
|
}
|
|
|
|
})();
|