// ============================================================ // AUDIO BACKUP — IndexedDB-based audio recording backup // Saves audio blobs locally so failed transcriptions can be retried // ============================================================ (function() { var DB_NAME = 'PedScribeAudioBackup'; var STORE_NAME = 'recordings'; var DB_VERSION = 1; var MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours var _db = null; function openDB() { if (_db) return Promise.resolve(_db); return new Promise(function(resolve, reject) { var request = indexedDB.open(DB_NAME, DB_VERSION); request.onupgradeneeded = function(e) { var db = e.target.result; if (!db.objectStoreNames.contains(STORE_NAME)) { var store = db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true }); store.createIndex('timestamp', 'timestamp', { unique: false }); } }; request.onsuccess = function(e) { _db = e.target.result; resolve(_db); }; request.onerror = function() { reject(new Error('IndexedDB open failed')); }; }); } // Save audio blob with metadata window.saveAudioBackup = function(blob, module) { return openDB().then(function(db) { return new Promise(function(resolve, reject) { var tx = db.transaction(STORE_NAME, 'readwrite'); var store = tx.objectStore(STORE_NAME); var record = { blob: blob, module: module || 'unknown', timestamp: Date.now(), size: blob.size, mimeType: blob.type }; var req = store.add(record); req.onsuccess = function() { resolve(req.result); }; // returns ID req.onerror = function() { reject(new Error('Failed to save audio backup')); }; }); }).then(function(id) { window._lastAudioBackupId = id; cleanupOldBackups(); return id; }).catch(function(err) { console.warn('[AudioBackup] Save failed:', err.message); return null; }); }; // Delete a specific backup window.deleteAudioBackup = function(id) { return openDB().then(function(db) { return new Promise(function(resolve) { var tx = db.transaction(STORE_NAME, 'readwrite'); tx.objectStore(STORE_NAME).delete(id); tx.oncomplete = function() { resolve(); }; tx.onerror = function() { resolve(); }; }); }).catch(function() {}); }; // Get all backups (for UI listing) window.getAudioBackups = function() { return openDB().then(function(db) { return new Promise(function(resolve) { var tx = db.transaction(STORE_NAME, 'readonly'); var store = tx.objectStore(STORE_NAME); var req = store.getAll(); req.onsuccess = function() { var records = (req.result || []).filter(function(r) { return (Date.now() - r.timestamp) < MAX_AGE_MS; }); resolve(records); }; req.onerror = function() { resolve([]); }; }); }).catch(function() { return []; }); }; // Retry transcription from backup window.retryAudioBackup = function(id) { return openDB().then(function(db) { return new Promise(function(resolve, reject) { var tx = db.transaction(STORE_NAME, 'readonly'); var req = tx.objectStore(STORE_NAME).get(id); req.onsuccess = function() { if (!req.result) { reject(new Error('Backup not found')); return; } resolve(req.result); }; req.onerror = function() { reject(new Error('Failed to read backup')); }; }); }).then(function(record) { showLoading('Re-transcribing audio backup...'); window._lastAudioBackupId = id; return transcribeAudio(record.blob).then(function(data) { hideLoading(); if (data.success) { showToast('Backup transcribed successfully!', 'success'); // Copy to clipboard if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(data.text); showToast('Transcript copied to clipboard', 'info'); } } else { showToast('Retry failed: ' + (data.error || 'unknown'), 'error'); } return data; }); }); }; // Clean up old backups function cleanupOldBackups() { openDB().then(function(db) { var tx = db.transaction(STORE_NAME, 'readwrite'); var store = tx.objectStore(STORE_NAME); var index = store.index('timestamp'); var cutoff = Date.now() - MAX_AGE_MS; var range = IDBKeyRange.upperBound(cutoff); var req = index.openCursor(range); req.onsuccess = function(e) { var cursor = e.target.result; if (cursor) { cursor.delete(); cursor.continue(); } }; }).catch(function() {}); } // Render audio backups list in settings window.renderAudioBackups = function() { var container = document.getElementById('audio-backups-list'); if (!container) return; getAudioBackups().then(function(backups) { if (backups.length === 0) { container.innerHTML = '

No audio backups. Recordings are saved here automatically and kept for 24 hours.

'; return; } container.innerHTML = backups.map(function(b) { var date = new Date(b.timestamp); var sizeKb = Math.round(b.size / 1024); var age = Math.round((Date.now() - b.timestamp) / 60000); var ageStr = age < 60 ? age + 'm ago' : Math.round(age / 60) + 'h ago'; return '
' + '
' + '
' + esc(b.module) + ' recording
' + '
' + date.toLocaleString() + ' · ' + sizeKb + ' KB · ' + ageStr + '
' + '
' + '' + '' + '
'; }).join(''); container.querySelectorAll('.audio-backup-retry').forEach(function(btn) { btn.addEventListener('click', function() { retryAudioBackup(parseInt(btn.dataset.id)); }); }); container.querySelectorAll('.audio-backup-delete').forEach(function(btn) { btn.addEventListener('click', function() { deleteAudioBackup(parseInt(btn.dataset.id)).then(function() { showToast('Backup deleted', 'info'); renderAudioBackups(); }); }); }); }); }; function esc(str) { if (!str) return ''; return String(str).replace(/&/g, '&').replace(//g, '>'); } // Clean up on load cleanupOldBackups(); console.log('Audio backup system loaded'); })();