Four changes batched: 1. ED Encounters tab (new) — multi-stage emergency note with don't-miss tooltips and 2023 E/M MDM finalize. New route /api/ed-encounters (generate per-stage + finalize MDM), new ed-encounters.js owning all client logic, new ed-encounter.html component, new template_ed memory category. Persists draft to localStorage every keystroke and to saved_encounters on stage advance. encounters.js touched only to register the new tab in sessionStorage restore + tabMap (save and idempotency code untouched). 2. Notes model selector — /notes/from-voice now accepts a client-supplied model (validated by the existing callAI allow-list); falls back to the admin default. Added <select class="tab-model-select"> to notes.html so the existing app.js populator handles options + default. 3. Remove AI-learning-from-corrections — deleted correctionTracker.js, POST /memories/correction, the corrections branch in /memories/context, the settings UI section, the FAQ entry, and all dead trackAIOutput/saveCorrection guards in callers. Legacy correction_* DB rows are filtered (NOT LIKE) rather than dropped, so no destructive migration. 4. Fix notes AI framing — /notes/from-voice prompt no longer assumes "physician dictation". Plain notes (shopping lists, reminders, ideas) now match the dictation tone instead of being forced into clinical structure. All 46 tests pass.
180 lines
7.5 KiB
JavaScript
180 lines
7.5 KiB
JavaScript
// ============================================================
|
|
// 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',
|
|
template_ed: 'ED Template',
|
|
custom: 'Custom'
|
|
};
|
|
|
|
// ── 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;
|
|
if (_memories.length === 0) {
|
|
container.innerHTML = '<p style="color:var(--g400);font-size:13px;">No templates saved yet. Add one above.</p>';
|
|
return;
|
|
}
|
|
container.innerHTML = _memories.map(function(m) {
|
|
var catLabel = CATEGORY_LABELS[m.category] || m.category;
|
|
var preview = (m.content || '').substring(0, 100).replace(/\n/g, ' ');
|
|
return '<div class="mem-item" data-id="' + m.id + '">' +
|
|
'<div class="mem-item-info">' +
|
|
'<div style="display:flex;align-items:center;gap:6px;margin-bottom:2px;">' +
|
|
'<span class="mem-item-name">' + esc(m.name) + '</span>' +
|
|
'<span class="mem-item-cat">' + esc(catLabel) + '</span>' +
|
|
'</div>' +
|
|
'<div class="mem-item-preview">' + esc(preview) + (m.content && m.content.length > 100 ? '...' : '') + '</div>' +
|
|
'</div>' +
|
|
'<button class="btn-sm btn-ghost mem-edit-btn" data-id="' + m.id + '" title="Edit"><i class="fas fa-pen"></i></button>' +
|
|
'<button class="btn-sm btn-ghost mem-delete-btn" data-id="' + m.id + '" style="color:var(--red);" title="Delete"><i class="fas fa-trash"></i></button>' +
|
|
'</div>';
|
|
}).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)); });
|
|
});
|
|
}
|
|
|
|
// ── 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 = '<i class="fas fa-check"></i> 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 = '<i class="fas fa-times"></i> 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 = '<i class="fas fa-plus"></i> 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; });
|
|
showConfirm('Delete template "' + (mem ? mem.name : '') + '"?', function() {
|
|
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'); });
|
|
}, { danger: true, confirmText: 'Delete' });
|
|
}
|
|
|
|
// ── 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, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
console.log('✅ Memories module loaded');
|
|
|
|
})();
|