Translation formatting
LibreTranslate's text mode destroys markdown syntax. Verified against the live
container: table pipes come back as "←", the |---| delimiter row is translated
as prose ("Silencio."), and "**bold**" returns as "** bold**" which no longer
renders. Its html mode leaves tags — and bare [n] markers — completely intact.
Messages and the patient take home are now rendered to HTML, simplified (maths
and UI chrome flattened to text), and translated as HTML. Citation chips are
re-linked from the returned markers afterwards, which is the step the original
html path was missing. A text-mode fallback remains for builds that reject html.
Repeat image generation
Typing "Окей" or "Nice" after an image turn produced another image every time:
the model saw its own "I'll generate an educational image…" in the history and
repeated it. Recognising acknowledgements in every language is not possible, so
the rule is inverted — a short follow-up (<=3 words) that mentions nothing about
a picture does not get the image tool offered at all when the previous assistant
turn produced an image. Terse repeat requests ("again", "ещё", "another one")
still work. The worst case is that a terse question is answered in text.
In-chat images
Generated images render as a 320x240 thumbnail instead of filling the bubble,
and the image itself opens the full-resolution preview.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkfrkQwA4YGrGw9LZSpeAq
86 lines
4.7 KiB
JavaScript
86 lines
4.7 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const path = require('node:path');
|
|
const { pathToFileURL } = require('node:url');
|
|
|
|
async function imagesModule() {
|
|
return import(pathToFileURL(path.join(__dirname, '..', 'public/js/assistant/images.js')).href);
|
|
}
|
|
|
|
test('assistant image intent does not intercept table retrieval requests', async () => {
|
|
const { isImageRequest } = await imagesModule();
|
|
assert.equal(isImageRequest('can you show me the table?'), false);
|
|
assert.equal(isImageRequest('show me Table 13.1'), false);
|
|
});
|
|
|
|
test('assistant image intent still handles explicit visual requests', async () => {
|
|
const { isImageRequest } = await imagesModule();
|
|
assert.equal(isImageRequest('show me the diagram'), true);
|
|
assert.equal(isImageRequest('create an infographic for asthma'), true);
|
|
});
|
|
|
|
// A run of image turns used to make every acknowledgement produce another image:
|
|
// the model saw its own "I'll generate an educational image…" in the history and
|
|
// repeated it for "Окей" and "Nice". The tool is withheld for short follow-ups
|
|
// that say nothing about a picture, which does not depend on recognising an
|
|
// acknowledgement in any particular language.
|
|
function imageToolGuard() {
|
|
const fs = require('node:fs');
|
|
const src = fs.readFileSync(path.join(__dirname, '..', 'src/routes/clinicalAssistant.js'), 'utf8');
|
|
const start = src.indexOf('const IMAGE_REQUEST_PATTERN');
|
|
const end = src.indexOf('async function dispatchImageRequestFallback');
|
|
assert.ok(start > 0 && end > start, 'image guard block located');
|
|
return new Function(src.slice(start, end) + '; return { withholdImageTool, mentionsImage };')();
|
|
}
|
|
|
|
const afterImage = [
|
|
{ role: 'user', content: 'Can you illustrate with an image' },
|
|
{ role: 'assistant', content: "I'll generate an educational image illustrating the imaging findings of periventricular leukomalacia." }
|
|
];
|
|
const afterText = [
|
|
{ role: 'user', content: 'periventricular leukomalacia' },
|
|
{ role: 'assistant', content: 'PVL is a disorder of the periventricular white matter.' }
|
|
];
|
|
|
|
test('acknowledgements after an image turn do not get the image tool, in any language', () => {
|
|
const { withholdImageTool } = imageToolGuard();
|
|
for (const ack of ['ok', 'okay', 'Nice', 'Perfect', 'thanks!', 'Окей', 'bien', 'ç', '👍', 'got it']) {
|
|
assert.equal(withholdImageTool(ack, afterImage), true, JSON.stringify(ack) + ' must not trigger another image');
|
|
}
|
|
});
|
|
|
|
test('real image follow-ups keep the tool, including terse repeat requests', () => {
|
|
const { withholdImageTool } = imageToolGuard();
|
|
for (const ask of ['again', 'another one', 'ещё', 'redo', 'make it bigger with labels',
|
|
'now show the MRI diagram', 'can you add a figure for the cystic phase']) {
|
|
assert.equal(withholdImageTool(ask, afterImage), false, JSON.stringify(ask) + ' is a real image request');
|
|
}
|
|
});
|
|
|
|
test('the guard only applies after an image turn and never blocks a first request', () => {
|
|
const { withholdImageTool } = imageToolGuard();
|
|
assert.equal(withholdImageTool('Nice', afterText), false, 'no preceding image turn, nothing to repeat');
|
|
assert.equal(withholdImageTool('Окей', []), false, 'an empty conversation cannot be repeating an image');
|
|
assert.equal(withholdImageTool('draw me a diagram of the airway', afterText), false);
|
|
assert.equal(withholdImageTool('Explain the pathophysiology of PVL in preterm infants', afterImage), false,
|
|
'a substantive question is not a short acknowledgement');
|
|
});
|
|
|
|
test('the in-chat image is a clickable thumbnail, not a full-width picture', async () => {
|
|
const fs = require('node:fs');
|
|
const root = path.join(__dirname, '..');
|
|
const { createAssistantImageStore } = await import(pathToFileURL(path.join(root, 'public/js/assistant/images.js')).href);
|
|
const store = createAssistantImageStore({});
|
|
const html = store.renderGeneratedImage('/api/generated-images/synthetic.png', 'Generated teaching visual');
|
|
assert.match(html, /<img[^>]*data-assistant-open-image="img-\d+"/, 'the image itself opens the full-resolution preview');
|
|
assert.match(html, /<img[^>]*role="button"[^>]*>/, 'and announces itself as activatable');
|
|
assert.match(html, /data-assistant-open-image="img-\d+"><i class="fas fa-expand"><\/i> Preview/, 'the Preview button still works');
|
|
|
|
const css = fs.readFileSync(path.join(root, 'public/css/assistant.css'), 'utf8');
|
|
const rule = css.split('\n').find(line => line.startsWith('.assistant-generated-image img {'));
|
|
assert.ok(rule, 'the generated-image rule exists');
|
|
assert.doesNotMatch(rule, /width:100%/, 'no longer stretched to the bubble width');
|
|
assert.match(rule, /max-width:min\(100%,320px\)/, 'capped to a thumbnail');
|
|
assert.match(rule, /max-height:240px/);
|
|
assert.match(rule, /cursor:zoom-in/, 'the thumbnail invites the click');
|
|
});
|