- Flask + gunicorn backend replacing static nginx - 3 live AI demos powered by Gemini 2.5 Flash - Nano Banana + Nano Banana Pro for product image generation - Real JV ecommerce dashboard (728K orders, 230K customers, 4MB data) - AI Infrastructure Proposal + Offer pages - Live product scraper for justvitamins.co.uk + competitor pages - API: /api/scrape, /api/generate-pack, /api/competitor-xray, /api/pdp-surgeon, /api/generate-images
329 lines
14 KiB
Python
329 lines
14 KiB
Python
"""AI engine — Gemini for copy, Nano Banana / Nano Banana Pro for imagery.
|
|
|
|
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)
|
|
|
|
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
|
|
"""
|
|
|
|
import os, json, hashlib, re
|
|
from pathlib import Path
|
|
from google import genai
|
|
from google.genai import types
|
|
|
|
GEMINI_KEY = os.environ.get("GEMINI_API_KEY", "")
|
|
client = genai.Client(api_key=GEMINI_KEY) if GEMINI_KEY else None
|
|
|
|
GEN_DIR = Path(__file__).parent / "generated"
|
|
GEN_DIR.mkdir(exist_ok=True)
|
|
|
|
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
|
|
|
|
|
|
def _call_gemini(prompt: str, temperature: float = 0.7) -> dict:
|
|
"""Call Gemini text model, return parsed JSON."""
|
|
if not client:
|
|
return {"error": "GEMINI_API_KEY not configured"}
|
|
response = client.models.generate_content(
|
|
model=TEXT_MODEL,
|
|
contents=prompt,
|
|
config=types.GenerateContentConfig(
|
|
temperature=temperature,
|
|
response_mime_type="application/json",
|
|
),
|
|
)
|
|
try:
|
|
return json.loads(response.text)
|
|
except json.JSONDecodeError:
|
|
match = re.search(r'\{.*\}', response.text, re.DOTALL)
|
|
if match:
|
|
return json.loads(match.group())
|
|
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:
|
|
return ("", "")
|
|
|
|
cache_key = hashlib.md5(f"{model}:{prompt}".encode()).hexdigest()[:14]
|
|
# Check cache
|
|
for ext in ("png", "jpg", "jpeg"):
|
|
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] {model} error: {e}")
|
|
return ("", "")
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# DEMO A — One Product → 12 Assets
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
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.
|
|
|
|
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','')}
|
|
|
|
Generate a COMPLETE 12-asset marketing pack. Be specific to THIS product.
|
|
|
|
Return JSON:
|
|
{{
|
|
"hero_angles": [
|
|
{{"headline":"…","target_desire":"…","best_for":"…"}},
|
|
{{"headline":"…","target_desire":"…","best_for":"…"}},
|
|
{{"headline":"…","target_desire":"…","best_for":"…"}}
|
|
],
|
|
"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":"…"}}
|
|
]
|
|
}}
|
|
|
|
RULES: EFSA claims must be accurate. Email subjects <50 chars. Meta title <60 chars, description <160 chars. UK English."""
|
|
return _call_gemini(prompt, 0.75)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# DEMO B — Competitor X-Ray
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
def competitor_xray(competitor_data: dict) -> dict:
|
|
prompt = f"""You are a competitive intelligence analyst for Just Vitamins (justvitamins.co.uk) — trusted UK supplement brand, 20 yrs, 4.8★ Trustpilot, 230K+ customers, eco bio-pouch packaging.
|
|
|
|
COMPETITOR PAGE:
|
|
- URL: {competitor_data.get('url','')}
|
|
- Title: {competitor_data.get('title','')}
|
|
- Brand: {competitor_data.get('brand','')}
|
|
- Price: {competitor_data.get('price','')}
|
|
- Meta: {competitor_data.get('meta_description','')}
|
|
- Description: {competitor_data.get('description','')[:2000]}
|
|
- Bullets: {json.dumps(competitor_data.get('bullets',[])[:10])}
|
|
- Page extract: {competitor_data.get('raw_text','')[:2000]}
|
|
|
|
Perform a deep competitive analysis. Output JSON:
|
|
{{
|
|
"competitor_name":"…",
|
|
"what_theyre_selling":"One sentence — what they're REALLY selling (emotional promise, not product)",
|
|
"top_5_tactics":[
|
|
{{"tactic":"…","explanation":"…"}},
|
|
{{"tactic":"…","explanation":"…"}},
|
|
{{"tactic":"…","explanation":"…"}},
|
|
{{"tactic":"…","explanation":"…"}},
|
|
{{"tactic":"…","explanation":"…"}}
|
|
],
|
|
"weakest_claim":"Their most vulnerable claim / biggest gap",
|
|
"jv_hero_section":{{
|
|
"headline":"Killer headline positioning JV as better",
|
|
"body":"2-3 sentences of copy that beats them without naming them",
|
|
"value_prop":"Single most powerful reason to choose JV"
|
|
}},
|
|
"differentiators":[
|
|
{{"point":"…","proof_idea":"Specific content or test idea to prove it"}},
|
|
{{"point":"…","proof_idea":"…"}},
|
|
{{"point":"…","proof_idea":"…"}}
|
|
],
|
|
"do_not_say":["Compliance note 1","…","…","…"]
|
|
}}
|
|
|
|
RULES: No false claims. EFSA/ASA compliant. Strategic, not aggressive."""
|
|
return _call_gemini(prompt, 0.7)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# DEMO C — PDP Surgeon
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
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.",
|
|
}
|
|
|
|
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.
|
|
|
|
PRODUCT:
|
|
- Title: {product.get('title','')}
|
|
- Subtitle: {product.get('subtitle','')}
|
|
- Price: {product.get('price','')} for {product.get('quantity','')}
|
|
- 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.
|
|
|
|
Output JSON:
|
|
{{
|
|
"style":"{style}",
|
|
"title":"…",
|
|
"subtitle":"…",
|
|
"hero_copy":"Main persuasion paragraph",
|
|
"hero_annotation":"Why this works — conversion impact",
|
|
"bullets":[
|
|
{{"text":"…","annotation":"Why — e.g. +22% add-to-cart"}},
|
|
{{"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":"…"
|
|
}}
|
|
|
|
RULES: EFSA claims accurate. Realistic % lifts (5-40%). UK English."""
|
|
return _call_gemini(prompt, 0.8)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# FULL PDP OPTIMISATION
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
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.
|
|
|
|
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:
|
|
{{
|
|
"seo_title":"…",
|
|
"subtitle":"…",
|
|
"benefit_bullets":["…","…","…","…","…"],
|
|
"why_section":"Para 1\\n\\nPara 2\\n\\nPara 3",
|
|
"who_for":["…","…","…"],
|
|
"social_proof":"…",
|
|
"meta_description":"…",
|
|
"faqs":[{{"q":"…","a":"…"}},{{"q":"…","a":"…"}},{{"q":"…","a":"…"}}]
|
|
}}
|
|
|
|
Keep EFSA claims accurate. 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
|