Files
justvitamin/ai_engine.py
Omair Saleh 09d837a660 v2: Live Flask app — real Gemini AI demos, Nano Banana image gen, real £19.4M data dashboard
- 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
2026-03-02 20:02:25 +08:00

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