+ 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"/);
+});