diff --git a/public/components/my-resources.html b/public/components/my-resources.html index a2e2a9f8..a163086a 100644 --- a/public/components/my-resources.html +++ b/public/components/my-resources.html @@ -154,11 +154,9 @@ shown; Images is every picture this account has generated, wherever it was made, because a figure is worth keeping after the deck it was drawn for has been replaced. --> -
- - +
+ +
diff --git a/public/css/styles.css b/public/css/styles.css index 9bb44aae..77523032 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -1555,3 +1555,14 @@ button, a, .btn-sm, .btn-generate, .btn-send, .tab-btn, input, textarea, select, border-radius:8px;box-shadow:0 16px 48px rgba(0,0,0,.45);background:var(--g100);} .img-lightbox-bar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:center;max-width:640px;} .img-lightbox-caption{color:#e5e7eb;font-size:12px;line-height:1.5;text-align:center;} + +/* Library view switch. The active view has to be unmistakable — two buttons of + the same weight read as two buttons, not as a current view and an alternative. */ +.lib-tabs{display:flex;gap:2px;background:var(--g100);border-radius:8px;padding:3px;} +.lib-tab{border:0;border-radius:6px;background:transparent;color:var(--g500); + font:inherit;font-size:12px;font-weight:500;padding:5px 12px;cursor:pointer; + transition:background .15s ease,color .15s ease,box-shadow .15s ease;} +.lib-tab:hover{color:var(--g700);} +.lib-tab[aria-selected="true"]{background:var(--white,#fff);color:var(--g900);font-weight:600; + box-shadow:0 1px 2px rgba(17,24,39,.10),0 0 0 1px rgba(17,24,39,.04);} +.lib-tab:focus-visible{outline:2px solid var(--blue);outline-offset:1px;} diff --git a/public/js/myResources.js b/public/js/myResources.js index 1fe39758..270b24d9 100644 --- a/public/js/myResources.js +++ b/public/js/myResources.js @@ -281,8 +281,9 @@ [['tab-mr-docs', docs], ['tab-mr-images', !docs]].forEach(function (pair) { var tab = document.getElementById(pair[0]); if (!tab) return; + // aria-selected is the whole state: the stylesheet keys off it, so the + // visible highlight and what a screen reader announces cannot disagree. tab.setAttribute('aria-selected', pair[1] ? 'true' : 'false'); - tab.style.background = pair[1] ? 'var(--white)' : 'transparent'; }); var list = document.getElementById('mr-list'); var panel = document.getElementById('mr-images-panel'); diff --git a/src/utils/visionTool.js b/src/utils/visionTool.js index bdbef305..79ab2544 100644 --- a/src/utils/visionTool.js +++ b/src/utils/visionTool.js @@ -58,7 +58,18 @@ function describePrompt(question) { 'cannot see it can reason about it. Report only what is observable — colours, ' + 'distribution, morphology, text, readings, scale. Do not diagnose, do not ' + 'speculate about causes, and say plainly when something is unclear or cut off ' + - 'rather than guessing.\n\nThe question to answer: ' + question; + 'rather than guessing.\n\n' + + // The question is written by a model that has not seen the image, from what + // the user said. So it can presume something that is not there — "describe + // this rash" about a photograph of a drug chart. Answering the presumption + // rather than the picture is the failure that matters, because the answer + // then travels back as fact. + 'The question below was written by someone who cannot see the image, so it ' + + 'may assume something the image does not show. Describe what is actually ' + + 'there. If the question presumes something absent — a finding, a body part, ' + + 'a kind of document — say so first, in plain words, and then describe what ' + + 'the image does contain.\n\n' + + 'The question: ' + question; } /** diff --git a/test/vision-tool.test.js b/test/vision-tool.test.js index 44ee0832..21ae90d8 100644 --- a/test/vision-tool.test.js +++ b/test/vision-tool.test.js @@ -56,6 +56,16 @@ test('a call routes the image to the vision model, never to the chat model', asy assert.equal(out.content, 'That pattern fits...'); }); +test('a question that presumes the wrong thing is corrected, not answered', () => { + // The question is written by a model that has not seen the image, from what + // the user said — so "describe this rash" can arrive about a photograph of a + // drug chart. Answering the presumption would send that back as fact. + const prompt = visionTool.describePrompt('what is the distribution of this rash'); + assert.match(prompt, /may assume something the image does not show/); + assert.match(prompt, /If the question presumes something absent/); + assert.match(prompt, /describe what the image does contain/); +}); + test('the seeing model is told to report, not to diagnose', () => { // It has a picture and no conversation, no sources and no system prompt. An // opinion from it would carry weight it has not earned.