diff --git a/src/routes/myResources.js b/src/routes/myResources.js index 6296e2e9..dbefc3e5 100644 --- a/src/routes/myResources.js +++ b/src/routes/myResources.js @@ -132,11 +132,29 @@ function buildPrompt(opts) { // has no way to ask for any of them. Articles stay markdown, which is the // right shape for prose. if (kind === 'presentation' && opts.deckMode) { + // The figure request goes last, after the author's own instructions. Inside + // the layout vocabulary it was one line among forty and the model passed + // over it: a twelve-slide deck asked to "include a diagram of the diagnostic + // pathway" came back with no image_prompt at all. Placement was the same + // lesson the tool path taught — whatever must not be missed goes last. + var figures = !opts.wantsImages ? '' : '\n\n' + (opts.figureCount + ? 'Before you finish: this deck must carry ' + opts.figureCount + ' figure' + + (opts.figureCount === 1 ? '' : 's') + '. Make ' + opts.figureCount + ' of the slides ' + + 'type "figure" (bullets on the left, the picture on the right) or type "image" (the ' + + 'picture is the whole slide), and give each one an "image_prompt" describing what to ' + + 'draw. Those two types are the only ones that carry a picture. A deck returned without ' + + (opts.figureCount === 1 ? 'it' : 'them') + ' has not followed the instruction.' + : 'Before you finish: where a diagram or labelled figure would genuinely help, make that ' + + 'slide type "figure" or type "image" and give it an "image_prompt" describing what to ' + + 'draw. Those two types are the only ones that carry a picture. Schematic or anatomical ' + + 'teaching artwork only, never a real patient.'); + return 'You are building a teaching presentation for a medical professional ' + 'audience (pediatrics / primary care).\n\nTOPIC: ' + opts.topic + '\n' + grounding + findings + '\n' + deckSchema.instructions(opts.slideCount, opts.figureCount) + - (opts.refinement ? '\n\nAdditional instructions: ' + opts.refinement + '\n' : ''); + (opts.refinement ? '\n\nAdditional instructions: ' + opts.refinement + '\n' : '') + + figures; } var shape = kind === 'presentation' diff --git a/src/utils/deckSchema.js b/src/utils/deckSchema.js index dbc2ae82..a48653c2 100644 --- a/src/utils/deckSchema.js +++ b/src/utils/deckSchema.js @@ -57,7 +57,8 @@ function instructions(slideCount, figureCount) { slideShapes.instructions(), '', figureCount - ? 'Include exactly ' + figureCount + ' slides carrying an image_prompt, spread through the deck.' + ? 'Include exactly ' + figureCount + ' slide' + (figureCount === 1 ? '' : 's') + + ' carrying an image_prompt, spread through the deck.' : 'Include an image_prompt only where a picture genuinely earns its place, and at most three.', '', 'Vary the layouts: a deck of nothing but "bullets" is the thing to avoid.', @@ -137,6 +138,17 @@ function normalise(raw, gaps) { if (type === 'bullets' && !out.bullets.length && !out.heading) return; } + // An image_prompt on a slide that does not carry pictures used to be dropped + // in silence, so a model that asked for a figure the wrong way got none and + // no one found out. The intent is honoured instead: a slide with words + // becomes a figure, one without becomes a full-slide image. + if (slide.image_prompt && type !== 'figure' && type !== 'image' + && type !== 'title' && type !== 'custom') { + out.type = type = (out.bullets && out.bullets.length) || (slide.bullets || []).length + ? 'figure' : 'image'; + if (type === 'figure' && !(out.bullets || []).length) out.bullets = bullets(slide.bullets); + } + // A figure request is a prompt, not a path. The route turns the ones it can // afford into jobs and hands back files; anything unfulfilled degrades to a // slide of text rather than an empty frame. diff --git a/test/slide-spec.test.js b/test/slide-spec.test.js index 5c267eae..fe690804 100644 --- a/test/slide-spec.test.js +++ b/test/slide-spec.test.js @@ -218,3 +218,27 @@ test('a deck is given room to be a deck', () => { assert.match(route, /cut short at ' \+ replyLength \+ ' chars'/); assert.match(route, /empty reply/); }); + +test('a figure asked for the wrong way is honoured, not dropped', () => { + // image_prompt is only carried by the figure and image types, so a model that + // put one on a bullets slide had it silently discarded — it asked for a + // picture, got none, and nothing said why. Measured on a real generation: a + // twenty-slide deck, an explicit "include a diagram", zero image_prompt. + const deck = deckSchema.normalise({ slides: [ + { type: 'bullets', heading: 'Pathway', bullets: ['a', 'b'], image_prompt: 'a flow diagram' }, + { type: 'section', heading: 'Overview', image_prompt: 'a full-bleed picture' }, + ]}); + // Words beside a picture is a figure; no words is a full-slide image. + assert.equal(deck.slides[0].type, 'figure'); + assert.equal(deck.slides[0].image_prompt, 'a flow diagram'); + assert.equal(deck.slides[1].type, 'image'); + + // And the instruction now names the only two types that carry one, last in + // the prompt, after the author's own instructions. + const route = read('src/routes/myResources.js'); + assert.match(route, /Those two types are the only ones that carry a picture/); + const deckBranch = route.slice(route.indexOf("if (kind === 'presentation' && opts.deckMode)")); + const built = deckBranch.indexOf('var figures ='); + const used = deckBranch.indexOf("+\n figures;"); + assert.ok(built > -1 && used > built, 'the figure request is appended last'); +});