// ============================================================ // MODULE 1: LIVE ENCOUNTER → HPI (separate from milestones) // ============================================================ (function() { var recordBtn = document.getElementById('enc-record-btn'); var indicator = document.getElementById('enc-recording-indicator'); var timerEl = document.getElementById('enc-timer'); var transcript = document.getElementById('enc-transcript'); var clearBtn = document.getElementById('enc-clear-transcript'); var generateBtn = document.getElementById('enc-generate-btn'); var outputCard = document.getElementById('enc-output'); var hpiText = document.getElementById('enc-hpi-text'); var modelUsed = document.getElementById('enc-model-used'); var recorder = new AudioRecorder(); var timer = createTimer(timerEl); var isRecording = false; // Web Speech for live preview var recognition = null; var finalText = ''; if (window.SpeechRecognition || window.webkitSpeechRecognition) { var SR = window.SpeechRecognition || window.webkitSpeechRecognition; recognition = new SR(); recognition.continuous = true; recognition.interimResults = true; recognition.lang = 'en-US'; recognition.onresult = function(e) { var interim = ''; for (var i = e.resultIndex; i < e.results.length; i++) { if (e.results[i].isFinal) { finalText += e.results[i][0].transcript + ' '; } else { interim = e.results[i][0].transcript; } } transcript.innerHTML = finalText + (interim ? '' + interim + '' : ''); }; recognition.onend = function() { if (isRecording) { try { recognition.start(); } catch(e) {} } }; recognition.onerror = function() {}; } recordBtn.addEventListener('click', function() { if (!isRecording) { finalText = ''; recorder.start().then(function() { isRecording = true; recordBtn.classList.add('recording'); recordBtn.querySelector('span').textContent = 'Stop Recording'; indicator.classList.remove('hidden'); timer.start(); if (recognition) { try { recognition.start(); } catch(e) {} } showToast('Recording started', 'info'); }).catch(function() { showToast('Microphone access denied', 'error'); }); } else { isRecording = false; var duration = timer.stop(); recordBtn.classList.remove('recording'); recordBtn.querySelector('span').textContent = 'Start Recording'; indicator.classList.add('hidden'); if (recognition) { try { recognition.stop(); } catch(e) {} } showLoading('Transcribing audio with Whisper...'); recorder.stop().then(function(blob) { if (!blob || blob.size === 0) { hideLoading(); showToast('No audio captured', 'error'); return; } var formData = new FormData(); formData.append('audio', blob, 'encounter.webm'); return fetch('/api/transcribe', { method: 'POST', body: formData }) .then(function(r) { return r.json(); }) .then(function(data) { hideLoading(); if (data.success) { transcript.textContent = data.text; showToast('Transcribed ' + duration + 's of audio', 'success'); } else { showToast(data.error || 'Transcription failed', 'error'); // Keep the live transcript if whisper failed if (finalText.trim()) { transcript.textContent = finalText.trim(); } } }); }).catch(function(err) { hideLoading(); showToast('Error: ' + err.message, 'error'); if (finalText.trim()) { transcript.textContent = finalText.trim(); } }); } }); clearBtn.addEventListener('click', function() { transcript.textContent = ''; finalText = ''; outputCard.classList.add('hidden'); }); generateBtn.addEventListener('click', function() { var text = transcript.innerText.trim(); if (!text) { showToast('No transcript to process', 'error'); return; } showLoading('Generating HPI from encounter...'); fetch('/api/generate-hpi-encounter', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ transcript: text, patientAge: document.getElementById('enc-patient-age').value, patientGender: document.getElementById('enc-patient-gender').value, model: getSelectedModel() }) }) .then(function(r) { return r.json(); }) .then(function(data) { hideLoading(); if (data.success) { hpiText.textContent = data.hpi; modelUsed.textContent = (data.model || '').split('/').pop(); outputCard.classList.remove('hidden'); outputCard.scrollIntoView({ behavior: 'smooth' }); showToast('HPI generated!', 'success'); } else { showToast(data.error || 'Generation failed', 'error'); } }) .catch(function(err) { hideLoading(); showToast('Error: ' + err.message, 'error'); }); }); console.log('✅ Live Encounter module loaded'); })();