Skip to content

Commit 9cfa457

Browse files
Ahad690claude
andcommitted
Make the AI image prompt model-authored (img.ai_prompt), deterministic as fallback
Thumbnail design is offer design, not a market measurement — so the model now authors a rich image prompt per gig (scenes/metaphors an image model can render but the canvas can't) while assembling gig-config.json. SKILL.md Step 6 sets the guardrails: exact quoted text strings, 1280x769, accent-consistent, no watermarks/unquoted text, and never credibility claims that aren't supplied (no invented "Top Rated"/review counts). The catalog prefers img.ai_prompt and falls back to the deterministic canvas-mirror composer when absent. 81 tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 372be43 commit 9cfa457

4 files changed

Lines changed: 55 additions & 12 deletions

File tree

USER_MANUAL.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,12 @@ claude plugin validate .
9090
**`fiverr-catalog.html`**. Open it in a browser: each gig has a thumbnail,
9191
copy buttons for title/description/tags, a PNG download, and the launch plan.
9292
Prefer an AI-generated thumbnail instead? Each gig also includes a
93-
**copy-ready AI image prompt** (under the thumbnail) describing the same
94-
design brief — paste it into ChatGPT/DALL·E/Midjourney and use that image
95-
in place of the built-in PNG.
93+
**copy-ready AI image prompt** (under the thumbnail) — written by the model
94+
alongside your titles and descriptions, so it can describe a richer scene or
95+
visual metaphor than the flat canvas design. Paste it into
96+
ChatGPT/DALL·E/Midjourney and use that image in place of the built-in PNG.
97+
(Configs without an authored prompt fall back to a deterministic one that
98+
mirrors the canvas design.)
9699

97100
That's the whole loop — no key, no setup.
98101

skills/fiverr-gig-optimizer/SKILL.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,19 @@ titles/tags/descriptions (lint rules in the playbook), pick phases and
9595
cross-sells, set per-gig thumbnail accent from the palette. The `competition`,
9696
`scores`, and `pricing` blocks are the script outputs — do not alter them.
9797

98+
Also author `img.ai_prompt` for each gig — a rich prompt for an AI image model
99+
(ChatGPT/DALL·E/Midjourney), offered in the catalog as an alternative to the
100+
canvas PNG. Thumbnail design is offer design, so be creative here: describe a
101+
distinctive scene, composition, or visual metaphor suited to the service (an
102+
isometric workflow, a stylized robot at a desk, glowing pipeline nodes…) —
103+
not just flat text on a gradient. Requirements: specify 1280×769 landscape;
104+
quote the exact headline/badge strings that must appear and say "spelled
105+
exactly as written"; keep it consistent with the gig's accent color; forbid
106+
watermarks, logos, and any unquoted text. Never put claims in the image that
107+
aren't true of the seller (no "Top Rated", review counts, or client numbers
108+
unless supplied). If you skip `ai_prompt`, the catalog falls back to a
109+
deterministic prompt mirroring the canvas design.
110+
98111
**Step 7 — Render (FR14/FR15).**
99112
`build_catalog.py gig-config.json``fiverr-catalog.html`. Optionally
100113
`build_pdfs.py gig-config.json` for per-gig PDFs (skips if no Chrome).

