v4: Real product image generation + conversion PDP output
Image Generation: - Downloads actual product images from justvitamins.co.uk - Sends real photo as reference to Gemini (image-to-image) - Generates 5 ecommerce-grade variations maintaining product consistency: Hero (clean studio), Lifestyle (kitchen scene), Scale (hand reference), Detail (ingredients close-up), Banner (wide hero) - Uses Nano Banana Pro for hero/lifestyle/banner, Nano Banana for fast shots PDP Output: - Demo A now renders as a real ecommerce product detail page - Gallery: original + AI-generated images with clickable thumbnails - Above the fold: H1, value props, price block, trust bar, CTAs - Key Benefits: Feature → Benefit → Proof format, 5 icon cards - Stats bar, Why This Formula, 5★ review, FAQ accordion - Meta SEO (Google preview), Ad Hooks (5 platform-targeted), Email sequences Prompts: - Conversion-optimised based on Cialdini/Kahneman principles - EFSA health claim compliance baked into every prompt - Feature → Benefit → Proof bullet structure - Price anchoring, social proof, urgency psychology
This commit is contained in:
445
ai_engine.py
445
ai_engine.py
@@ -1,20 +1,18 @@
|
||||
"""AI engine — Gemini for copy, Nano Banana / Nano Banana Pro for imagery.
|
||||
"""AI engine — Gemini for copy, Nano Banana / Pro for image-to-image product shots.
|
||||
|
||||
Models used:
|
||||
Text: gemini-2.5-flash — all copy generation
|
||||
Image: gemini-2.5-flash-image — Nano Banana (fast lifestyle shots)
|
||||
Image: gemini-3-pro-image-preview — Nano Banana Pro (premium hero/product shots)
|
||||
Image generation uses the REAL scraped product image as a reference.
|
||||
Gemini receives the actual photo and generates ecommerce-grade variations
|
||||
that maintain product consistency.
|
||||
|
||||
Powers:
|
||||
Demo A: generate_asset_pack() — 1 product → 12 marketing assets
|
||||
Demo B: competitor_xray() — competitor URL → analysis + JV upgrade
|
||||
Demo C: pdp_surgeon() — existing copy → style variants
|
||||
PDP: optimise_pdp_copy() — full PDP rewrite
|
||||
Images: generate_all_images() — Nano Banana product imagery
|
||||
Models:
|
||||
Text: gemini-2.5-flash
|
||||
Image: gemini-2.5-flash-image — Nano Banana (fast edits)
|
||||
Image: gemini-3-pro-image-preview — Nano Banana Pro (premium product photography)
|
||||
"""
|
||||
|
||||
import os, json, hashlib, re
|
||||
import os, json, hashlib, re, io, time
|
||||
from pathlib import Path
|
||||
import requests as http_requests
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
|
||||
@@ -28,6 +26,13 @@ TEXT_MODEL = "gemini-2.5-flash"
|
||||
IMG_FAST = "gemini-2.5-flash-image" # Nano Banana
|
||||
IMG_PRO = "gemini-3-pro-image-preview" # Nano Banana Pro
|
||||
|
||||
HEADERS = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
||||
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
}
|
||||
|
||||
|
||||
# ── Helpers ──────────────────────────────────────────────────
|
||||
|
||||
def _call_gemini(prompt: str, temperature: float = 0.7) -> dict:
|
||||
"""Call Gemini text model, return parsed JSON."""
|
||||
@@ -50,14 +55,30 @@ def _call_gemini(prompt: str, temperature: float = 0.7) -> dict:
|
||||
return {"error": "Failed to parse AI response", "raw": response.text[:500]}
|
||||
|
||||
|
||||
def _generate_image(prompt: str, model: str = IMG_PRO) -> tuple:
|
||||
"""Generate image via Nano Banana. Returns (filename, mime_type) or ('','')."""
|
||||
if not client:
|
||||
def _download_image(url: str) -> tuple:
|
||||
"""Download image, return (bytes, mime_type) or (None, None)."""
|
||||
try:
|
||||
r = http_requests.get(url, headers=HEADERS, timeout=15)
|
||||
r.raise_for_status()
|
||||
ct = r.headers.get("content-type", "image/jpeg")
|
||||
mime = ct.split(";")[0].strip()
|
||||
if "image" not in mime:
|
||||
mime = "image/jpeg"
|
||||
return (r.content, mime)
|
||||
except Exception as e:
|
||||
print(f"[download] Failed {url}: {e}")
|
||||
return (None, None)
|
||||
|
||||
|
||||
def _generate_image_from_ref(prompt: str, ref_bytes: bytes, ref_mime: str,
|
||||
model: str = IMG_PRO) -> tuple:
|
||||
"""Generate image using a real product photo as reference.
|
||||
Returns (filename, mime_type) or ('', '')."""
|
||||
if not client or not ref_bytes:
|
||||
return ("", "")
|
||||
|
||||
cache_key = hashlib.md5(f"{model}:{prompt}".encode()).hexdigest()[:14]
|
||||
# Check cache
|
||||
for ext in ("png", "jpg", "jpeg"):
|
||||
cache_key = hashlib.md5(f"{model}:{prompt}:{hashlib.md5(ref_bytes).hexdigest()[:8]}".encode()).hexdigest()[:16]
|
||||
for ext in ("png", "jpg", "jpeg", "webp"):
|
||||
cached = GEN_DIR / f"{cache_key}.{ext}"
|
||||
if cached.exists():
|
||||
return (cached.name, f"image/{ext}")
|
||||
@@ -65,7 +86,10 @@ def _generate_image(prompt: str, model: str = IMG_PRO) -> tuple:
|
||||
try:
|
||||
response = client.models.generate_content(
|
||||
model=model,
|
||||
contents=prompt,
|
||||
contents=[
|
||||
types.Part.from_bytes(data=ref_bytes, mime_type=ref_mime),
|
||||
prompt,
|
||||
],
|
||||
config=types.GenerateContentConfig(
|
||||
response_modalities=["IMAGE", "TEXT"],
|
||||
),
|
||||
@@ -82,74 +106,268 @@ def _generate_image(prompt: str, model: str = IMG_PRO) -> tuple:
|
||||
return ("", "")
|
||||
|
||||
|
||||
def _generate_image_text_only(prompt: str, model: str = IMG_PRO) -> tuple:
|
||||
"""Fallback: text-only image generation when no reference available."""
|
||||
if not client:
|
||||
return ("", "")
|
||||
cache_key = hashlib.md5(f"{model}:{prompt}".encode()).hexdigest()[:16]
|
||||
for ext in ("png", "jpg", "jpeg", "webp"):
|
||||
cached = GEN_DIR / f"{cache_key}.{ext}"
|
||||
if cached.exists():
|
||||
return (cached.name, f"image/{ext}")
|
||||
try:
|
||||
response = client.models.generate_content(
|
||||
model=model,
|
||||
contents=prompt,
|
||||
config=types.GenerateContentConfig(
|
||||
response_modalities=["IMAGE", "TEXT"],
|
||||
),
|
||||
)
|
||||
for part in response.candidates[0].content.parts:
|
||||
if part.inline_data and part.inline_data.data:
|
||||
mime = part.inline_data.mime_type or "image/png"
|
||||
ext = "jpg" if "jpeg" in mime or "jpg" in mime else "png"
|
||||
filename = f"{cache_key}.{ext}"
|
||||
(GEN_DIR / filename).write_bytes(part.inline_data.data)
|
||||
return (filename, mime)
|
||||
except Exception as e:
|
||||
print(f"[img-gen-fallback] {model} error: {e}")
|
||||
return ("", "")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# DEMO A — One Product → 12 Assets
|
||||
# DEMO A — Conversion-Optimised PDP + Asset Pack
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def generate_asset_pack(product: dict) -> dict:
|
||||
prompt = f"""You are a world-class ecommerce copywriter and marketing strategist for Just Vitamins (justvitamins.co.uk), a trusted UK vitamin brand — 4.8★ Trustpilot, 230,000+ customers, 20 years trading.
|
||||
"""Generate a full conversion-optimised PDP structure.
|
||||
|
||||
The output maps directly to a real product detail page layout:
|
||||
hero section, image gallery context, benefit bullets, trust bar,
|
||||
persuasion copy, FAQ, meta SEO, ad hooks, email subjects.
|
||||
"""
|
||||
prompt = f"""You are a senior ecommerce conversion strategist who has optimised PDPs for brands doing £50M+/yr.
|
||||
|
||||
You're writing for Just Vitamins (justvitamins.co.uk) — a trusted UK supplement brand:
|
||||
- 4.8★ Trustpilot (230,000+ customers)
|
||||
- 20 years trading
|
||||
- Eco bio-pouch packaging
|
||||
- UK-made, GMP certified
|
||||
|
||||
PRODUCT DATA:
|
||||
- Title: {product.get('title','')}
|
||||
- Subtitle: {product.get('subtitle','')}
|
||||
- Price: {product.get('price','')} for {product.get('quantity','')}
|
||||
- Per unit: {product.get('per_unit_cost','')}
|
||||
- Benefits: {json.dumps(product.get('benefits',[]))}
|
||||
- Description: {product.get('description','')[:1500]}
|
||||
- EFSA Health Claims: {json.dumps(product.get('health_claims',[]))}
|
||||
- Category: {product.get('category','')}
|
||||
- Benefits: {json.dumps(product.get('benefits',[]))}
|
||||
- Description: {product.get('description','')[:2000]}
|
||||
- EFSA Health Claims: {json.dumps(product.get('health_claims',[]))}
|
||||
|
||||
Generate a COMPLETE 12-asset marketing pack. Be specific to THIS product.
|
||||
Generate a conversion-optimised PDP structure following these ecommerce best practices:
|
||||
|
||||
1. ABOVE THE FOLD: Hero headline + value prop must stop the scroll
|
||||
2. GALLERY CONTEXT: Captions for each product image (main, lifestyle, scale, ingredients)
|
||||
3. BENEFIT BULLETS: Feature → Benefit → Proof format. Max 6, icon-ready.
|
||||
4. TRUST BAR: 4 trust signals (Trustpilot, years, GMP, eco)
|
||||
5. PERSUASION SECTION: "Why this formula" — science-led, 2-3 short paragraphs
|
||||
6. SOCIAL PROOF: Real-format review quote + stats
|
||||
7. PRICE FRAMING: Reframe as daily cost, compare to coffee/etc
|
||||
8. URGENCY: Ethical scarcity or time-based nudge
|
||||
9. FAQ: 4 questions that handle real objections (dosage, interactions, results timeline, quality)
|
||||
10. CTA: Primary + secondary button text
|
||||
11. META SEO: Title <60 chars, description <155 chars, primary keyword
|
||||
12. AD HOOKS: 5 scroll-stopping hooks for Meta/TikTok ads
|
||||
13. EMAIL: 3 subject lines with preview text for welcome/abandon/restock flows
|
||||
|
||||
Return JSON:
|
||||
{{
|
||||
"hero_angles": [
|
||||
{{"headline":"…","target_desire":"…","best_for":"…"}},
|
||||
{{"headline":"…","target_desire":"…","best_for":"…"}},
|
||||
{{"headline":"…","target_desire":"…","best_for":"…"}}
|
||||
"pdp": {{
|
||||
"hero_headline": "Conversion-optimised H1",
|
||||
"hero_subhead": "One sentence that makes them stay",
|
||||
"value_props": ["Short prop 1", "Short prop 2", "Short prop 3"],
|
||||
"gallery_captions": {{
|
||||
"main": "What the buyer sees first — describe ideal main shot context",
|
||||
"lifestyle": "Describe the lifestyle scene this product belongs in",
|
||||
"scale": "Describe a scale/size reference shot",
|
||||
"ingredients": "Describe the close-up ingredients/label shot"
|
||||
}},
|
||||
"benefit_bullets": [
|
||||
{{"icon": "emoji", "headline": "Short benefit", "detail": "Why it matters to the buyer", "proof": "Clinical or data point"}},
|
||||
{{"icon": "emoji", "headline": "…", "detail": "…", "proof": "…"}},
|
||||
{{"icon": "emoji", "headline": "…", "detail": "…", "proof": "…"}},
|
||||
{{"icon": "emoji", "headline": "…", "detail": "…", "proof": "…"}},
|
||||
{{"icon": "emoji", "headline": "…", "detail": "…", "proof": "…"}}
|
||||
],
|
||||
"trust_signals": [
|
||||
{{"icon": "⭐", "text": "4.8★ Trustpilot"}},
|
||||
{{"icon": "🏆", "text": "20 Years Trusted"}},
|
||||
{{"icon": "🇬🇧", "text": "UK Made · GMP"}},
|
||||
{{"icon": "🌿", "text": "Eco Bio-Pouch"}}
|
||||
],
|
||||
"why_section_title": "Why This Formula",
|
||||
"why_paragraphs": ["Science paragraph 1", "Absorption/bioavailability paragraph 2", "Who benefits paragraph 3"],
|
||||
"review_quote": {{"text": "Realistic-sounding 5★ review", "author": "Verified Buyer name", "stars": 5}},
|
||||
"stats_bar": [
|
||||
{{"number": "230,000+", "label": "Happy Customers"}},
|
||||
{{"number": "4.8★", "label": "Trustpilot Rating"}},
|
||||
{{"number": "20+", "label": "Years Trusted"}}
|
||||
],
|
||||
"price_display": {{
|
||||
"main_price": "{product.get('price','')}",
|
||||
"price_per_day": "Daily cost calculation",
|
||||
"comparison": "That's less than a daily coffee",
|
||||
"savings_note": "Subscribe & save note if applicable"
|
||||
}},
|
||||
"urgency_note": "Ethical urgency message",
|
||||
"cta_primary": "Primary button text",
|
||||
"cta_secondary": "Secondary action text",
|
||||
"faq": [
|
||||
{{"q": "Dosage question", "a": "Clear answer"}},
|
||||
{{"q": "Results timeline question", "a": "Honest answer"}},
|
||||
{{"q": "Quality/safety question", "a": "Trust-building answer"}},
|
||||
{{"q": "Interaction question", "a": "Responsible answer"}}
|
||||
],
|
||||
"usage_instructions": "Simple, friction-free how-to-take instructions"
|
||||
}},
|
||||
"meta_seo": {{
|
||||
"title": "SEO title under 60 chars",
|
||||
"description": "Meta description under 155 chars with primary keyword",
|
||||
"primary_keyword": "target keyword"
|
||||
}},
|
||||
"ad_hooks": [
|
||||
{{"hook": "Scroll-stopping first line", "angle": "What desire it targets", "platform": "Meta/TikTok/Google"}},
|
||||
{{"hook": "…", "angle": "…", "platform": "…"}},
|
||||
{{"hook": "…", "angle": "…", "platform": "…"}},
|
||||
{{"hook": "…", "angle": "…", "platform": "…"}},
|
||||
{{"hook": "…", "angle": "…", "platform": "…"}}
|
||||
],
|
||||
"pdp_copy": {{
|
||||
"headline":"…",
|
||||
"bullets":["…","…","…","…","…"],
|
||||
"faq":[{{"q":"…","a":"…"}},{{"q":"…","a":"…"}},{{"q":"…","a":"…"}}]
|
||||
}},
|
||||
"ad_hooks":["…","…","…","…","…"],
|
||||
"email_subjects":[
|
||||
{{"subject":"…","preview":"…"}},
|
||||
{{"subject":"…","preview":"…"}},
|
||||
{{"subject":"…","preview":"…"}}
|
||||
],
|
||||
"tiktok_script":{{
|
||||
"title":"…",
|
||||
"hook_0_3s":"…",
|
||||
"body_3_12s":"…",
|
||||
"cta_12_15s":"…",
|
||||
"why_it_works":"…"
|
||||
}},
|
||||
"blog_outline":{{
|
||||
"title":"…",
|
||||
"sections":["…","…","…","…","…"],
|
||||
"seo_keyword":"…",
|
||||
"monthly_searches":"…"
|
||||
}},
|
||||
"meta_seo":{{
|
||||
"title":"…","description":"…","title_chars":0,"desc_chars":0
|
||||
}},
|
||||
"alt_text":[
|
||||
{{"image_type":"Hero product shot","alt":"…","filename":"…"}},
|
||||
{{"image_type":"Lifestyle image","alt":"…","filename":"…"}}
|
||||
],
|
||||
"ab_variants":[
|
||||
{{"label":"Rational","copy":"…"}},
|
||||
{{"label":"Emotional","copy":"…"}},
|
||||
{{"label":"Social Proof","copy":"…"}}
|
||||
"email_subjects": [
|
||||
{{"flow": "Welcome", "subject": "Under 50 chars", "preview": "Preview text"}},
|
||||
{{"flow": "Abandon Cart", "subject": "…", "preview": "…"}},
|
||||
{{"flow": "Restock", "subject": "…", "preview": "…"}}
|
||||
]
|
||||
}}
|
||||
|
||||
RULES: EFSA claims must be accurate. Email subjects <50 chars. Meta title <60 chars, description <160 chars. UK English."""
|
||||
RULES:
|
||||
- EFSA health claims only — no therapeutic claims
|
||||
- UK English
|
||||
- Be specific to THIS product — no generic filler
|
||||
- Every bullet must pass the "so what?" test
|
||||
- Price framing must use real numbers from the data"""
|
||||
return _call_gemini(prompt, 0.75)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# IMAGE GENERATION — Reference-based product photography
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def generate_product_images(product: dict) -> dict:
|
||||
"""Generate ecommerce product images using the REAL scraped product photo.
|
||||
|
||||
Downloads the actual product image from justvitamins.co.uk, sends it
|
||||
to Gemini as visual reference, and generates conversion-optimised
|
||||
variations that maintain product consistency.
|
||||
"""
|
||||
title = product.get("title", "vitamin supplement")
|
||||
images = product.get("images", [])
|
||||
results = {"original_images": images}
|
||||
|
||||
# Download the primary product image as reference
|
||||
ref_bytes, ref_mime = None, None
|
||||
for img_url in images:
|
||||
ref_bytes, ref_mime = _download_image(img_url)
|
||||
if ref_bytes:
|
||||
# Save the original too
|
||||
orig_hash = hashlib.md5(ref_bytes).hexdigest()[:12]
|
||||
ext = "jpg" if "jpeg" in ref_mime else "png"
|
||||
orig_name = f"orig_{orig_hash}.{ext}"
|
||||
orig_path = GEN_DIR / orig_name
|
||||
if not orig_path.exists():
|
||||
orig_path.write_bytes(ref_bytes)
|
||||
results["original"] = {"filename": orig_name, "mime": ref_mime, "source": img_url}
|
||||
break
|
||||
|
||||
if not ref_bytes:
|
||||
results["error"] = "Could not download product image from source"
|
||||
return results
|
||||
|
||||
# ── 1. HERO: Clean white-background product shot ─────────
|
||||
hero_prompt = (
|
||||
"You are a professional ecommerce product photographer. "
|
||||
"Take this exact product and create a clean, premium product photograph. "
|
||||
"KEEP THE EXACT SAME PRODUCT — same packaging, same label, same colours. "
|
||||
"Place it on a pure white background with soft studio lighting and subtle shadow. "
|
||||
"The product should be centred, well-lit, and sharp. "
|
||||
"This is the main product image for an online store — it must look professional "
|
||||
"and match Amazon/Shopify product photography standards. "
|
||||
"Do NOT change the product, do NOT add text, do NOT alter the packaging."
|
||||
)
|
||||
fname, mime = _generate_image_from_ref(hero_prompt, ref_bytes, ref_mime, IMG_PRO)
|
||||
results["hero"] = {"filename": fname, "mime": mime, "model": "Nano Banana Pro",
|
||||
"caption": "Main product shot — clean white background, studio lighting"}
|
||||
|
||||
# ── 2. LIFESTYLE: Product in real-life setting ───────────
|
||||
lifestyle_prompt = (
|
||||
"You are a lifestyle product photographer for a premium health brand. "
|
||||
"Take this exact supplement product and photograph it in a beautiful morning "
|
||||
"kitchen scene. Place the EXACT SAME product on a light marble countertop "
|
||||
"with morning sunlight, a glass of water, and fresh green leaves nearby. "
|
||||
"Warm, healthy, inviting mood. Shallow depth of field with product in sharp focus. "
|
||||
"KEEP the product exactly as it is — same packaging, same label. "
|
||||
"Do NOT change, redesign, or alter the product in any way. "
|
||||
"Professional lifestyle product photography for an ecommerce website."
|
||||
)
|
||||
fname, mime = _generate_image_from_ref(lifestyle_prompt, ref_bytes, ref_mime, IMG_PRO)
|
||||
results["lifestyle"] = {"filename": fname, "mime": mime, "model": "Nano Banana Pro",
|
||||
"caption": "Lifestyle shot — morning kitchen scene, natural light"}
|
||||
|
||||
# ── 3. SCALE: Product with hand/everyday object ──────────
|
||||
scale_prompt = (
|
||||
"You are a product photographer creating a scale reference image. "
|
||||
"Show this exact supplement product being held in a person's hand, "
|
||||
"or placed next to a coffee mug for size reference. "
|
||||
"The product must be the EXACT SAME — same packaging, same label, same design. "
|
||||
"Clean, bright lighting. Natural skin tones. "
|
||||
"This helps online shoppers understand the actual size of the product. "
|
||||
"Do NOT modify, redesign, or change the product appearance."
|
||||
)
|
||||
fname, mime = _generate_image_from_ref(scale_prompt, ref_bytes, ref_mime, IMG_FAST)
|
||||
results["scale"] = {"filename": fname, "mime": mime, "model": "Nano Banana",
|
||||
"caption": "Scale reference — real-world size context"}
|
||||
|
||||
# ── 4. INGREDIENTS: Close-up detail shot ─────────────────
|
||||
ingredients_prompt = (
|
||||
"You are a detail product photographer. "
|
||||
"Create a close-up macro shot of this supplement product, focusing on the "
|
||||
"label, ingredients panel, or the capsules/tablets spilling out of the packaging. "
|
||||
"KEEP the exact same product — same packaging, same branding. "
|
||||
"Sharp focus on details, soft bokeh background. "
|
||||
"Premium, trustworthy feel — suitable for a health brand website. "
|
||||
"Do NOT change the product design or branding."
|
||||
)
|
||||
fname, mime = _generate_image_from_ref(ingredients_prompt, ref_bytes, ref_mime, IMG_FAST)
|
||||
results["ingredients"] = {"filename": fname, "mime": mime, "model": "Nano Banana",
|
||||
"caption": "Detail shot — ingredients & quality close-up"}
|
||||
|
||||
# ── 5. BANNER: Wide hero banner for category/landing page ─
|
||||
banner_prompt = (
|
||||
"You are creating a wide ecommerce hero banner image. "
|
||||
"Place this exact supplement product on the right side of a wide composition. "
|
||||
"The left side should have clean space for text overlay (no text in the image). "
|
||||
"Use a soft gradient background in natural greens and creams. "
|
||||
"Include subtle natural elements — leaves, light rays, bokeh. "
|
||||
"KEEP the product exactly as it is — same packaging, same label. "
|
||||
"Wide aspect ratio, suitable for a website hero banner. "
|
||||
"Do NOT add any text, logos, or modify the product."
|
||||
)
|
||||
fname, mime = _generate_image_from_ref(banner_prompt, ref_bytes, ref_mime, IMG_PRO)
|
||||
results["banner"] = {"filename": fname, "mime": mime, "model": "Nano Banana Pro",
|
||||
"caption": "Hero banner — wide format for landing pages"}
|
||||
|
||||
return results
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# DEMO B — Competitor X-Ray
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
@@ -201,51 +419,58 @@ RULES: No false claims. EFSA/ASA compliant. Strategic, not aggressive."""
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
STYLE_INSTRUCTIONS = {
|
||||
"balanced": "Balanced, trustworthy DTC supplement voice. Mix emotional hooks with rational proof.",
|
||||
"premium": "Premium aspirational voice. Sophisticated language, formulation science, target affluent buyers.",
|
||||
"dr": "Direct-response style. Pattern interrupts, urgency, specific numbers, scarcity, stacked bonuses.",
|
||||
"medical": "Clinical, medically-safe tone. Proper nomenclature, structure/function claims only, FDA disclaimer.",
|
||||
"balanced": "Balanced, trustworthy DTC supplement voice. Mix emotional hooks with rational proof. Think Huel or Athletic Greens.",
|
||||
"premium": "Premium aspirational voice. Sophisticated language, formulation science focus, target affluent health-conscious buyers. Think Lyma or Seed.",
|
||||
"dr": "Direct-response style. Pattern interrupts, urgency, specific numbers, stacked value, scarcity. Think agora-style health copy.",
|
||||
"medical": "Clinical, medically-safe tone. Proper nomenclature, structure/function claims only, evidence citations. Think Thorne or Pure Encapsulations.",
|
||||
}
|
||||
|
||||
def pdp_surgeon(product: dict, style: str = "balanced") -> dict:
|
||||
instruction = STYLE_INSTRUCTIONS.get(style, STYLE_INSTRUCTIONS["balanced"])
|
||||
prompt = f"""You are a PDP conversion specialist rewriting a product page for Just Vitamins.
|
||||
prompt = f"""You are an elite PDP conversion specialist. You've increased CVR by 30-80% for DTC supplement brands.
|
||||
|
||||
PRODUCT:
|
||||
- Title: {product.get('title','')}
|
||||
- Subtitle: {product.get('subtitle','')}
|
||||
- Price: {product.get('price','')} for {product.get('quantity','')}
|
||||
- Per unit: {product.get('per_unit_cost','')}
|
||||
- Benefits: {json.dumps(product.get('benefits',[]))}
|
||||
- Description: {product.get('description','')[:1500]}
|
||||
- EFSA Claims: {json.dumps(product.get('health_claims',[]))}
|
||||
|
||||
STYLE: {style.upper()} — {instruction}
|
||||
|
||||
Rewrite the PDP. For EVERY change add a conversion annotation with estimated % impact.
|
||||
Rewrite the entire PDP in this style. For EVERY element, add a conversion annotation explaining the psychology and estimated lift.
|
||||
|
||||
Output JSON:
|
||||
{{
|
||||
"style":"{style}",
|
||||
"title":"…",
|
||||
"subtitle":"…",
|
||||
"hero_copy":"Main persuasion paragraph",
|
||||
"hero_annotation":"Why this works — conversion impact",
|
||||
"title":"Conversion-optimised title",
|
||||
"subtitle":"Desire-triggering subtitle",
|
||||
"hero_copy":"2-3 sentence persuasion paragraph — the most important copy on the page",
|
||||
"hero_annotation":"Why this works — which conversion principle, estimated % lift",
|
||||
"bullets":[
|
||||
{{"text":"…","annotation":"Why — e.g. +22% add-to-cart"}},
|
||||
{{"text":"Feature → Benefit → Proof bullet","annotation":"Conversion principle + estimated lift"}},
|
||||
{{"text":"…","annotation":"…"}},
|
||||
{{"text":"…","annotation":"…"}},
|
||||
{{"text":"…","annotation":"…"}},
|
||||
{{"text":"…","annotation":"…"}}
|
||||
],
|
||||
"social_proof":"Line using 4.8★ Trustpilot, 230K customers",
|
||||
"social_proof_annotation":"…",
|
||||
"price_reframe":"Reframe price as no-brainer",
|
||||
"price_annotation":"…",
|
||||
"cta_text":"CTA button text",
|
||||
"usage_instruction":"How to take — written to remove friction",
|
||||
"usage_annotation":"…"
|
||||
"social_proof":"Specific social proof line using 4.8★ Trustpilot, 230K customers, years trading",
|
||||
"social_proof_annotation":"Which social proof principle this uses + estimated lift",
|
||||
"price_reframe":"Reframe the price as a no-brainer — use daily cost, comparison anchoring",
|
||||
"price_annotation":"Price psychology principle + estimated lift",
|
||||
"cta_text":"CTA button text — action-oriented, benefit-driven",
|
||||
"cta_annotation":"Why this CTA works",
|
||||
"usage_instruction":"How to take — written to build routine and reduce friction",
|
||||
"usage_annotation":"How this reduces returns and increases LTV"
|
||||
}}
|
||||
|
||||
RULES: EFSA claims accurate. Realistic % lifts (5-40%). UK English."""
|
||||
RULES:
|
||||
- EFSA claims only — no disease claims, no cure claims
|
||||
- Realistic lift estimates (5-40% range)
|
||||
- UK English
|
||||
- Every annotation must cite a specific conversion principle (Cialdini, Kahneman, Fogg, etc.)"""
|
||||
return _call_gemini(prompt, 0.8)
|
||||
|
||||
|
||||
@@ -254,17 +479,15 @@ RULES: EFSA claims accurate. Realistic % lifts (5-40%). UK English."""
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def optimise_pdp_copy(product: dict) -> dict:
|
||||
prompt = f"""You are an expert ecommerce copywriter for Just Vitamins (justvitamins.co.uk) — 4.8★ Trustpilot, 230K+ customers, 20 years.
|
||||
prompt = f"""You are an expert ecommerce copywriter for Just Vitamins — 4.8★ Trustpilot, 230K+ customers, 20 years.
|
||||
|
||||
PRODUCT:
|
||||
- Title: {product['title']}
|
||||
- Subtitle: {product.get('subtitle','')}
|
||||
- Price: {product.get('price','')} for {product.get('quantity','')}
|
||||
- Per unit: {product.get('per_unit_cost','')}
|
||||
- Benefits: {json.dumps(product.get('benefits',[]))}
|
||||
- Description: {product.get('description','')[:1500]}
|
||||
- EFSA Claims: {json.dumps(product.get('health_claims',[]))}
|
||||
- Category: {product.get('category','')}
|
||||
|
||||
Rewrite everything. Output JSON:
|
||||
{{
|
||||
@@ -278,51 +501,5 @@ Rewrite everything. Output JSON:
|
||||
"faqs":[{{"q":"…","a":"…"}},{{"q":"…","a":"…"}},{{"q":"…","a":"…"}}]
|
||||
}}
|
||||
|
||||
Keep EFSA claims accurate. UK English."""
|
||||
EFSA claims only. UK English."""
|
||||
return _call_gemini(prompt, 0.7)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# IMAGE GENERATION — Nano Banana / Pro
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
def generate_product_images(product: dict) -> dict:
|
||||
"""Generate product images using Nano Banana (fast) + Nano Banana Pro (hero)."""
|
||||
title = product.get("title", "vitamin supplement")
|
||||
category = product.get("category", "vitamins")
|
||||
results = {}
|
||||
|
||||
# Hero — use Nano Banana Pro for best quality
|
||||
hero_prompt = (
|
||||
f"Professional product hero photograph for an ecommerce page. "
|
||||
f"A premium eco-friendly kraft bio-pouch supplement packaging for '{title}' "
|
||||
f"by Just Vitamins UK. Clean cream/white background, soft natural shadows, "
|
||||
f"minimalist staging with a small ceramic dish of capsules/tablets beside it "
|
||||
f"and a sprig of green herb. Premium, trustworthy, modern. "
|
||||
f"Commercial product photography, sharp focus, studio lighting."
|
||||
)
|
||||
fname, mime = _generate_image(hero_prompt, IMG_PRO)
|
||||
results["hero"] = {"filename": fname, "mime": mime, "model": "Nano Banana Pro"}
|
||||
|
||||
# Lifestyle — Nano Banana (fast)
|
||||
lifestyle_prompt = (
|
||||
f"Lifestyle product photograph: '{title}' by Just Vitamins UK in an eco "
|
||||
f"bio-pouch sitting on a marble kitchen counter with morning sunlight "
|
||||
f"streaming through a window. Fresh fruit, a glass of water, and green "
|
||||
f"leaves nearby. Warm, healthy, inviting mood. Shallow depth of field. "
|
||||
f"Photorealistic, 4K quality."
|
||||
)
|
||||
fname, mime = _generate_image(lifestyle_prompt, IMG_FAST)
|
||||
results["lifestyle"] = {"filename": fname, "mime": mime, "model": "Nano Banana"}
|
||||
|
||||
# Benefits — Nano Banana Pro
|
||||
benefits_prompt = (
|
||||
f"Clean infographic-style illustration showing health benefits of "
|
||||
f"{category} supplements: strong bones, immune support, energy, vitality. "
|
||||
f"Modern flat design, warm gold/green/cream palette. "
|
||||
f"Professional, suitable for a premium UK health brand website. No text."
|
||||
)
|
||||
fname, mime = _generate_image(benefits_prompt, IMG_PRO)
|
||||
results["benefits"] = {"filename": fname, "mime": mime, "model": "Nano Banana Pro"}
|
||||
|
||||
return results
|
||||
|
||||
Reference in New Issue
Block a user