diff --git a/public/components/admin.html b/public/components/admin.html
index 99ef15d..6864e11 100644
--- a/public/components/admin.html
+++ b/public/components/admin.html
@@ -337,8 +337,8 @@
Conversation input budget
-
-
UTF-16 code units. Leave empty to use CLINICAL_ASSISTANT_CONVERSATION_CHARS.
+
+
Controlled by CLINICAL_ASSISTANT_CONVERSATION_CHARS — saved settings cannot change it.
diff --git a/public/js/admin/clinicalAssistant.js b/public/js/admin/clinicalAssistant.js
index 597535a..5b476c0 100644
--- a/public/js/admin/clinicalAssistant.js
+++ b/public/js/admin/clinicalAssistant.js
@@ -138,13 +138,13 @@ export function initClinicalAssistantAdmin(adminEscapeHtml) {
setValue('assistant-translate-provider', ['libretranslate', 'deepl'].includes(cfg['clinical_assistant.translate_provider']) ? cfg['clinical_assistant.translate_provider'] : 'libretranslate');
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);
+ budgetInput.value = String(budget.limit);
+ budgetInput.readOnly = true;
}
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.';
+ if (budgetMeta) budgetMeta.textContent = budget && Number.isInteger(budget.limit)
+ ? 'Controlled by CLINICAL_ASSISTANT_CONVERSATION_CHARS (' + budget.limit.toLocaleString() + ' UTF-16 code units). Saved settings cannot change it.'
+ : 'Controlled by CLINICAL_ASSISTANT_CONVERSATION_CHARS — saved settings cannot change it.';
configState = 'ready';
updateAssistantLoadState();
loadAssistantImageModels();
@@ -363,7 +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/src/routes/clinicalAssistant.js b/src/routes/clinicalAssistant.js
index 894db98..d6ed7c7 100644
--- a/src/routes/clinicalAssistant.js
+++ b/src/routes/clinicalAssistant.js
@@ -757,13 +757,8 @@ function isUsefulIndexedTopicExample(item) {
}
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;
- }
+ // The conversation budget is controlled by CLINICAL_ASSISTANT_CONVERSATION_CHARS
+ // only. Saved settings never change it (adminConfig.js rejects the key).
return conversationBudget(process.env).limit;
}
diff --git a/test/admin-clinical-assistant-wiring.test.js b/test/admin-clinical-assistant-wiring.test.js
index 03bd6a0..931108b 100644
--- a/test/admin-clinical-assistant-wiring.test.js
+++ b/test/admin-clinical-assistant-wiring.test.js
@@ -54,9 +54,11 @@ test('native admin initializer preserves lazy navigation, assistant actions and
assert.match(document.getElementById('assistant-prompt-pool-status').textContent, /3 prompts/);
const budget = document.getElementById('assistant-conversation-budget');
assert.equal(document.querySelectorAll('#assistant-conversation-chars').length, 0);
- assert.equal(budget.type, 'number', 'the conversation budget is an editable admin input');
- assert.equal(budget.value, '999999', 'prefilled with the server-reported limit');
+ assert.equal(budget.type, 'number', 'the conversation budget is an admin input');
+ assert.equal(budget.readOnly, true, 'the budget is read-only: the env var controls it');
+ assert.equal(budget.value, '240000', 'prefilled with the environment limit');
assert.match(document.getElementById('assistant-conversation-budget-meta').textContent, /CLINICAL_ASSISTANT_CONVERSATION_CHARS/);
+ assert.match(document.getElementById('assistant-conversation-budget-meta').textContent, /cannot change it/);
const initialConfigLoads = calls.filter(c => c.url === '/api/admin/config').length;
document.querySelector('[data-tab="home"]').click(); await tick();
document.querySelector('[data-tab="admin"]').click(); await tick();
@@ -66,9 +68,9 @@ test('native admin initializer preserves lazy navigation, assistant actions and
const writes = () => calls.filter(c => c.options.method === 'PUT');
const save = document.getElementById('btn-save-assistant-config');
save.click(); await tick();
- assert.equal(writes().length, 7);
+ assert.equal(writes().length, 6);
assert.deepEqual(writes().map(c => c.url.split('/').pop()).sort(), [
- 'clinical_assistant.allowed_image_models', 'clinical_assistant.allowed_models', 'clinical_assistant.chat_model', 'clinical_assistant.context_chars', 'clinical_assistant.conversation_chars', 'clinical_assistant.search_limit', 'clinical_assistant.translate_provider'
+ 'clinical_assistant.allowed_image_models', 'clinical_assistant.allowed_models', 'clinical_assistant.chat_model', 'clinical_assistant.context_chars', 'clinical_assistant.search_limit', 'clinical_assistant.translate_provider'
]);
assert.ok(toasts.some(([message, kind]) => message === 'Assistant settings saved' && kind === 'success'));
@@ -95,7 +97,7 @@ test('real extracted initializer never invents a cap when metadata is missing, i
document.dispatchEvent(new dom.window.CustomEvent('tabChanged', { detail: { tab: 'admin' } }));
await tick();
assert.equal(document.getElementById('assistant-conversation-budget').value, '', 'failed load leaves the budget input empty');
- assert.equal(document.getElementById('assistant-conversation-budget-meta').textContent, 'UTF-16 code units. Leave empty to use CLINICAL_ASSISTANT_CONVERSATION_CHARS.');
+ assert.equal(document.getElementById('assistant-conversation-budget-meta').textContent, 'Controlled by CLINICAL_ASSISTANT_CONVERSATION_CHARS — saved settings cannot change it.');
});
}
});
@@ -109,5 +111,6 @@ test('real extracted initializer displays the server default only when returned
initClinicalAssistantAdmin(value => value);
document.dispatchEvent(new dom.window.CustomEvent('tabChanged', { detail: { tab: 'admin' } }));
await tick();
- assert.equal(document.getElementById('assistant-conversation-budget').value, '120000', 'editable budget prefilled from the server metadata');
+ assert.equal(document.getElementById('assistant-conversation-budget').value, '120000', 'read-only budget prefilled from the server metadata');
+ assert.equal(document.getElementById('assistant-conversation-budget').readOnly, true);
});
diff --git a/test/clinical-release-integration.test.js b/test/clinical-release-integration.test.js
index 738fbb1..6f3cf02 100644
--- a/test/clinical-release-integration.test.js
+++ b/test/clinical-release-integration.test.js
@@ -80,12 +80,13 @@ test('native admin and assistant modules retain budget, table/source identity an
document.dispatchEvent(new window.CustomEvent('tabChanged', { detail: { tab: 'admin' } }));
await tick();
assert.equal(document.querySelectorAll('#assistant-conversation-chars').length, 0);
- assert.equal(document.getElementById('assistant-conversation-budget').value, '999999');
+ assert.equal(document.getElementById('assistant-conversation-budget').value, '2000');
+ assert.equal(document.getElementById('assistant-conversation-budget').readOnly, true, 'the budget is read-only: the env var controls it');
document.getElementById('btn-save-assistant-config').click();
await tick();
assert.equal(limit, 2000);
- assert.equal(calls.filter(call => call.options.method === 'PUT').length, 7, 'one native admin initializer; prompts and ENV budget are not generic setting saves');
- assert.equal(calls.some(call => call.url.endsWith('/config/clinical_assistant.conversation_chars')), true, 'the conversation budget is now an admin-settable override');
+ assert.equal(calls.filter(call => call.options.method === 'PUT').length, 6, 'one native admin initializer; prompts and ENV budget are not generic setting saves');
+ assert.equal(calls.some(call => call.url.endsWith('/config/clinical_assistant.conversation_chars')), false, 'the conversation budget is never saved as a setting');
document.dispatchEvent(new window.CustomEvent('tabChanged', { detail: { tab: 'assistant' } }));
await tick(); await tick(); await tick();
const input = document.getElementById('assistant-input');
diff --git a/test/frontend-prompt-env.test.js b/test/frontend-prompt-env.test.js
index dd4b8f3..7a2cb75 100644
--- a/test/frontend-prompt-env.test.js
+++ b/test/frontend-prompt-env.test.js
@@ -310,7 +310,8 @@ test('assistant config GET503 plus Save makes zero PUTs; failed retry preserves
assert.equal(setting(ui, 'chat-model').value, 'saved-chat');
assert.equal(setting(ui, 'search-limit').value, '17');
assert.equal(setting(ui, 'context-chars').value, '2300');
- assert.equal(setting(ui, 'conversation-budget').value, '240000', 'editable budget prefilled from environment metadata');
+ assert.equal(setting(ui, 'conversation-budget').value, '240000', 'read-only budget prefilled from environment metadata');
+ assert.equal(setting(ui, 'conversation-budget').readOnly, true);
assert.match(setting(ui, 'admin-status').textContent, /ready/i);
assert.equal(retry.hidden, true);
adminVisit(ui); adminVisit(ui); await tick();
@@ -321,7 +322,6 @@ test('assistant config GET503 plus Save makes zero PUTs; failed retry preserves
['clinical_assistant.chat_model', 'saved-chat'],
['clinical_assistant.search_limit', '19'], ['clinical_assistant.context_chars', '2300'],
['clinical_assistant.translate_provider', 'libretranslate'],
- ['clinical_assistant.conversation_chars', '240000'],
['clinical_assistant.allowed_models', ''], ['clinical_assistant.allowed_image_models', '']
]);
});