diff --git a/public/components/admin.html b/public/components/admin.html index fdb227f6..b9dd6323 100644 --- a/public/components/admin.html +++ b/public/components/admin.html @@ -292,6 +292,20 @@
+
+ Model availability +
+

Models users may select in the Clinical Assistant. Leave a list empty to keep only the configured model.

+
+
Chat models
+
+
+
+
Image models
+
+
+
+
Starter prompt pool
diff --git a/public/js/admin/clinicalAssistant.js b/public/js/admin/clinicalAssistant.js index 322f03b1..df6441f5 100644 --- a/public/js/admin/clinicalAssistant.js +++ b/public/js/admin/clinicalAssistant.js @@ -1,5 +1,52 @@ export function initClinicalAssistantAdmin(adminEscapeHtml) { let configState = 'idle'; + const IMAGE_MODEL_FALLBACKS = ['openai-gpt-image-1', 'openai-gpt-image-1-mini', 'openai-gpt-image-1.5', 'openai-dall-e-3']; + let savedChatAllowed = []; + let savedImageAllowed = []; + let chatRoster = []; + let imageRoster = []; + + function parseAssistantList(value) { + return String(value || '').split(',').map(function(s) { return s.trim(); }).filter(Boolean); + } + function renderAssistantCheckboxList(containerId, candidates, saved) { + var container = document.getElementById(containerId); + if (!container) return; + var ids = candidates.slice(); + saved.forEach(function(id) { if (ids.indexOf(id) === -1) ids.push(id); }); + ids = ids.filter(Boolean).filter(function(id, i, all) { return all.indexOf(id) === i; }).sort(); + container.innerHTML = ''; + if (!ids.length) { + var empty = document.createElement('p'); + empty.style.cssText = 'margin:0;font-size:12px;color:var(--g500);'; + empty.textContent = 'No models available. Add or enable models first.'; + container.appendChild(empty); + return; + } + ids.forEach(function(id) { + var row = document.createElement('label'); + row.style.cssText = 'display:flex;align-items:center;gap:6px;font-size:12px;padding:2px 0;'; + var box = document.createElement('input'); + box.type = 'checkbox'; + box.value = id; + box.checked = saved.indexOf(id) !== -1; + var span = document.createElement('span'); + span.textContent = id; + row.appendChild(box); + row.appendChild(span); + container.appendChild(row); + }); + } + function checkedAssistantModels(containerId) { + var container = document.getElementById(containerId); + if (!container) return []; + return Array.prototype.map.call(container.querySelectorAll('input[type="checkbox"]:checked'), function(box) { return box.value; }); + } + function renderAssistantImageModelCheckboxes(models) { + imageRoster = (Array.isArray(models) ? models : []).map(function(m) { return m.id; }).concat(IMAGE_MODEL_FALLBACKS, [window._assistantImageModelValue]); + renderAssistantCheckboxList('assistant-allowed-image-models', imageRoster, savedImageAllowed); + } + let imageModelsLoading = false; document.addEventListener('tabChanged', function(e) { @@ -80,6 +127,11 @@ export function initClinicalAssistantAdmin(adminEscapeHtml) { chatSelect.value = savedChatModel; } window._assistantImageModelValue = cfg['clinical_assistant.image_model'] || ''; + savedChatAllowed = parseAssistantList(cfg['clinical_assistant.allowed_models']); + savedImageAllowed = parseAssistantList(cfg['clinical_assistant.allowed_image_models']); + chatRoster = chatOptions().map(function(o) { return o.value; }).filter(Boolean).concat([cfg['clinical_assistant.chat_model'] || '']); + renderAssistantCheckboxList('assistant-allowed-chat-models', chatRoster, savedChatAllowed); + renderAssistantImageModelCheckboxes([]); renderAssistantImageModels([], window._assistantImageModelValue); setValue('assistant-search-limit', cfg['clinical_assistant.search_limit'] || '8'); setValue('assistant-context-chars', cfg['clinical_assistant.context_chars'] || '1400'); @@ -135,6 +187,7 @@ export function initClinicalAssistantAdmin(adminEscapeHtml) { .then(function(data) { if (!data || data.success === false || !Array.isArray(data.models) || !data.models.every(function(m) { return m && typeof m.id === 'string' && m.id; })) throw new Error('Invalid image models'); + renderAssistantImageModelCheckboxes(data.models); renderAssistantImageModels(data.models, sel.value); }) .catch(function() { @@ -304,7 +357,9 @@ export function initClinicalAssistantAdmin(adminEscapeHtml) { putAssistantConfig('clinical_assistant.chat_model', getValue('assistant-chat-model')), putAssistantConfig('clinical_assistant.search_limit', getValue('assistant-search-limit') || '8'), putAssistantConfig('clinical_assistant.context_chars', getValue('assistant-context-chars') || '1400'), - putAssistantConfig('clinical_assistant.translate_provider', getValue('assistant-translate-provider') || 'libretranslate') + putAssistantConfig('clinical_assistant.translate_provider', getValue('assistant-translate-provider') || 'libretranslate'), + putAssistantConfig('clinical_assistant.allowed_models', checkedAssistantModels('assistant-allowed-chat-models').join(',')), + putAssistantConfig('clinical_assistant.allowed_image_models', checkedAssistantModels('assistant-allowed-image-models').join(',')) ]).then(function() { if (status) status.textContent = ''; showToast('Assistant settings saved', 'success'); @@ -322,6 +377,12 @@ export function initClinicalAssistantAdmin(adminEscapeHtml) { return data; }); } + function chatOptions() { + var chat = document.getElementById('assistant-chat-model'); + var out = []; + if (chat) Array.prototype.forEach.call(chat.options, function(o) { out.push({ value: o.value }); }); + return out; + } function getValue(id) { var el = document.getElementById(id); return el ? el.value.trim() : ''; } function setValue(id, value) { var el = document.getElementById(id); if (el) el.value = value; } const escAssistant = adminEscapeHtml;