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 ? ' Resume' : ' 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 }; }