pediatric-ai-scribe-v3/public/js/notes/recorder.js
2026-05-08 02:44:22 +02:00

155 lines
5.8 KiB
JavaScript

export function createNotesRecorder(options) {
options = options || {};
var recorder = null;
var recTimer = null;
var recPaused = false;
var recActive = false;
function start() {
if (recActive) return;
if (typeof options.ensureEditor === 'function') options.ensureEditor();
if (typeof AudioRecorder === 'undefined') { updateStatus('Recorder unavailable', 'err'); return; }
recorder = new AudioRecorder();
recorder.start().then(function() {
recActive = true; recPaused = false;
setUI('recording');
startTimer();
}).catch(function(err) {
updateStatus('Mic denied: ' + (err && err.message || ''), 'err');
});
}
function togglePause() {
if (!recActive || !recorder || !recorder.mediaRecorder) return;
if (!recPaused) {
try { recorder.mediaRecorder.pause(); } catch (e) {}
recPaused = true; stopTimer(); setUI('paused');
} else {
try { recorder.mediaRecorder.resume(); } catch (e) {
try {
if (recorder.stream && recorder.stream.active) {
var mime = MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm';
recorder.mediaRecorder = new MediaRecorder(recorder.stream, { mimeType: mime, audioBitsPerSecond: 32000 });
recorder.mediaRecorder.ondataavailable = function(ev) { if (ev.data.size > 0) recorder.chunks.push(ev.data); };
recorder.mediaRecorder.start(1000);
}
} catch (e2) {}
}
recPaused = false; startTimer(); setUI('recording');
}
}
function stop(silent) {
if (!recActive || !recorder) {
if (!silent) setUI('idle');
return;
}
recActive = false; recPaused = false;
stopTimer(true);
if (silent) {
try { recorder.mediaRecorder && recorder.mediaRecorder.state !== 'inactive' && recorder.mediaRecorder.stop(); } catch (e) {}
try { recorder.stream && recorder.stream.getTracks().forEach(function(t) { t.stop(); }); } catch (e) {}
setUI('idle');
return;
}
setUI('processing');
updateStatus('Transcribing...', 'saving');
recorder.stop().then(function(blob) {
if (!blob || blob.size === 0) { updateStatus('Nothing recorded', 'err'); setUI('idle'); return; }
if (typeof transcribeAudio !== 'function') { updateStatus('Transcription unavailable', 'err'); setUI('idle'); return; }
return transcribeAudio(blob).then(function(resp) {
if (!resp || !resp.success || !resp.text) {
var msg = (resp && (resp.error || (resp.noProvider ? 'No STT provider configured' : null))) || 'Transcription failed';
updateStatus(msg, 'err'); setUI('idle'); return;
}
updateStatus('Generating note...', 'saving');
var modelEl = document.getElementById('notes-model-select');
var selectedModel = modelEl ? modelEl.value : '';
return options.noteFromVoice({ transcript: resp.text, model: selectedModel || undefined })
.then(function(data) {
setUI('idle');
if (!data.success) { updateStatus(data.error || 'Generation failed', 'err'); return; }
if (typeof options.applyGeneratedNote === 'function') options.applyGeneratedNote(data.title || 'Voice note', data.body || '');
updateStatus('Generated - autosaving...', 'ok');
if (typeof options.markDirty === 'function') options.markDirty();
if (typeof options.scheduleAutosave === 'function') options.scheduleAutosave();
});
});
}).catch(function(err) {
setUI('idle');
updateStatus(err.message || 'Recording failed', 'err');
});
}
function isActive() {
return recActive;
}
function startTimer() {
var el = getEl('notes-rec-timer');
if (recTimer || !el) return;
recTimer = createTimerLite(el);
recTimer.start();
}
function stopTimer(reset) {
if (recTimer) recTimer.stop();
if (reset && recTimer) { recTimer.reset(); recTimer = null; }
}
function createTimerLite(el) {
var s = 0, iv = null;
function paint() { el.textContent = String(Math.floor(s/60)).padStart(2,'0') + ':' + String(s%60).padStart(2,'0'); }
return {
start: function() { paint(); if (!iv) iv = setInterval(function() { s++; paint(); }, 1000); },
stop: function() { if (iv) { clearInterval(iv); iv = null; } },
reset: function() { s = 0; paint(); }
};
}
function setUI(state) {
var startBtn = getEl('btn-note-rec-start');
var pause = getEl('btn-note-rec-pause');
var stopBtn = getEl('btn-note-rec-stop');
var ind = getEl('notes-rec-indicator');
var stateEl = getEl('notes-rec-state');
var dot = ind ? ind.querySelector('.pulse-dot') : null;
if (!startBtn) return;
var idle = (state === 'idle');
var recording = (state === 'recording');
var paused = (state === 'paused');
var processing = (state === 'processing');
startBtn.classList.toggle('hidden', !idle);
pause.classList.toggle('hidden', !(recording || paused));
stopBtn.classList.toggle('hidden', !(recording || paused));
ind.classList.toggle('hidden', idle);
if (pause) pause.innerHTML = paused ? '<i class="fas fa-play"></i> Resume' : '<i class="fas fa-pause"></i> Pause';
if (stateEl) stateEl.textContent = processing ? 'Processing...' : (paused ? 'Paused' : 'Recording');
if (dot) dot.classList.toggle('paused', paused || processing);
startBtn.disabled = !idle;
pause.disabled = processing;
stopBtn.disabled = processing;
}
function getEl(id) {
if (typeof options.getEl === 'function') return options.getEl(id);
return document.getElementById(id);
}
function updateStatus(text, kind) {
if (typeof options.updateStatus === 'function') options.updateStatus(text, kind);
}
return {
start: start,
togglePause: togglePause,
stop: stop,
isActive: isActive,
setUI: setUI
};
}