diff --git a/public/components/admin.html b/public/components/admin.html index 74c5190d..15e526fe 100644 --- a/public/components/admin.html +++ b/public/components/admin.html @@ -296,7 +296,7 @@ Model availability

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

-

To make a new model appear here, add it from the gateway: chat models in AI Model Management above, image models in Image Generation below — search, press + Add, and it shows up in this list.

+

To make a new model appear here, add it from the gateway: chat models under AI Model Management above, image models under Image Generation below — search there, press + Add, and it shows up in this list.

Chat models
diff --git a/public/js/admin.js b/public/js/admin.js index c3b5bb9a..f50a883d 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -909,19 +909,6 @@ initImageSettings(); container.innerHTML = '

Querying provider API...

'; if (hint) hint.style.display = 'none'; - // The Clinical Assistant lists models but cannot add them; adding happens in - // AI Model Management, one section up. Without this the two sections look - // unrelated and there is no visible way to get a new model into the list. - document.addEventListener('click', function(event) { - var jump = event.target.closest && event.target.closest('[data-admin-jump]'); - if (!jump) return; - event.preventDefault(); - var target = document.getElementById(jump.getAttribute('data-admin-jump')); - if (!target) return; - target.scrollIntoView({ behavior: 'smooth', block: 'center' }); - target.focus({ preventScroll: true }); - }); - fetch('/api/admin/config/models/discover?q=' + encodeURIComponent(search), { headers: getAuthHeaders() }) .then(function(r) { return r.json(); }) diff --git a/test/admin-clinical-assistant-wiring.test.js b/test/admin-clinical-assistant-wiring.test.js index 0bb41fff..42f6081e 100644 --- a/test/admin-clinical-assistant-wiring.test.js +++ b/test/admin-clinical-assistant-wiring.test.js @@ -3,10 +3,19 @@ const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const { pathToFileURL } = require('node:url'); -const { JSDOM } = require('jsdom'); +const { JSDOM, VirtualConsole } = require('jsdom'); +const { Console } = require('node:console'); const root = path.join(__dirname, '..'); const read = file => fs.readFileSync(path.join(root, file), 'utf8'); const tick = () => new Promise(resolve => setImmediate(resolve)); +// The page's console goes to stderr, never stdout. node:test reads each test +// file's results back over stdout as serialized frames; app.js's own log line +// ("✅ App.js loaded") written there could land inside a frame and fail the +// whole file with "Unable to deserialize cloned data" — about 1 run in 4. +function pageConsole() { + // jsdom 29 renamed sendTo() to forwardTo(). + return new VirtualConsole().forwardTo(new Console({ stdout: process.stderr, stderr: process.stderr })); +} function browserGlobals(t, dom, fetch, toasts) { const values = { window: dom.window, document: dom.window.document, fetch, getAuthHeaders: () => ({ 'X-Test': 'synthetic' }), showToast: (...args) => toasts.push(args) }; @@ -20,7 +29,7 @@ function browserGlobals(t, dom, fetch, toasts) { } test('native admin initializer preserves lazy navigation, assistant actions and read-only ENV budget metadata', async t => { - const dom = new JSDOM('
', { runScripts: 'outside-only', url: 'https://app.example' }); + const dom = new JSDOM('
', { runScripts: 'outside-only', url: 'https://app.example', virtualConsole: pageConsole() }); const calls = []; const toasts = []; const fetch = async (url, options = {}) => { calls.push({ url, options });