You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit f05d694
Browse filesBrowse the repository at this point in the historyBrowse files
Inline image generation was producing pretty-but-generic
abstract-editorial blobs — the same tactile-metaphor language was
applied to every section regardless of what the section was doing.
For posts with quantitative content, comparison tables, or step-by-step
workflows, the right image is a small infographic, not another
sculpture macro.
Let Claude pick the right kind per section. ArticleSchema's
inline_image_prompts now carries:
- kind: concept | chart | flow | comparison | checklist
- labels: 2–6 short strings (≤24 chars) to render in the figure
System prompt explains when to use each kind and asks for a MIX
across the three slots — at least one structural visual when the
article has any quantitative or procedural content.
generateInlineImage dispatches on kind to a tailored art-direction
overlay. New INFOGRAPHIC_ART_DIRECTION (paper background, single
accent color, sans-serif, clean strokes, NO 3D / drop-shadows /
emoji / faux-handwriting) is the visual language for chart/flow/
comparison/checklist. SHARED_ART_DIRECTION (the cinematic macro
style) stays for hero + concept-mode inlines, keeping the magazine
cover feel where it makes sense.
Existing callers (articleGen + guestPostGen) updated to forward
kind/labels through to the image renderer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@@ -163,7 +174,20 @@ export function buildSystemPrompt(): string {
163
174
"",
164
175
"Internal links: insert each provided URL inline exactly once as a standard markdown `[anchor](url)` link where the surrounding sentence is genuinely about that URL's topic. Never create a \"Further reading\" list. Never invent URLs — use only those provided. Link candidates may include both site pages AND prior blog posts on this same site — treat both the same way (inline contextual anchor; the prior posts are clearly labeled in the candidate list).",
165
176
"",
166
-
`Inline images: place exactly ${INLINE_IMAGE_COUNT} placeholder lines of the form '<!--INLINE_IMAGE_1-->', '<!--INLINE_IMAGE_2-->', '<!--INLINE_IMAGE_3-->' (each on its own line, in numeric order) inside markdown_body. Place each marker on a blank line immediately after a major H2 boundary, distributed across the body — never inside the intro, the TOC, a blockquote, a table, or inside the final '### Try {brand}' CTA block. In inline_image_prompts return exactly ${INLINE_IMAGE_COUNT} objects in the same 1→${INLINE_IMAGE_COUNT} order: each with a short alt text describing what the image shows for accessibility, and an image generation prompt for that section's topic (abstract editorial style, no text/typography/logos/people).`,
177
+
`Inline images: place exactly ${INLINE_IMAGE_COUNT} placeholder lines of the form '<!--INLINE_IMAGE_1-->', '<!--INLINE_IMAGE_2-->', '<!--INLINE_IMAGE_3-->' (each on its own line, in numeric order) inside markdown_body. Place each marker on a blank line immediately after a major H2 boundary, distributed across the body — never inside the intro, the TOC, a blockquote, a table, or inside the final '### Try {brand}' CTA block.`,
178
+
"",
179
+
`For each placeholder, return one object in inline_image_prompts (same 1→${INLINE_IMAGE_COUNT} order). Each object MUST set a kind that fits what the surrounding section is doing — this is the difference between a decorative blob and an informative graphic:`,
180
+
"",
181
+
"- kind=\"chart\" — when the section discusses metrics, percentages, comparisons of magnitudes, or trends. Provide 2–5 short labels (≤24 chars each) that should appear on bars/segments. Plausible numbers are fine; the chart is illustrative.",
182
+
"- kind=\"flow\" — when the section walks through a process or workflow with 3–5 steps. Provide the step labels in order.",
183
+
"- kind=\"comparison\" — when the section explicitly contrasts two approaches (good/bad, before/after, wrong/right). Provide exactly 2 labels (e.g., [\"Before\", \"After\"]).",
184
+
"- kind=\"checklist\" — when the section is a 'do these things' list of 3–6 items. Provide the checklist labels.",
185
+
"- kind=\"concept\" — fallback for atmospheric / opening sections where no data, flow, comparison, or list is being presented. No labels needed.",
186
+
"",
187
+
"Aim for a MIX across the three inline images — at least one should be chart/flow/comparison/checklist when the article has any quantitative or procedural content. 'concept' is fine for at most one of the three.",
188
+
"",
189
+
"alt: a short accessibility description of what the image shows.",
190
+
"prompt: a one-sentence brief describing the image's content (this becomes the image-model prompt). Don't include style direction — the renderer adds it.",
167
191
"",
168
192
"Output: strict JSON matching the schema. The markdown_body is the article body only.",
"Strictly NO text, NO typography, NO UI mockups, NO logos, NO charts with labels, NO people, NO faces, NO stock-photo office scenes, NO generic abstract gradient blobs.",
398
422
];
399
423
424
+
// Visual-design language shared across all infographic-style inline
425
+
// images. Keeps charts/flows/comparisons/checklists feeling like they
426
+
// came from the same magazine even though Claude wrote each prompt
427
+
// independently. Different from SHARED_ART_DIRECTION (which is for
428
+
// "concept" mode + the hero) — that one explicitly forbids text;
429
+
// this one explicitly permits it.
430
+
constINFOGRAPHIC_ART_DIRECTION=[
431
+
"Style: clean editorial infographic — the kind you'd see in a Bloomberg, Stripe Press, or Information is Beautiful piece. NOT a 3D render, NOT a slide template, NOT clip-art.",
432
+
"Surface: matte off-white paper background (#F2EFE8-ish) with one charcoal accent (#1A1F26) and ONE saturated highlight color picked from {electric cyan, deep emerald, warm amber, vermilion} — pick one and stick to it for the whole figure.",
433
+
"Typography: legible sans-serif (Inter / SF Pro feel), tight tracking, NO ALL-CAPS body text, generous whitespace. Labels short and unambiguous. Numbers rendered cleanly.",
434
+
"Lines: 1.5–2px strokes, no drop shadows, no gradient fills unless explicitly part of a data series. Rounded corners on shape primitives.",
435
+
"Layout: 3:2 aspect, asymmetric balance, single clear visual hierarchy. Title-free — the surrounding article copy provides the headline.",
436
+
"Strictly NO photographic textures, NO 3D bevels, NO gradients on backgrounds, NO emoji, NO stock-icon people, NO faux-handwriting fonts, NO drop-shadows on text.",
// Bar / column chart with 3–5 segments. Plausible illustrative numbers.
452
+
return[
453
+
"Editorial bar-chart infographic for a long-form technical article.",
454
+
`Topic: ${spec.prompt}.`,
455
+
niche,
456
+
`Render a simple 3–5 segment vertical bar chart (or horizontal if it fits the labels better). Each bar has a clear short label beneath/beside it and one rendered numeric value on or above the bar (percentages OK).`,
457
+
labelClause,
458
+
"One bar should be visually emphasized in the accent color; the rest in charcoal.",
459
+
...INFOGRAPHIC_ART_DIRECTION,
460
+
]
461
+
.filter(Boolean)
462
+
.join(" ");
463
+
}
464
+
case"flow": {
465
+
return[
466
+
"Editorial process-flow diagram for a long-form technical article.",
467
+
`Topic: ${spec.prompt}.`,
468
+
niche,
469
+
"Render a left-to-right (or top-to-bottom if labels are long) sequence of 3–5 rounded-rectangle nodes connected by single arrows. Each node contains one short label. The final node is the outcome and is emphasized in the accent color.",
470
+
labelClause,
471
+
...INFOGRAPHIC_ART_DIRECTION,
472
+
]
473
+
.filter(Boolean)
474
+
.join(" ");
475
+
}
476
+
case"comparison": {
477
+
return[
478
+
"Editorial side-by-side comparison panel for a long-form technical article.",
479
+
`Topic: ${spec.prompt}.`,
480
+
niche,
481
+
"Render exactly two vertical columns separated by a thin vertical divider. Each column has its label as the column header and 3–4 short bullet-style items below — one icon glyph (geometric primitive, NOT emoji) next to each. The 'good' / 'after' / 'right' column gets the accent color; the other is charcoal.",
482
+
labelClause,
483
+
...INFOGRAPHIC_ART_DIRECTION,
484
+
]
485
+
.filter(Boolean)
486
+
.join(" ");
487
+
}
488
+
case"checklist": {
489
+
return[
490
+
"Editorial visual checklist for a long-form technical article.",
491
+
`Topic: ${spec.prompt}.`,
492
+
niche,
493
+
"Render a stacked vertical list of 3–6 items, each prefixed with a small filled square in the accent color. Items aligned left, generous line height. No screenshot framing.",
494
+
labelClause,
495
+
...INFOGRAPHIC_ART_DIRECTION,
496
+
]
497
+
.filter(Boolean)
498
+
.join(" ");
499
+
}
500
+
case"concept":
501
+
default: {
502
+
// Legacy tactile-metaphor path. Used when the section is
503
+
// atmospheric and there's nothing structural to visualize.
504
+
return[
505
+
`Editorial section image for a long-form technical SEO article.`,
506
+
`Concept to evoke (do NOT depict literally — find a tactile metaphor): ${spec.prompt}.`,
507
+
niche,
508
+
...SHARED_ART_DIRECTION,
509
+
"Slightly more subdued than a hero — a chapter divider, not the cover. 3:2 aspect.",
510
+
]
511
+
.filter(Boolean)
512
+
.join(" ");
513
+
}
514
+
}
515
+
}
516
+
400
517
exportasyncfunctiongenerateInlineImage(
401
518
openai: OpenAI,
402
519
promptText: string,
403
520
nicheHint: string|null,
521
+
opts: {kind?: string;labels?: string[]}={},
404
522
): Promise<Buffer|null>{
405
-
constprompt=[
406
-
`Editorial section image for a long-form technical SEO article.`,
407
-
`Concept to evoke (do NOT depict literally — find a tactile metaphor): ${promptText}.`,
408
-
nicheHint ? `Subject area: ${nicheHint}.` : "",
409
-
...SHARED_ART_DIRECTION,
410
-
"Slightly more subdued than a hero — a chapter divider, not the cover. 3:2 aspect.",
411
-
]
412
-
.filter(Boolean)
413
-
.join(" ");
523
+
constprompt=buildInlineImagePrompt(
524
+
{
525
+
prompt: promptText,
526
+
kind: opts.kind??"concept",
527
+
labels: opts.labels??[],
528
+
},
529
+
nicheHint,
530
+
);
414
531
constres=awaitopenai.images.generate({
415
532
model: IMAGE_MODEL,
416
533
prompt,
@@ -803,7 +920,10 @@ export async function generateArticle(
0 commit comments