Spaces:
Sleeping
Sleeping
| """ | |
| Generate ad creative packages from product data and analysis. | |
| """ | |
| import json | |
| from datetime import date | |
| from app.llm import call_llm, extract_json | |
| def _upcoming_festival_from_web() -> str: | |
| """Web search for upcoming Indian festival; on failure tell LLM to use date-relevant festival.""" | |
| today = date.today() | |
| try: | |
| from ddgs import DDGS | |
| query = f"upcoming Indian festivals {today.strftime('%B %Y')} next festival dates" | |
| with DDGS() as ddgs: | |
| for r in ddgs.text(query, max_results=2): | |
| title = (r.get("title") or "").strip() | |
| body = (r.get("body") or "").strip() | |
| if title or body: | |
| return f"{title}. {body}"[:450].strip() | |
| except Exception: | |
| pass | |
| return f"Use the upcoming Indian festival relevant to {today.strftime('%B %Y')} (e.g. Diwali, Holi, Children's Day)." | |
| def generate_ad_creatives( | |
| product_data: dict, analysis: dict, target_audience: list[str] | None = None | |
| ) -> dict: | |
| """Generate 20 ad creative packages (17 product-focused, 3 no-product).""" | |
| price = product_data.get("price", "") | |
| product_name = product_data.get("product_name", "") | |
| today_str = date.today().strftime("%d %B %Y") | |
| festival_context = _upcoming_festival_from_web() | |
| audience_block = "" | |
| if target_audience and len(target_audience) > 0: | |
| audience_list = ", ".join(target_audience) | |
| audience_block = f""" | |
| TARGET AUDIENCES (tailor copy and concepts to these segments): | |
| {audience_list} | |
| Headlines, body copy, scene prompts, and CTA must speak directly to these audiences. Match tone, occasions, and value framing to the selected segments (e.g. parents of toddlers, gift buyers, Montessori seekers, festive gifting). Do not use generic "parents" or one-size-fits-all copy when specific segments are provided. | |
| """ | |
| prompt = f""" | |
| You are an elite, performance-driven DTC kids' and educational toys ad creative director (Hawbeez: wooden, Montessori, eco-friendly toys). | |
| Your job is to generate EXACTLY 20 ad creatives. | |
| {audience_block} | |
| Today's date: {today_str}. For the "Current Festival" creative, use this upcoming/current festival: {festival_context}. Use it for headlines, copy, and scene using a hyper-specific festival related elements (e.g. gifting for Diwali, Children's Day). | |
| ━━━━━━━━━━━━━━━━━━ | |
| OUTPUT DISTRIBUTION (STRICT) | |
| ━━━━━━━━━━━━━━━━━━ | |
| PRODUCT-FOCUSED (17 total): Dark Moody ×2, Flat Lay ×1, Social Proof ×1, Witty Copy-Led ×1, Lifestyle (tight crop, realistic—child/parent with toy) ×1, Texture Contrast ×1, Shadow Play ×1, Gift Reveal ×1, Current Festival ×1, Price Confidence ×1, One-Word Dominance ×1, Macro Extreme Closeup ×1, Minimal ×1, Heirloom Depth ×1, Power Suit ×1, Scarcity Drop ×1. | |
| Set category to the frame name. Invent headlines, body copy, and scene prompts that fit each frame and the product—no prescribed lines. All copy and scenes should be toy- and parent-focused. | |
| NO-PRODUCT (3 total): Instagram DM screenshot ×1, WhatsApp chat style ×1, Twitter/X post style ×1. Copy can reference parent reviews, offers, "Shop now" for toys. | |
| ━━━━━━━━━━━━━━━━━━ | |
| PRODUCT-FOCUSED RULES | |
| ━━━━━━━━━━━━━━━━━━ | |
| - Product (toy) MUST dominate visual hierarchy. Product must occupy 40–60% of visual attention. Always sharpest focus. Clearly legible on mobile. Lifestyle shots MUST be tightly cropped (child or parent with toy in frame). | |
| ━━━━━━━━━━━━━━━━━━ | |
| NO-PRODUCT RULES | |
| ━━━━━━━━━━━━━━━━━━ | |
| - DO NOT show product. Image prompt should describe UI layout, screenshot style, card design, Twitter/X post layout (handle, post text, engagement UI), or typography layout only. | |
| ━━━━━━━━━━━━━━━━━━ | |
| PRICE RULE | |
| ━━━━━━━━━━━━━━━━━━ | |
| Include price in EXACTLY 7 creatives. For those 7: price_original = final price + ₹500, price_final = {price}. All other creatives: includes_price = false. For those 7, set price_original_style to one of: strikethrough | slant | faded | small | double (vary across creatives; match style to concept). CRITICAL: Only the original (higher) price is styled with strikethrough/slant/etc.; the final (selling) price must NEVER be shown with strikethrough—it is the main price customers pay. | |
| ━━━━━━━━━━━━━━━━━━ | |
| CTA POSITION | |
| ━━━━━━━━━━━━━━━━━━ | |
| Vary CTA placement across creatives. Set "cta_position" to exactly one of: bottom_center | bottom_left | bottom_right | top_center | top_left | top_right. Do not put every CTA in bottom_center—use corners and top positions too so we get layout variety. | |
| ━━━━━━━━━━━━━━━━━━ | |
| SCENE PROMPT RULES | |
| ━━━━━━━━━━━━━━━━━━ | |
| For PRODUCT creatives: Describe ONLY background, lighting, framing, scene. NEVER describe the toy/product itself. End EVERY scene_prompt with: "Ensure the toy/product is the dominant focal point in sharp focus. Preserve the product exactly as shown. Square 1:1, 1080x1080px." | |
| For NO-PRODUCT creatives: Describe UI layout, typography, screenshot realism, card structure, or Twitter/X post layout (tweet card, handle, text, likes/retweets) only. Do NOT include the mandatory toy/product sentence. | |
| ━━━━━━━━━━━━━━━━━━ | |
| PRODUCT DATA | |
| ━━━━━━━━━━━━━━━━━━ | |
| {json.dumps(product_data, indent=2)} | |
| ━━━━━━━━━━━━━━━━━━ | |
| STRATEGIC ANALYSIS | |
| ━━━━━━━━━━━━━━━━━━ | |
| {json.dumps(analysis, indent=2)} | |
| ━━━━━━━━━━━━━━━━━━ | |
| OUTPUT FORMAT (STRICT JSON ONLY) | |
| ━━━━━━━━━━━━━━━━━━ | |
| Return ONLY valid JSON. No markdown. No extra text. | |
| {{ | |
| "ad_creatives": [ | |
| {{ | |
| "id": 1, | |
| "concept_name": "short name", | |
| "creative_type": "PRODUCT" or "NO_PRODUCT", | |
| "category": "one of the defined categories", | |
| "target_emotion": "single dominant emotion", | |
| "best_platform": "Instagram Feed / Instagram Story / Facebook Ad", | |
| "includes_price": true or false, | |
| "shooting_angle": "precise framing or null", | |
| "color_world": "lighting and color style", | |
| "scene_prompt": "see rules above", | |
| "ad_copy": {{ | |
| "headline_serif": "UPPERCASE", | |
| "headline_script": "Friendly script line", | |
| "body": "1–2 lines", | |
| "product_label": "{product_name}", | |
| "price_original": "higher/compare-at price, same currency as price_final (₹ or Rs); strikethrough style", | |
| "price_final": "selling price only, plain text (e.g. ₹2,499). Same currency as price_original. Never include strikethrough, Unicode strikethrough, or any decoration in this value.", | |
| "price_original_style": "strikethrough|slant|faded|small if includes_price (applies to price_original only)", | |
| "cta": "CTA" | |
| }}, | |
| "cta_position": "bottom_center | bottom_left | bottom_right | top_center | top_left | top_right (vary across creatives)", | |
| "layout_notes": "how layout reinforces message" | |
| }} | |
| ] | |
| }} | |
| Validate: 20 total creatives, 17 PRODUCT, 3 NO_PRODUCT, exactly 7 includes_price=true. | |
| """ | |
| raw = call_llm(prompt, temperature=0.85) | |
| return extract_json(raw) | |