Fix: admin models loading, clear refine/instructions on New, bigger HPI areas, unique labels
- Admin models: reset modelsLoaded flag on error so retry works - Admin default model: fix redundant fetch race condition - clearTab: now clears refine inputs, instructions, and demographic fields - Encounter/dictation clear buttons: also clear refine input - SOAP: instructions textarea already cleared by clearTab (soap-instructions) - Encounter HPI: bigger transcript (400px) and output (600px) text areas - Unique label enforcement: 409 error if saving with duplicate label
This commit is contained in:
parent
50492ff30c
commit
51fc818e4a
7 changed files with 47 additions and 13 deletions
|
|
@ -56,7 +56,7 @@
|
|||
|
||||
<div class="card">
|
||||
<div class="card-header"><h3><i class="fas fa-file-lines"></i> Transcript</h3><button id="enc-clear" class="btn-sm btn-ghost"><i class="fas fa-eraser"></i> Clear</button></div>
|
||||
<div id="enc-transcript" class="editable-box" contenteditable="true" data-placeholder="Transcript appears here or type/paste directly..."></div>
|
||||
<div id="enc-transcript" class="editable-box editable-box-large" contenteditable="true" data-placeholder="Transcript appears here or type/paste directly..."></div>
|
||||
</div>
|
||||
|
||||
<button id="enc-generate-btn" class="btn-generate"><i class="fas fa-wand-magic-sparkles"></i> Generate HPI</button>
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
<button class="btn-sm btn-ghost" data-action="nc-export" data-target="enc-hpi-text" data-label="hpi-encounter"><i class="fas fa-cloud-arrow-up"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="enc-hpi-text" class="output-text" contenteditable="true"></div>
|
||||
<div id="enc-hpi-text" class="output-text output-text-large" contenteditable="true"></div>
|
||||
<div class="refine-bar">
|
||||
<textarea id="enc-refine-input" class="refine-input" rows="2" placeholder="Tell AI to modify (e.g., 'make it shorter', 'add that patient has asthma history')"></textarea>
|
||||
<button class="btn-sm btn-primary" id="enc-refine-btn"><i class="fas fa-edit"></i> Refine</button>
|
||||
|
|
|
|||
|
|
@ -153,6 +153,8 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(--
|
|||
.output-header{background:var(--green-light);border-bottom-color:var(--green);}
|
||||
.output-actions{display:flex;align-items:center;gap:6px;flex-wrap:wrap;}
|
||||
.output-text{padding:20px 24px;font-size:13px;line-height:1.9;outline:none;min-height:480px;white-space:pre-wrap;overflow-y:auto;}
|
||||
.output-text-large{min-height:600px;}
|
||||
.editable-box-large{min-height:400px;}
|
||||
.model-tag{font-size:10px;padding:2px 7px;background:var(--g200);border-radius:4px;color:var(--g600);}
|
||||
|
||||
/* REFINE BAR */
|
||||
|
|
|
|||
|
|
@ -571,7 +571,7 @@
|
|||
fetch('/api/admin/config/models', { headers: getAuthHeaders() })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (!data.success) return;
|
||||
if (!data.success) { modelsLoaded = false; return; }
|
||||
window._adminModelsData = data;
|
||||
var providerEl = document.getElementById('admin-model-provider');
|
||||
if (providerEl) providerEl.textContent = (data.provider || 'openrouter').toUpperCase();
|
||||
|
|
@ -579,7 +579,7 @@
|
|||
renderCustomModels(data.custom || []);
|
||||
populateDefaultModelSelect(data.models || [], data.custom || []);
|
||||
})
|
||||
.catch(function(err) { console.error('[Admin] Models load failed:', err); });
|
||||
.catch(function(err) { modelsLoaded = false; console.error('[Admin] Models load failed:', err); });
|
||||
}
|
||||
|
||||
function renderModelTable(models) {
|
||||
|
|
@ -668,15 +668,22 @@
|
|||
sel.appendChild(opt);
|
||||
});
|
||||
|
||||
// Load current default
|
||||
fetch('/api/admin/config', { headers: getAuthHeaders() })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (!data.success) return;
|
||||
var cfg = {};
|
||||
(data.config || []).forEach(function(row) { cfg[row.key] = row.value; });
|
||||
if (cfg['models.default']) sel.value = cfg['models.default'];
|
||||
});
|
||||
// Load current default from already-loaded config, or fetch if needed
|
||||
var cfg = window._adminEmailConfig || {};
|
||||
if (cfg['models.default']) {
|
||||
sel.value = cfg['models.default'];
|
||||
} else {
|
||||
// Fetch only the default model setting
|
||||
fetch('/api/admin/config', { headers: getAuthHeaders() })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (!data.success) return;
|
||||
(data.config || []).forEach(function(row) {
|
||||
if (row.key === 'models.default' && row.value) sel.value = row.value;
|
||||
});
|
||||
})
|
||||
.catch(function() {});
|
||||
}
|
||||
}
|
||||
|
||||
function addCustomModel() {
|
||||
|
|
|
|||
|
|
@ -468,6 +468,17 @@
|
|||
// Hide output card
|
||||
var out = document.getElementById(outputElMap[type]);
|
||||
if (out) out.classList.add('hidden');
|
||||
// Clear refine input (textarea or input) for every tab
|
||||
var refineEl = document.getElementById(pfx + '-refine-input');
|
||||
if (refineEl) refineEl.value = '';
|
||||
// Clear instructions textarea (SOAP, hospital, etc.)
|
||||
var instrEl = document.getElementById(pfx + '-instructions');
|
||||
if (instrEl) instrEl.value = '';
|
||||
// Clear demographic fields
|
||||
var ageEl = document.getElementById(pfx + '-age');
|
||||
if (ageEl) ageEl.value = '';
|
||||
var genderEl = document.getElementById(pfx + '-gender');
|
||||
if (genderEl) genderEl.value = '';
|
||||
// Reset saved ID and idempotency key (memory + sessionStorage)
|
||||
window['_savedEncId_' + type] = null;
|
||||
try { sessionStorage.removeItem('_savedEncId_' + type); } catch(e) {}
|
||||
|
|
|
|||
|
|
@ -131,6 +131,8 @@
|
|||
window._savedEncId_encounter = null;
|
||||
var labelEl = document.getElementById('enc-label');
|
||||
if (labelEl) labelEl.value = '';
|
||||
var refineEl = document.getElementById('enc-refine-input');
|
||||
if (refineEl) refineEl.value = '';
|
||||
});
|
||||
|
||||
generateBtn.addEventListener('click', function() {
|
||||
|
|
|
|||
|
|
@ -126,6 +126,8 @@
|
|||
window._savedEncId_dictation = null;
|
||||
var labelEl = document.getElementById('dict-label');
|
||||
if (labelEl) labelEl.value = '';
|
||||
var refineEl = document.getElementById('dict-refine-input');
|
||||
if (refineEl) refineEl.value = '';
|
||||
});
|
||||
|
||||
generateBtn.addEventListener('click', function() {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,16 @@ router.post('/encounters/saved', async function(req, res) {
|
|||
);
|
||||
res.json({ success: true, id: id });
|
||||
} else {
|
||||
// Enforce unique label per user (within active/non-expired encounters)
|
||||
if (label && label.trim()) {
|
||||
var labelDup = await db.get(
|
||||
"SELECT id FROM saved_encounters WHERE user_id = $1 AND LOWER(label) = LOWER($2) AND expires_at > NOW()",
|
||||
[req.user.id, label.trim()]
|
||||
);
|
||||
if (labelDup) {
|
||||
return res.status(409).json({ error: 'An encounter with this label already exists. Use a unique label or load the existing one.' });
|
||||
}
|
||||
}
|
||||
// Check for duplicate via idempotency_key
|
||||
if (idempotency_key) {
|
||||
var dup = await db.get(
|
||||
|
|
|
|||
Loading…
Reference in a new issue