@@ -72,7 +72,6 @@ async def analyze_landing_page(self, url: str) -> Dict:
72
72
screenshot_path = None
73
73
timestamp = datetime .now ().strftime ("%Y%m%d_%H%M%S" )
74
74
75
- # Take screenshot after page fully loads
76
75
async def screenshot_callback (agent_instance ):
77
76
nonlocal screenshot_path
78
77
import asyncio
@@ -103,7 +102,10 @@ async def screenshot_callback(agent_instance):
103
102
}
104
103
105
104
class AdGenerator :
106
- def __init__ (self , api_key : str = GOOGLE_API_KEY ):
105
+ def __init__ (self , api_key : str | None = GOOGLE_API_KEY ):
106
+ if not api_key :
107
+ raise ValueError ("GOOGLE_API_KEY is missing or empty – set the environment variable or pass api_key explicitly" )
108
+
107
109
self .client = genai .Client (api_key = api_key )
108
110
self .output_dir = Path ("output" )
109
111
self .output_dir .mkdir (exist_ok = True )
@@ -128,30 +130,44 @@ def create_ad_prompt(self, browser_analysis: str) -> str:
128
130
Style: Modern Instagram advertisement, (1:1), scroll-stopping, professional but playful, conversion-focused"""
129
131
return prompt
130
132
131
- async def generate_ad_image (self , prompt : str , screenshot_path : Path = None ) -> bytes :
133
+ async def generate_ad_image (self , prompt : str , screenshot_path : Path | None = None ) -> bytes :
134
+ """Generate ad image bytes using Gemini. Returns *empty bytes* on failure."""
135
+
132
136
try :
133
- contents = [prompt ]
134
-
137
+ from typing import Any , List
138
+
139
+ contents : List [Any ] = [prompt ]
140
+
135
141
if screenshot_path and screenshot_path .exists ():
136
- screenshot_prompt = f"\n \n Here is the actual landing page screenshot to reference for design inspiration, colors, layout, and visual style:"
137
- text_part = prompt + screenshot_prompt
138
- img = Image .open (screenshot_path )
139
- w ,h = img .size
140
- img = img .crop (((w - min (w ,h ))// 2 ,(h - min (w ,h ))// 2 ,(w + min (w ,h ))// 2 ,(h + min (w ,h ))// 2 ))
141
- contents = [text_part ,img ]
142
-
142
+ screenshot_prompt = (
143
+ "\n \n Here is the actual landing page screenshot to reference for design inspiration, "
144
+ "colors, layout, and visual style:"
145
+ )
146
+
147
+ img = Image .open (screenshot_path )
148
+ w , h = img .size
149
+ side = min (w , h )
150
+ img = img .crop (((w - side ) // 2 , (h - side ) // 2 , (w + side ) // 2 , (h + side ) // 2 ))
151
+
152
+ contents = [prompt + screenshot_prompt , img ]
153
+
143
154
response = self .client .models .generate_content (
144
155
model = "gemini-2.5-flash-image-preview" ,
145
- contents = contents
156
+ contents = contents ,
146
157
)
147
-
148
- for part in response .candidates [0 ].content .parts :
149
- if hasattr (part , 'inline_data' ) and part .inline_data :
150
- return part .inline_data .data
151
-
158
+
159
+ cand = getattr (response , "candidates" , None )
160
+ if cand :
161
+ for part in getattr (cand [0 ].content , "parts" , []):
162
+ inline = getattr (part , "inline_data" , None )
163
+ if inline :
164
+ return inline .data
165
+
152
166
except Exception as e :
153
167
print (f"❌ Image generation failed: { e } " )
154
-
168
+
169
+ return b""
170
+
155
171
async def save_results (self , ad_image : bytes , prompt : str , analysis : str , url : str , timestamp : str ) -> str :
156
172
image_path = self .output_dir / f"ad_{ timestamp } .png"
157
173
with open (image_path , 'wb' ) as f :
0 commit comments