diff --git a/public/components/admin.html b/public/components/admin.html
index b9dd6323..99ef15d4 100644
--- a/public/components/admin.html
+++ b/public/components/admin.html
@@ -336,7 +336,10 @@
Conversation input budget
-
Loading server budget metadata...
+
+
+
UTF-16 code units. Leave empty to use CLINICAL_ASSISTANT_CONVERSATION_CHARS.
+
Clinical Assistant prompts
diff --git a/public/js/admin/clinicalAssistant.js b/public/js/admin/clinicalAssistant.js
index df6441f5..597535a3 100644
--- a/public/js/admin/clinicalAssistant.js
+++ b/public/js/admin/clinicalAssistant.js
@@ -136,10 +136,15 @@ export function initClinicalAssistantAdmin(adminEscapeHtml) {
setValue('assistant-search-limit', cfg['clinical_assistant.search_limit'] || '8');
setValue('assistant-context-chars', cfg['clinical_assistant.context_chars'] || '1400');
setValue('assistant-translate-provider', ['libretranslate', 'deepl'].includes(cfg['clinical_assistant.translate_provider']) ? cfg['clinical_assistant.translate_provider'] : 'libretranslate');
- var budgetLabel = document.getElementById('assistant-conversation-budget');
- if (budgetLabel) budgetLabel.textContent =
- budget.limit.toLocaleString() + ' ' + budget.unit + ' (' + budget.measure + ') — ' + budget.env +
- (budget.source === 'environment' ? ' (environment).' : ' (server default; environment unset).');
+ var budgetInput = document.getElementById('assistant-conversation-budget');
+ if (budgetInput && budget && Number.isInteger(budget.limit)) {
+ var cfgBudget = cfg['clinical_assistant.conversation_chars'];
+ budgetInput.value = cfgBudget ? String(cfgBudget) : String(budget.limit);
+ }
+ var budgetMeta = document.getElementById('assistant-conversation-budget-meta');
+ if (budgetMeta) budgetMeta.textContent = budget && budget.source === 'environment'
+ ? 'Currently from CLINICAL_ASSISTANT_CONVERSATION_CHARS (' + budget.limit.toLocaleString() + '). Set a value here to override it; clear it to use the environment again.'
+ : 'UTF-16 code units. Leave empty to use CLINICAL_ASSISTANT_CONVERSATION_CHARS.';
configState = 'ready';
updateAssistantLoadState();
loadAssistantImageModels();
@@ -358,6 +363,7 @@ export function initClinicalAssistantAdmin(adminEscapeHtml) {
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.conversation_chars', getValue('assistant-conversation-budget')),
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() {
diff --git a/public/js/clinicalAssistant.js b/public/js/clinicalAssistant.js
index 771e42c7..55d59745 100644
--- a/public/js/clinicalAssistant.js
+++ b/public/js/clinicalAssistant.js
@@ -192,12 +192,12 @@ import {
imageModel: data.imageModel || ''
};
bindModelSelects();
- fillModelSelect(document.getElementById('assistant-chat-model-select'), statusChoices.allowedChatModels, statusChoices.chatModel, selectedChatModel, 'ped_assistant_chat_model');
+ fillModelSelect(document.getElementById('assistant-chat-model-select'), statusChoices.allowedChatModels, statusChoices.chatModel, selectedChatModel, 'ped_assistant_chat_model', 'chat');
var chatSel = document.getElementById('assistant-chat-model-select');
selectedChatModel = chatSel ? chatSel.value : selectedChatModel;
var imageSel = document.getElementById('assistant-image-model-select');
if (imageSel) {
- fillModelSelect(imageSel, statusChoices.allowedImageModels, statusChoices.imageModel, selectedImageModel, 'ped_assistant_image_model');
+ fillModelSelect(imageSel, statusChoices.allowedImageModels, statusChoices.imageModel, selectedImageModel, 'ped_assistant_image_model', 'image');
selectedImageModel = imageSel.value || selectedImageModel;
}
}
@@ -1165,7 +1165,7 @@ import {
var select = modal.querySelector('#create-image-chat');
if (select) select.selectedIndex = 0;
bindModelSelects();
- fillModelSelect(document.getElementById('assistant-image-model-select'), statusChoices.allowedImageModels, statusChoices.imageModel, selectedImageModel, 'ped_assistant_image_model');
+ fillModelSelect(document.getElementById('assistant-image-model-select'), statusChoices.allowedImageModels, statusChoices.imageModel, selectedImageModel, 'ped_assistant_image_model', 'image');
renderCreateImageHistory();
}
@@ -1296,8 +1296,9 @@ import {
else localStorage.removeItem(key);
} catch (e) {}
}
- function fillModelSelect(select, allowed, configured, saved, saveKind) {
+ function fillModelSelect(select, allowed, configured, saved, saveKind, kind) {
if (!select) return;
+ if (kind) select.setAttribute('data-model-select-kind', kind);
select.innerHTML = '';
var def = document.createElement('option');
def.value = '';
@@ -1320,22 +1321,18 @@ import {
if (pill) pill.hidden = !show;
}
function bindModelSelects() {
- var chatSel = document.getElementById('assistant-chat-model-select');
- if (chatSel && !chatSel.dataset.bound) {
- chatSel.dataset.bound = '1';
- chatSel.addEventListener('change', function() {
- selectedChatModel = chatSel.value || '';
- saveModelSelection('ped_assistant_chat_model', selectedChatModel);
- });
- }
- var imageSel = document.getElementById('assistant-image-model-select');
- if (imageSel && !imageSel.dataset.bound) {
- imageSel.dataset.bound = '1';
- imageSel.addEventListener('change', function() {
- selectedImageModel = imageSel.value || '';
- saveModelSelection('ped_assistant_image_model', selectedImageModel);
- });
- }
+ // Delegated persistence: any chat/image model select saves immediately,
+ // even when the popup recreates its element.
+ if (document.dataset.modelSelectsBound) return;
+ document.dataset.modelSelectsBound = '1';
+ document.addEventListener('change', function(e) {
+ var sel = e.target && e.target.closest ? e.target.closest('[data-model-select-kind]') : null;
+ if (!sel) return;
+ var kind = sel.getAttribute('data-model-select-kind');
+ var value = sel.value || '';
+ if (kind === 'chat') { selectedChatModel = value; saveModelSelection('ped_assistant_chat_model', value); }
+ if (kind === 'image') { selectedImageModel = value; saveModelSelection('ped_assistant_image_model', value); }
+ });
}
// ── Saved-chat options: Rename, Pin, Export, Delete (ChatGPT-style menu) ──
diff --git a/src/routes/clinicalAssistant.js b/src/routes/clinicalAssistant.js
index 454d4405..9c78f29a 100644
--- a/src/routes/clinicalAssistant.js
+++ b/src/routes/clinicalAssistant.js
@@ -755,7 +755,14 @@ function isUsefulIndexedTopicExample(item) {
return true;
}
-function getConversationLimit() {
+async function getConversationLimit() {
+ // Admin-set value wins over the environment so the administrator can test
+ // the warning/refusal behavior with a lower limit.
+ var override = await getSetting('clinical_assistant.conversation_chars', '');
+ if (override !== '') {
+ var parsed = parseInt(override, 10);
+ if (Number.isInteger(parsed) && parsed >= 1000 && parsed <= 1000000) return parsed;
+ }
return conversationBudget(process.env).limit;
}