// ============================================================ // MEMORIES.JS — User template/memory storage + settings UI // ============================================================ (function() { var _memories = []; var _editingId = null; var CATEGORY_LABELS = { physical_exam: 'Physical Exam', ros: 'Review of Systems', encounter_format: 'Encounter Format', family_history: 'Family History', assessment_plan: 'Assessment & Plan', template_soap: 'SOAP Template', template_hpi: 'HPI Template', template_wellvisit: 'Well Visit Template', template_sickvisit: 'Sick Visit Template', custom: 'Custom' }; // Correction categories (hidden from manual editing, managed by correction tracker) var CORRECTION_LABELS = { correction_soap: 'SOAP Correction', correction_hpi: 'HPI Correction', correction_encounter: 'Encounter Correction', correction_wellvisit: 'Well Visit Correction', correction_sickvisit: 'Sick Visit Correction' }; // ── Load memories ──────────────────────────────────────────────────────── function loadMemories() { fetch('/api/memories', { headers: getAuthHeaders() }) .then(function(r) { return r.json(); }) .then(function(data) { if (!data.success) return; _memories = data.memories || []; renderMemoryList(); }) .catch(function(err) { console.error('[Memories] Load failed:', err); }); } function renderMemoryList() { var container = document.getElementById('mem-list'); if (!container) return; // Separate templates from corrections var templates = _memories.filter(function(m) { return !m.category.startsWith('correction_'); }); var corrections = _memories.filter(function(m) { return m.category.startsWith('correction_'); }); // Render corrections list renderCorrectionsList(corrections); if (templates.length === 0) { container.innerHTML = '

No templates saved yet. Add one above.

'; return; } container.innerHTML = templates.map(function(m) { var catLabel = CATEGORY_LABELS[m.category] || m.category; var preview = (m.content || '').substring(0, 100).replace(/\n/g, ' '); return '
' + '
' + '
' + '' + esc(m.name) + '' + '' + esc(catLabel) + '' + '
' + '
' + esc(preview) + (m.content && m.content.length > 100 ? '...' : '') + '
' + '
' + '' + '' + '
'; }).join(''); container.querySelectorAll('.mem-edit-btn').forEach(function(btn) { btn.addEventListener('click', function() { editMemory(parseInt(btn.dataset.id)); }); }); container.querySelectorAll('.mem-delete-btn').forEach(function(btn) { btn.addEventListener('click', function() { deleteMemory(parseInt(btn.dataset.id)); }); }); } function renderCorrectionsList(corrections) { var container = document.getElementById('corrections-list'); if (!container) return; if (corrections.length === 0) { container.innerHTML = '

No corrections yet. Edit AI-generated notes and save to start learning.

