Zero-config speech: skip upload when no transcription API, use browser speech directly
- Add GET /api/transcribe/status endpoint — returns whether any server transcription provider (Whisper/AWS/Local) is configured - Frontend checks status on login via checkTranscribeStatus() - When no provider configured: recording stops instantly, keeps live Web Speech API text, shows friendly toast — no error, no upload wait - Works in encounter, dictation, and SOAP tabs - App now works fully out-of-the-box with just an AI provider key
This commit is contained in:
parent
51fc818e4a
commit
bf89cad954
6 changed files with 112 additions and 46 deletions
|
|
@ -482,7 +482,31 @@ AudioRecorder.prototype.stop = function() {
|
|||
});
|
||||
};
|
||||
|
||||
// Check if server-side transcription (Whisper/AWS) is available
|
||||
window._transcribeAvailable = null; // null = not checked yet, true/false after check
|
||||
function checkTranscribeStatus() {
|
||||
var token = window.AUTH_TOKEN || localStorage.getItem('ped_scribe_token') || '';
|
||||
if (!token) return;
|
||||
fetch('/api/transcribe/status', {
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
window._transcribeAvailable = !!data.available;
|
||||
window._transcribeProvider = data.provider || 'none';
|
||||
if (!data.available) {
|
||||
console.log('[Transcribe] No server transcription configured — using browser speech recognition only');
|
||||
}
|
||||
})
|
||||
.catch(function() { window._transcribeAvailable = false; });
|
||||
}
|
||||
|
||||
function transcribeAudio(blob) {
|
||||
// If no server transcription is configured, skip upload entirely
|
||||
if (window._transcribeAvailable === false) {
|
||||
return Promise.resolve({ success: false, noProvider: true, error: 'No transcription API configured — using live transcript' });
|
||||
}
|
||||
|
||||
var startTime = Date.now();
|
||||
var formData = new FormData();
|
||||
formData.append('audio', blob, 'audio.webm');
|
||||
|
|
|
|||
|
|
@ -167,6 +167,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
|
||||
// Load announcement banner if function is available
|
||||
if (typeof loadAnnouncement === 'function') loadAnnouncement();
|
||||
|
||||
// Check if server-side transcription is configured
|
||||
if (typeof checkTranscribeStatus === 'function') checkTranscribeStatus();
|
||||
}
|
||||
|
||||
function exitApp() {
|
||||
|
|
|
|||
|
|
@ -78,21 +78,30 @@
|
|||
if (recognition) try { recognition.stop(); } catch(e) {}
|
||||
document.dispatchEvent(new CustomEvent('recording-stopped', { detail: { module: 'enc' } }));
|
||||
|
||||
showLoading('Transcribing...');
|
||||
recorder.stop().then(function(blob) {
|
||||
if (!blob || blob.size === 0) { hideLoading(); if (finalText.trim()) transcript.textContent = finalText.trim(); return; }
|
||||
if (blob.size > 24 * 1024 * 1024) {
|
||||
hideLoading();
|
||||
transcript.textContent = finalText.trim();
|
||||
showToast('Recording too large for AI transcription — using live transcript', 'info');
|
||||
return;
|
||||
}
|
||||
return transcribeAudio(blob).then(function(data) {
|
||||
hideLoading();
|
||||
if (data.success) { transcript.textContent = data.text; showToast('Transcribed ' + dur + 's', 'success'); }
|
||||
else { if (finalText.trim()) transcript.textContent = finalText.trim(); showToast(data.error || 'Failed', 'error'); }
|
||||
});
|
||||
}).catch(function(err) { hideLoading(); if (finalText.trim()) transcript.textContent = finalText.trim(); showToast(err.message, 'error'); });
|
||||
// If no server transcription API, use live transcript directly (no upload)
|
||||
var liveText = (finalText + sessionFinals).trim();
|
||||
if (window._transcribeAvailable === false) {
|
||||
recorder.stop().then(function() {});
|
||||
if (liveText) transcript.textContent = liveText;
|
||||
showToast('Using browser speech recognition (no transcription API configured)', 'info');
|
||||
} else {
|
||||
showLoading('Transcribing...');
|
||||
recorder.stop().then(function(blob) {
|
||||
if (!blob || blob.size === 0) { hideLoading(); if (liveText) transcript.textContent = liveText; return; }
|
||||
if (blob.size > 24 * 1024 * 1024) {
|
||||
hideLoading();
|
||||
transcript.textContent = liveText;
|
||||
showToast('Recording too large for AI transcription — using live transcript', 'info');
|
||||
return;
|
||||
}
|
||||
return transcribeAudio(blob).then(function(data) {
|
||||
hideLoading();
|
||||
if (data.success) { transcript.textContent = data.text; showToast('Transcribed ' + dur + 's', 'success'); }
|
||||
else if (data.noProvider) { if (liveText) transcript.textContent = liveText; showToast('Using live transcript', 'info'); }
|
||||
else { if (liveText) transcript.textContent = liveText; showToast(data.error || 'Failed', 'error'); }
|
||||
});
|
||||
}).catch(function(err) { hideLoading(); if (liveText) transcript.textContent = liveText; showToast(err.message, 'error'); });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -65,21 +65,29 @@
|
|||
indicator.classList.add('hidden');
|
||||
if (recognition) try { recognition.stop(); } catch(e) {}
|
||||
|
||||
showLoading('Transcribing...');
|
||||
recorder.stop().then(function(blob) {
|
||||
if (!blob) { hideLoading(); if (finalText.trim()) transcript.textContent = finalText.trim(); return; }
|
||||
if (blob.size > 24 * 1024 * 1024) {
|
||||
hideLoading();
|
||||
transcript.textContent = finalText.trim();
|
||||
showToast('Recording too large for AI transcription — using live transcript', 'info');
|
||||
return;
|
||||
}
|
||||
return transcribeAudio(blob).then(function(data) {
|
||||
hideLoading();
|
||||
if (data.success) transcript.textContent = data.text;
|
||||
else { if (finalText.trim()) transcript.textContent = finalText.trim(); showToast(data.error || 'Transcription failed — using live transcript', 'error'); }
|
||||
});
|
||||
}).catch(function(err) { hideLoading(); if (finalText.trim()) transcript.textContent = finalText.trim(); showToast('Transcription error: ' + (err.message || 'unknown'), 'error'); });
|
||||
var liveText = (finalText + sessionFinals).trim();
|
||||
if (window._transcribeAvailable === false) {
|
||||
recorder.stop().then(function() {});
|
||||
if (liveText) transcript.textContent = liveText;
|
||||
showToast('Using browser speech recognition', 'info');
|
||||
} else {
|
||||
showLoading('Transcribing...');
|
||||
recorder.stop().then(function(blob) {
|
||||
if (!blob) { hideLoading(); if (liveText) transcript.textContent = liveText; return; }
|
||||
if (blob.size > 24 * 1024 * 1024) {
|
||||
hideLoading();
|
||||
transcript.textContent = liveText;
|
||||
showToast('Recording too large for AI transcription — using live transcript', 'info');
|
||||
return;
|
||||
}
|
||||
return transcribeAudio(blob).then(function(data) {
|
||||
hideLoading();
|
||||
if (data.success) transcript.textContent = data.text;
|
||||
else if (data.noProvider) { if (liveText) transcript.textContent = liveText; showToast('Using live transcript', 'info'); }
|
||||
else { if (liveText) transcript.textContent = liveText; showToast(data.error || 'Transcription failed — using live transcript', 'error'); }
|
||||
});
|
||||
}).catch(function(err) { hideLoading(); if (liveText) transcript.textContent = liveText; showToast('Transcription error: ' + (err.message || 'unknown'), 'error'); });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -73,21 +73,29 @@
|
|||
if (recognition) try { recognition.stop(); } catch(e) {}
|
||||
document.dispatchEvent(new CustomEvent('recording-stopped', { detail: { module: 'dict' } }));
|
||||
|
||||
showLoading('Transcribing...');
|
||||
recorder.stop().then(function(blob) {
|
||||
if (!blob || blob.size === 0) { hideLoading(); if (finalText.trim()) transcript.textContent = finalText.trim(); return; }
|
||||
if (blob.size > 24 * 1024 * 1024) {
|
||||
hideLoading();
|
||||
transcript.textContent = finalText.trim();
|
||||
showToast('Recording too large for AI transcription — using live transcript', 'info');
|
||||
return;
|
||||
}
|
||||
return transcribeAudio(blob).then(function(data) {
|
||||
hideLoading();
|
||||
if (data.success) { transcript.textContent = data.text; showToast('Transcribed ' + dur + 's', 'success'); }
|
||||
else { if (finalText.trim()) transcript.textContent = finalText.trim(); showToast(data.error || 'Transcription failed — using live transcript', 'error'); }
|
||||
});
|
||||
}).catch(function(err) { hideLoading(); if (finalText.trim()) transcript.textContent = finalText.trim(); showToast('Transcription error: ' + (err.message || 'unknown'), 'error'); });
|
||||
var liveText = (finalText + sessionFinals).trim();
|
||||
if (window._transcribeAvailable === false) {
|
||||
recorder.stop().then(function() {});
|
||||
if (liveText) transcript.textContent = liveText;
|
||||
showToast('Using browser speech recognition (no transcription API configured)', 'info');
|
||||
} else {
|
||||
showLoading('Transcribing...');
|
||||
recorder.stop().then(function(blob) {
|
||||
if (!blob || blob.size === 0) { hideLoading(); if (liveText) transcript.textContent = liveText; return; }
|
||||
if (blob.size > 24 * 1024 * 1024) {
|
||||
hideLoading();
|
||||
transcript.textContent = liveText;
|
||||
showToast('Recording too large for AI transcription — using live transcript', 'info');
|
||||
return;
|
||||
}
|
||||
return transcribeAudio(blob).then(function(data) {
|
||||
hideLoading();
|
||||
if (data.success) { transcript.textContent = data.text; showToast('Transcribed ' + dur + 's', 'success'); }
|
||||
else if (data.noProvider) { if (liveText) transcript.textContent = liveText; showToast('Using live transcript', 'info'); }
|
||||
else { if (liveText) transcript.textContent = liveText; showToast(data.error || 'Transcription failed — using live transcript', 'error'); }
|
||||
});
|
||||
}).catch(function(err) { hideLoading(); if (liveText) transcript.textContent = liveText; showToast('Transcription error: ' + (err.message || 'unknown'), 'error'); });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -23,9 +23,23 @@ function getTranscribeProvider() {
|
|||
return 'openai';
|
||||
}
|
||||
|
||||
// Check if any transcription provider is actually available
|
||||
function isTranscribeAvailable() {
|
||||
if (isLocalWhisperConfigured()) return true;
|
||||
if (isAWSTranscribeConfigured()) return true;
|
||||
if (whisperClient) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
var provider = getTranscribeProvider();
|
||||
var medical = process.env.AWS_TRANSCRIBE_MEDICAL === 'true';
|
||||
console.log('🎙️ Transcribe provider:', provider + (provider === 'aws' && medical ? ' (Medical)' : ''));
|
||||
var available = isTranscribeAvailable();
|
||||
console.log('🎙️ Transcribe provider:', provider + (provider === 'aws' && medical ? ' (Medical)' : '') + (available ? '' : ' (NOT CONFIGURED — browser speech only)'));
|
||||
|
||||
// Status endpoint — frontend checks this to decide whether to upload audio
|
||||
router.get('/transcribe/status', authMiddleware, (req, res) => {
|
||||
res.json({ available: available, provider: available ? provider : 'none' });
|
||||
});
|
||||
|
||||
router.post('/transcribe', authMiddleware, upload.single('audio'), async (req, res) => {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in a new issue