skills/fiverr-gig-optimizer/scripts/build_catalog.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
44
Reads a gig-config.json (§7.2) and writes a single fiverr-catalog.html with:
55
- canvas thumbnails (1280x769) drawn from each gig's img block
6-
- a copy-ready AI image-generation prompt per gig (same design brief as the
7-
canvas, for users who prefer ChatGPT/DALL-E/Midjourney over the built-in PNG)
6+
- a copy-ready AI image-generation prompt per gig, for users who prefer
7+
ChatGPT/DALL-E/Midjourney over the built-in PNG. Primary source is the
8+
model-authored ``img.ai_prompt`` (written with the rest of the gig copy —
9+
it can describe scenes/compositions the canvas can't draw); when absent,
10+
a deterministic composer mirrors the canvas design as a fallback.
811
- copy-to-clipboard buttons for title / description / tags
912
- per-gig PNG download (from the canvas)
1013
- a cross-sell map and a phase-based action plan
@@ -118,10 +121,11 @@
118121
function copy(text,btn){ navigator.clipboard.writeText(text).then(()=>{
119122
const o=btn.textContent; btn.textContent='Copied!'; setTimeout(()=>btn.textContent=o,1200); }); }
120123
121-
// Compose an AI image-generation prompt describing the SAME design the canvas
122-
// draws (deterministic — built only from the gig's own img block, no invention).
123-
// For users who prefer generating the thumbnail with ChatGPT/DALL-E/Midjourney
124-
// instead of downloading the canvas PNG.
124+
// FALLBACK image prompt: mirrors the flat canvas design, built only from the
125+
// gig's own img block. The PRIMARY path is gig.img.ai_prompt — authored by the
126+
// model while assembling gig-config.json (thumbnail design is offer design,
127+
// so creative freedom is allowed there; an image model can render scenes the
128+
// canvas can't). This composer covers configs that lack ai_prompt.
125129
function aiPrompt(gig){
126130
const img = gig.img || {};
127131
const accent = img.accent || '#06b6d4';
@@ -188,7 +192,9 @@
188192
<p class="desc">${(gig.desc||'').replace(/</g,'&lt;')}</p>`;
189193
app.appendChild(el);
190194
draw(document.getElementById('cv'+gig.id), gig.img||{});
191-
const prompt = aiPrompt(gig);
195+
// Prefer the model-authored prompt (richer: scenes/metaphors an image model
196+
// can render); fall back to the deterministic canvas-mirror when absent.
197+
const prompt = (gig.img && gig.img.ai_prompt) ? gig.img.ai_prompt : aiPrompt(gig);
192198
document.getElementById('pp'+gig.id).textContent = prompt;
193199
document.getElementById('cp'+gig.id).onclick=e=>copy(prompt,e.target);
194200
document.getElementById('ct'+gig.id).onclick=e=>copy(gig.title||'',e.target);

tests/test_build_catalog.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,36 @@ def test_ai_prompt_ui_present(self):
5050
self.assertIn("aiPrompt(", self.html)
5151
self.assertIn("ChatGPT / DALL·E / Midjourney", self.html)
5252

53-
def test_prompt_composed_from_gig_img_block_only(self):
54-
# The composer must reference the img fields and thumbnail spec…
53+
def test_fallback_prompt_composed_from_gig_img_block(self):
54+
# No ai_prompt in CONFIG -> the deterministic fallback composer is used;
55+
# it must reference the img fields and thumbnail spec…
5556
for needle in ("1280x769", "img.headline", "img.badge", "img.tools",
5657
"img.accent", "spelled exactly as written"):
5758
self.assertIn(needle, self.html)
5859
# …and the gig's real data must be in the embedded CONFIG for it to use.
5960
self.assertIn("FAST DELIVERY", self.html)
6061
self.assertIn("n8n + OpenAI", self.html)
6162

63+
def test_model_authored_ai_prompt_takes_priority(self):
64+
cfg = json.loads(json.dumps(CONFIG))
65+
cfg["gigs"][0]["img"]["ai_prompt"] = (
66+
"An isometric 3D scene of a friendly robot assembling chat bubbles "
67+
'on a conveyor belt, headline "AI CHATBOT" spelled exactly as written, '
68+
"1280x769 landscape, teal #06b6d4 accent, no watermarks."
69+
)
70+
cfg_path = os.path.join(self.dir, "gig-config2.json")
71+
out = os.path.join(self.dir, "catalog2.html")
72+
with open(cfg_path, "w", encoding="utf-8") as fh:
73+
json.dump(cfg, fh)
74+
r = subprocess.run([sys.executable, SCRIPT, cfg_path, "--out", out],
75+
capture_output=True, text=True)
76+
self.assertEqual(r.returncode, 0, r.stderr)
77+
with open(out, encoding="utf-8") as fh:
78+
html = fh.read()
79+
# The authored prompt is embedded, and the renderer prefers it.
80+
self.assertIn("isometric 3D scene of a friendly robot", html)
81+
self.assertIn("gig.img.ai_prompt", html)
82+
6283

6384
if __name__ == "__main__":
6485
unittest.main()

0 commit comments

Comments
 (0)