'; return; } container.innerHTML = corrections.map(function(m) { var catLabel = CORRECTION_LABELS[m.category] || m.category; var preview = (m.name || '').replace(/\n/g, ' '); // Parse original and corrected from content var parts = parseCorrection(m.content || ''); var date = m.created_at ? new Date(m.created_at).toLocaleDateString() : ''; return '
' + '
' + '' + '' + esc(catLabel) + '' + '' + esc(preview) + '' + '' + date + '' + '' + '
' + '' + '
'; }).join(''); // Toggle expand/collapse container.querySelectorAll('.correction-header').forEach(function(header) { header.addEventListener('click', function(e) { if (e.target.closest('.correction-delete-btn')) return; var id = header.dataset.toggle; var detail = document.getElementById('detail-' + id); var arrow = document.getElementById('arrow-' + id); if (detail) { detail.classList.toggle('hidden'); if (arrow) arrow.style.transform = detail.classList.contains('hidden') ? '' : 'rotate(90deg)'; } }); }); container.querySelectorAll('.correction-delete-btn').forEach(function(btn) { btn.addEventListener('click', function(e) { e.stopPropagation(); deleteMemory(parseInt(btn.dataset.id)); }); }); } function parseCorrection(content) { var original = ''; var corrected = ''; var idx = content.indexOf('\nCORRECTED TO: '); if (idx !== -1) { original = content.substring(0, idx).replace(/^ORIGINAL:\s*/i, ''); corrected = content.substring(idx + '\nCORRECTED TO: '.length); } else { original = content; } return { original: original.trim(), corrected: corrected.trim() }; } // ── Save memory ────────────────────────────────────────────────────────── function saveMemory() { var name = document.getElementById('mem-name').value.trim(); var category = document.getElementById('mem-category').value; var content = document.getElementById('mem-content').value.trim(); if (!name) { showToast('Enter a template name', 'error'); return; } if (!content) { showToast('Enter template content', 'error'); return; } var url = _editingId ? '/api/memories/' + _editingId : '/api/memories'; var method = _editingId ? 'PUT' : 'POST'; fetch(url, { method: method, headers: getAuthHeaders(), body: JSON.stringify({ name: name, category: category, content: content }) }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { showToast(_editingId ? 'Template updated' : 'Template saved', 'success'); cancelEdit(); loadMemories(); } else { showToast(data.error || 'Save failed', 'error'); } }) .catch(function() { showToast('Save failed', 'error'); }); } function editMemory(id) { var mem = _memories.find(function(m) { return m.id === id; }); if (!mem) return; _editingId = id; document.getElementById('mem-name').value = mem.name; document.getElementById('mem-category').value = mem.category; document.getElementById('mem-content').value = mem.content; var btn = document.getElementById('btn-mem-save'); if (btn) btn.innerHTML = ' Update Template'; // Add cancel button if not present if (!document.getElementById('btn-mem-cancel')) { var cancel = document.createElement('button'); cancel.id = 'btn-mem-cancel'; cancel.className = 'btn-sm btn-ghost'; cancel.innerHTML = ' Cancel'; cancel.style.marginLeft = '6px'; cancel.addEventListener('click', cancelEdit); if (btn && btn.parentNode) btn.parentNode.appendChild(cancel); } document.getElementById('mem-name').focus(); } function cancelEdit() { _editingId = null; document.getElementById('mem-name').value = ''; document.getElementById('mem-content').value = ''; var btn = document.getElementById('btn-mem-save'); if (btn) btn.innerHTML = ' Add Template'; var cancel = document.getElementById('btn-mem-cancel'); if (cancel) cancel.remove(); } function deleteMemory(id) { var mem = _memories.find(function(m) { return m.id === id; }); if (!confirm('Delete template "' + (mem ? mem.name : '') + '"?')) return; fetch('/api/memories/' + id, { method: 'DELETE', headers: getAuthHeaders() }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { showToast('Template deleted', 'info'); loadMemories(); } else showToast(data.error || 'Delete failed', 'error'); }) .catch(function() { showToast('Delete failed', 'error'); }); } // ── Event wiring ───────────────────────────────────────────────────────── // Expose so auth.js can call on settings tab navigation window.loadMemories = loadMemories; document.addEventListener('click', function(e) { // Settings button or settings tab — load memories if (e.target.closest('#btn-settings') || (e.target.closest('.tab-btn') && e.target.closest('.tab-btn[data-tab="settings"]'))) { setTimeout(loadMemories, 100); } // Save memory button if (e.target.closest('#btn-mem-save')) { saveMemory(); } }); // ── Expose context for AI generation ──────────────────────────────────── // Call this before any AI generation to get user memory context. // Always fetches from the API so it works even if _memories cache is empty. window.getUserMemoryContext = function() { return fetch('/api/memories/context', { headers: getAuthHeaders() }) .then(function(r) { return r.json(); }) .then(function(data) { return data.success ? (data.context || '') : ''; }) .catch(function() { return ''; }); }; function esc(str) { if (!str) return ''; return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } console.log('✅ Memories module loaded'); })();