diff --git a/public/components/cms.html b/public/components/cms.html index b0577585..59b300b4 100644 --- a/public/components/cms.html +++ b/public/components/cms.html @@ -242,7 +242,23 @@ - + +
+ +

+ Searches the indexed clinical corpus for this topic and writes from those + excerpts, preferring them over the model's own recall. The resource ends with + a References section listing what it drew on. Turn this off for a topic the + library does not cover — grounding on the nearest unrelated material is + worse than not grounding at all. +

+

+
+ +
diff --git a/public/js/learningHub.js b/public/js/learningHub.js index ca15ccc5..c9bd28b8 100644 --- a/public/js/learningHub.js +++ b/public/js/learningHub.js @@ -495,6 +495,14 @@ import { createWebdavController } from './learningHub/webdavController.js'; if (webdavCtx && webdavCtx.value.trim()) formData.append('topic', webdavCtx.value.trim()); } + // Grounding is opt-out rather than opt-in in the UI: for clinical teaching + // the library is nearly always the right source, and someone who forgets + // the checkbox should get the grounded version. + var corpusBox = document.getElementById('lh-ai-use-corpus'); + formData.append('useCorpus', corpusBox && corpusBox.checked === false ? 'false' : 'true'); + var groundingLine = document.getElementById('lh-ai-grounding-result'); + if (groundingLine) groundingLine.textContent = ''; + var btn = document.getElementById('btn-lh-ai-generate'); if (btn) { btn.disabled = true; btn.innerHTML = ' Generating...'; } showBusy('AI is generating content...'); @@ -512,6 +520,16 @@ import { createWebdavController } from './learningHub/webdavController.js'; hideBusy(); if (btn) { btn.disabled = false; btn.innerHTML = ' Generate Content'; } if (!data.success) { showToast(data.error || 'Generation failed', 'error'); return; } + // Say what it was written from. Ungrounded material presented as grounded + // is the failure worth preventing, so this never implies the library was + // used when it was not. + if (groundingLine) { + var g = data.grounding || {}; + groundingLine.textContent = g.used + ? 'Written from ' + g.count + ' library excerpt' + (g.count === 1 ? '' : 's') + '.' + : 'Not grounded' + (g.reason ? ' \u2014 ' + g.reason : '') + '. Written from the model alone.'; + groundingLine.style.color = g.used ? 'var(--green)' : 'var(--g600)'; + } // Presentation returns marpMarkdown directly; others return content{} var payload = data.contentType === 'presentation' ? data : data.content; return applyAiContent(payload, contentType).then(function() { diff --git a/test/learning-retrieval.test.js b/test/learning-retrieval.test.js index 451493a5..16a5daae 100644 --- a/test/learning-retrieval.test.js +++ b/test/learning-retrieval.test.js @@ -127,3 +127,24 @@ test('the corpus block tells the model to prefer it over recall', () => { // And every success response says what it was grounded on. assert.equal((route.match(/grounding: \{ used: Boolean\(corpus\.context\)/g) || []).length, 3); }); + +test('the Learning screen can ask for grounding and says what it got', () => { + const html = read('public/components/cms.html'); + const js = read('public/js/learningHub.js'); + + // Opt-out in the UI rather than opt-in: for clinical teaching the library is + // nearly always the right source, so forgetting the checkbox should give the + // grounded version, not the ungrounded one. + assert.match(html, //); + assert.match(js, /formData\.append\('useCorpus', corpusBox && corpusBox\.checked === false \? 'false' : 'true'\)/); + + // The checkbox explains when to turn it OFF, which is the non-obvious case. + assert.match(html, /Turn this off for a topic the\s*\n?\s*library does not cover/); + + // And the result is reported. Ungrounded material presented as grounded is + // the failure worth preventing, so the wording must never imply the library + // was used when it was not. + assert.match(js, /'Written from ' \+ g\.count \+ ' library excerpt'/); + assert.match(js, /'Not grounded' \+ \(g\.reason \? ' \\u2014 ' \+ g\.reason : ''\) \+ '\. Written from the model alone\.'/); + assert.match(html, /id="lh-ai-grounding-result"/); +});