Noo88ear commited on
Commit
da3d35e
Β·
verified Β·
1 Parent(s): ce2f7dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +791 -130
app.py CHANGED
@@ -1,154 +1,815 @@
1
- import gradio as gr
2
- import numpy as np
3
- import random
4
 
5
- # import spaces #[uncomment to use ZeroGPU]
6
- from diffusers import DiffusionPipeline
7
- import torch
 
8
 
9
- device = "cuda" if torch.cuda.is_available() else "cpu"
10
- model_repo_id = "stabilityai/sdxl-turbo" # Replace to the model you would like to use
 
11
 
12
- if torch.cuda.is_available():
13
- torch_dtype = torch.float16
14
- else:
15
- torch_dtype = torch.float32
 
 
 
 
 
 
 
 
16
 
17
- pipe = DiffusionPipeline.from_pretrained(model_repo_id, torch_dtype=torch_dtype)
18
- pipe = pipe.to(device)
 
19
 
20
- MAX_SEED = np.iinfo(np.int32).max
21
  MAX_IMAGE_SIZE = 1024
 
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
- # @spaces.GPU #[uncomment to use ZeroGPU]
25
- def infer(
26
- prompt,
27
- negative_prompt,
28
- seed,
29
- randomize_seed,
30
- width,
31
- height,
32
- guidance_scale,
33
- num_inference_steps,
34
- progress=gr.Progress(track_tqdm=True),
35
- ):
36
- if randomize_seed:
37
- seed = random.randint(0, MAX_SEED)
38
-
39
- generator = torch.Generator().manual_seed(seed)
40
-
41
- image = pipe(
42
- prompt=prompt,
43
- negative_prompt=negative_prompt,
44
- guidance_scale=guidance_scale,
45
- num_inference_steps=num_inference_steps,
46
- width=width,
47
- height=height,
48
- generator=generator,
49
- ).images[0]
50
-
51
- return image, seed
52
-
53
-
54
- examples = [
55
- "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k",
56
- "An astronaut riding a green horse",
57
- "A delicious ceviche cheesecake slice",
58
- ]
59
-
60
- css = """
61
- #col-container {
62
- margin: 0 auto;
63
- max-width: 640px;
64
- }
65
- """
66
 
67
- with gr.Blocks(css=css) as demo:
68
- with gr.Column(elem_id="col-container"):
69
- gr.Markdown(" # Text-to-Image Gradio Template")
70
 
71
- with gr.Row():
72
- prompt = gr.Text(
73
- label="Prompt",
74
- show_label=False,
75
- max_lines=1,
76
- placeholder="Enter your prompt",
77
- container=False,
78
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
- run_button = gr.Button("Run", scale=0, variant="primary")
 
81
 
82
- result = gr.Image(label="Result", show_label=False)
 
 
 
 
 
83
 
84
- with gr.Accordion("Advanced Settings", open=False):
85
- negative_prompt = gr.Text(
86
- label="Negative prompt",
87
- max_lines=1,
88
- placeholder="Enter a negative prompt",
89
- visible=False,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
- seed = gr.Slider(
93
- label="Seed",
94
- minimum=0,
95
- maximum=MAX_SEED,
96
- step=1,
97
- value=0,
98
- )
99
 
100
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
- with gr.Row():
103
- width = gr.Slider(
104
- label="Width",
105
- minimum=256,
106
- maximum=MAX_IMAGE_SIZE,
107
- step=32,
108
- value=1024, # Replace with defaults that work for your model
109
- )
110
 
111
- height = gr.Slider(
112
- label="Height",
113
- minimum=256,
114
- maximum=MAX_IMAGE_SIZE,
115
- step=32,
116
- value=1024, # Replace with defaults that work for your model
117
- )
118
 
119
- with gr.Row():
120
- guidance_scale = gr.Slider(
121
- label="Guidance scale",
122
- minimum=0.0,
123
- maximum=10.0,
124
- step=0.1,
125
- value=0.0, # Replace with defaults that work for your model
126
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
- num_inference_steps = gr.Slider(
129
- label="Number of inference steps",
130
- minimum=1,
131
- maximum=50,
132
- step=1,
133
- value=2, # Replace with defaults that work for your model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
- gr.Examples(examples=examples, inputs=[prompt])
137
- gr.on(
138
- triggers=[run_button.click, prompt.submit],
139
- fn=infer,
140
- inputs=[
141
- prompt,
142
- negative_prompt,
143
- seed,
144
- randomize_seed,
145
- width,
146
- height,
147
- guidance_scale,
148
- num_inference_steps,
149
- ],
150
- outputs=[result, seed],
151
- )
152
 
153
  if __name__ == "__main__":
154
- demo.launch()
 
 
 
 
 
 
 
1
+ """
2
+ Marketing Image Generator with Agent Review - Complete Gradio App
 
3
 
4
+ Integrated single-file application that includes:
5
+ 1. Image Generator Agent (using Google Imagen)
6
+ 2. Image Reviewer Agent (using Google Gemini Vision)
7
+ 3. Gradio UI for Hugging Face deployment
8
 
9
+ This combines the functionality of the entire marketing image generation system
10
+ into one deployable file for Hugging Face Spaces.
11
+ """
12
 
13
+ import gradio as gr
14
+ import os
15
+ import base64
16
+ import io
17
+ import time
18
+ import re
19
+ import logging
20
+ import asyncio
21
+ from typing import Dict, Any, List, Optional
22
+ from PIL import Image
23
+ import google.generativeai as genai
24
+ from google import genai as genai_sdk
25
 
26
+ # Configure logging
27
+ logging.basicConfig(level=logging.INFO)
28
+ logger = logging.getLogger(__name__)
29
 
30
+ # Configuration
31
  MAX_IMAGE_SIZE = 1024
32
+ DEFAULT_QUALITY_THRESHOLD = 0.8
33
 
34
+ # Initialize Google API with multiple authentication methods
35
+ def setup_google_auth():
36
+ """Setup Google authentication with multiple fallback options"""
37
+
38
+ # Method 1: Try service account JSON (for Google Cloud APIs)
39
+ gcp_service_account = os.getenv("GOOGLE_SERVICE_ACCOUNT_JSON")
40
+ if gcp_service_account:
41
+ try:
42
+ import json
43
+ from google.oauth2 import service_account
44
+ import google.auth
45
+
46
+ # Parse the service account JSON
47
+ service_account_info = json.loads(gcp_service_account)
48
+ credentials = service_account.Credentials.from_service_account_info(service_account_info)
49
+
50
+ # Set up for Google Cloud APIs
51
+ os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'temp_service_account.json'
52
+ with open('temp_service_account.json', 'w') as f:
53
+ json.dump(service_account_info, f)
54
+
55
+ logger.info("Google Cloud service account configured successfully")
56
+ return "service_account"
57
+
58
+ except Exception as e:
59
+ logger.warning(f"Failed to setup service account: {e}")
60
+
61
+ # Method 2: Try API keys (for Google AI Studio)
62
+ api_keys = [
63
+ os.getenv("GOOGLE_API_KEY"),
64
+ os.getenv("GOOGLE_AI_STUDIO_API_KEY"),
65
+ os.getenv("GCP_KEY_1"),
66
+ os.getenv("GCP_KEY_2"),
67
+ os.getenv("GCP_KEY_3")
68
+ ]
69
+
70
+ google_api_key = next((key for key in api_keys if key), None)
71
+ if google_api_key:
72
+ try:
73
+ genai.configure(api_key=google_api_key)
74
+ logger.info("Google AI Studio API key configured successfully")
75
+ return google_api_key
76
+ except Exception as e:
77
+ logger.warning(f"Failed to configure API key: {e}")
78
+
79
+ logger.warning("No Google authentication found - using fallback mode")
80
+ return None
81
 
82
+ # Setup authentication
83
+ GOOGLE_AUTH = setup_google_auth()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ # ====== IMAGE GENERATOR AGENT ======
 
 
86
 
87
+ class ImageGeneratorAgent:
88
+ """Agent responsible for generating marketing images using Google Imagen"""
89
+
90
+ def __init__(self):
91
+ self.name = "image_generator_agent"
92
+
93
+ async def enhance_prompt(self, prompt: str, style: str) -> str:
94
+ """Enhance user prompt for better image generation"""
95
+ if not GOOGLE_AUTH:
96
+ # Basic enhancement without AI
97
+ style_enhancers = {
98
+ "realistic": "photorealistic, high detail, professional photography, marketing quality",
99
+ "artistic": "artistic masterpiece, creative composition, marketing appeal",
100
+ "cartoon": "cartoon style, vibrant colors, playful, marketing friendly",
101
+ "illustration": "professional illustration, clean design, marketing material",
102
+ "photographic": "professional photograph, high quality, studio lighting, marketing shot"
103
+ }
104
+ enhancer = style_enhancers.get(style, "high quality, professional")
105
+ return f"{prompt}, {enhancer}, 4K resolution, sharp focus"
106
+
107
+ try:
108
+ enhancement_prompt = f"""
109
+ You are an expert prompt engineer for AI image generation. Enhance this marketing image prompt for optimal results with Google Imagen.
110
 
111
+ Original prompt: "{prompt}"
112
+ Desired style: "{style}"
113
 
114
+ Create an enhanced version that:
115
+ 1. Maintains the core marketing intent
116
+ 2. Adds specific technical details for image quality
117
+ 3. Includes appropriate style descriptors for "{style}" style
118
+ 4. Adds professional marketing composition guidance
119
+ 5. Keeps the enhanced prompt under 150 words
120
 
121
+ Return only the enhanced prompt without explanation.
122
+ """
123
+
124
+ model = genai.GenerativeModel('gemini-2.0-flash-exp')
125
+ response = model.generate_content(enhancement_prompt)
126
+ enhanced = response.text.strip()
127
+
128
+ logger.info(f"Enhanced prompt: {enhanced[:100]}...")
129
+ return enhanced
130
+
131
+ except Exception as e:
132
+ logger.warning(f"Failed to enhance prompt with AI: {e}")
133
+ style_enhancers = {
134
+ "realistic": "photorealistic, high detail, professional photography, marketing quality",
135
+ "artistic": "artistic masterpiece, creative composition, marketing appeal",
136
+ "cartoon": "cartoon style, vibrant colors, playful, marketing friendly",
137
+ "illustration": "professional illustration, clean design, marketing material",
138
+ "photographic": "professional photograph, high quality, studio lighting"
139
+ }
140
+ enhancer = style_enhancers.get(style, "high quality, professional")
141
+ return f"{prompt}, {enhancer}, 4K resolution, sharp focus"
142
+
143
+ async def generate_image(self, prompt: str, style: str = "realistic") -> Dict[str, Any]:
144
+ """Generate image using Google Imagen"""
145
+ try:
146
+ # Enhance the prompt first
147
+ enhanced_prompt = await self.enhance_prompt(prompt, style)
148
+
149
+ # Try Google Imagen API
150
+ if GOOGLE_AUTH:
151
+ image_data = await self._generate_with_imagen(enhanced_prompt)
152
+ if image_data:
153
+ return {
154
+ "success": True,
155
+ "image_data": image_data,
156
+ "enhanced_prompt": enhanced_prompt,
157
+ "method": "Google Imagen"
158
+ }
159
+
160
+ # Fallback to placeholder for demo
161
+ return await self._generate_fallback(enhanced_prompt, style)
162
+
163
+ except Exception as e:
164
+ logger.error(f"Image generation failed: {e}")
165
+ return {
166
+ "success": False,
167
+ "error": str(e),
168
+ "enhanced_prompt": prompt
169
+ }
170
+
171
+ async def _generate_with_imagen(self, enhanced_prompt: str) -> Optional[str]:
172
+ """Generate image using Google Imagen API"""
173
+ try:
174
+ # Handle different authentication methods
175
+ if GOOGLE_AUTH == "service_account":
176
+ # Use service account authentication
177
+ client = genai_sdk.Client() # Will use GOOGLE_APPLICATION_CREDENTIALS
178
+ else:
179
+ # Use API key authentication
180
+ client = genai_sdk.Client(api_key=GOOGLE_AUTH)
181
+
182
+ result = client.models.generate_images(
183
+ model="imagen-3.0-generate-002",
184
+ prompt=enhanced_prompt,
185
+ config={
186
+ "number_of_images": 1,
187
+ "output_mime_type": "image/png"
188
+ }
189
  )
190
+
191
+ if result and hasattr(result, 'generated_images') and len(result.generated_images) > 0:
192
+ generated_image = result.generated_images[0]
193
+
194
+ if hasattr(generated_image, 'image') and hasattr(generated_image.image, 'image_bytes'):
195
+ image_bytes = generated_image.image.image_bytes
196
+ base64_image = base64.b64encode(image_bytes).decode('utf-8')
197
+ return f"data:image/png;base64,{base64_image}"
198
+
199
+ return None
200
+
201
+ except Exception as e:
202
+ logger.warning(f"Imagen API failed: {e}")
203
+ return None
204
+
205
+ async def _generate_fallback(self, enhanced_prompt: str, style: str) -> Dict[str, Any]:
206
+ """Generate fallback placeholder image"""
207
+ try:
208
+ # Create a simple colored image based on prompt
209
+ import hashlib
210
+ prompt_hash = int(hashlib.md5(enhanced_prompt.encode()).hexdigest()[:8], 16)
211
+
212
+ # Generate deterministic but varied colors
213
+ colors = [
214
+ (70, 130, 180), # Steel Blue
215
+ (60, 179, 113), # Medium Sea Green
216
+ (255, 140, 0), # Dark Orange
217
+ (106, 90, 205), # Slate Blue
218
+ (220, 20, 60), # Crimson
219
+ (255, 215, 0), # Gold
220
+ (147, 112, 219), # Medium Purple
221
+ (32, 178, 170) # Light Sea Green
222
+ ]
223
+
224
+ color = colors[prompt_hash % len(colors)]
225
+ img = Image.new('RGB', (1024, 1024), color)
226
+
227
+ # Add some simple text overlay
228
+ try:
229
+ from PIL import ImageDraw, ImageFont
230
+ draw = ImageDraw.Draw(img)
231
+
232
+ # Try to use a font, fallback to default
233
+ try:
234
+ font = ImageFont.truetype("/System/Library/Fonts/Arial.ttf", 48)
235
+ except:
236
+ font = ImageFont.load_default()
237
+
238
+ # Add text
239
+ text = f"Marketing Image\n{style.title()} Style"
240
+ draw.multiline_text((50, 450), text, fill=(255, 255, 255), font=font, align="center")
241
+
242
+ except Exception as e:
243
+ logger.warning(f"Could not add text overlay: {e}")
244
+
245
+ # Convert to base64
246
+ img_buffer = io.BytesIO()
247
+ img.save(img_buffer, format='PNG')
248
+ img_buffer.seek(0)
249
+ base64_image = base64.b64encode(img_buffer.read()).decode('utf-8')
250
+
251
+ return {
252
+ "success": True,
253
+ "image_data": f"data:image/png;base64,{base64_image}",
254
+ "enhanced_prompt": enhanced_prompt,
255
+ "method": "Fallback Demo"
256
+ }
257
+
258
+ except Exception as e:
259
+ logger.error(f"Fallback generation failed: {e}")
260
+ return {"success": False, "error": str(e)}
261
 
262
+ # ====== IMAGE REVIEWER AGENT ======
 
 
 
 
 
 
263
 
264
+ class ImageReviewerAgent:
265
+ """Agent responsible for reviewing generated images for quality and relevance"""
266
+
267
+ def __init__(self):
268
+ self.name = "image_reviewer_agent"
269
+
270
+ def parse_prompt_elements(self, prompt: str) -> Dict[str, List[str]]:
271
+ """Parse prompt to extract key elements for validation"""
272
+ prompt_lower = prompt.lower()
273
+
274
+ # Define patterns for different element types
275
+ patterns = {
276
+ "subjects": [
277
+ r'\b(person|man|woman|child|people|human|figure|team|group)\b',
278
+ r'\b(product|device|phone|laptop|car|building|office|space)\b',
279
+ r'\b(logo|brand|company|business|service)\b'
280
+ ],
281
+ "style": [
282
+ r'\b(realistic|photorealistic|photograph|photo)\b',
283
+ r'\b(artistic|painting|drawing|illustration)\b',
284
+ r'\b(modern|contemporary|minimalist|professional)\b',
285
+ r'\b(cartoon|animated|3d|rendered)\b'
286
+ ],
287
+ "colors": [
288
+ r'\b(blue|red|green|yellow|orange|purple|pink|black|white|gray|grey)\b',
289
+ r'\b(bright|dark|light|vibrant|muted|pastel|neon)\b',
290
+ r'\b(colorful|monochrome|gradient)\b'
291
+ ],
292
+ "settings": [
293
+ r'\b(office|studio|outdoor|indoor|background|scene)\b',
294
+ r'\b(professional|corporate|casual|formal)\b',
295
+ r'\b(lighting|natural light|studio lighting)\b'
296
+ ],
297
+ "marketing": [
298
+ r'\b(marketing|advertisement|promotional|campaign|brand)\b',
299
+ r'\b(professional|business|corporate|commercial)\b',
300
+ r'\b(hero|banner|social media|web|digital)\b'
301
+ ]
302
+ }
303
+
304
+ def extract_matches(patterns_list: List[str], text: str) -> List[str]:
305
+ matches = set()
306
+ for pattern in patterns_list:
307
+ found = re.findall(pattern, text)
308
+ matches.update(found)
309
+ return list(matches)
310
+
311
+ return {
312
+ key: extract_matches(pattern_list, prompt_lower)
313
+ for key, pattern_list in patterns.items()
314
+ }
315
+
316
+ async def review_image(self, image_data: str, original_prompt: str, enhanced_prompt: str) -> Dict[str, Any]:
317
+ """Review generated image for quality and relevance"""
318
+ try:
319
+ logger.info("Starting image review analysis")
320
+
321
+ # Parse prompt elements
322
+ prompt_elements = self.parse_prompt_elements(original_prompt)
323
+
324
+ # Try AI-powered review if API available
325
+ if GOOGLE_AUTH and image_data.startswith("data:image"):
326
+ ai_review = await self._ai_powered_review(image_data, original_prompt, enhanced_prompt, prompt_elements)
327
+ if ai_review:
328
+ return ai_review
329
+
330
+ # Fallback to prompt-based analysis
331
+ return await self._prompt_based_review(original_prompt, enhanced_prompt, prompt_elements)
332
+
333
+ except Exception as e:
334
+ logger.error(f"Image review failed: {e}")
335
+ return self._fallback_review(original_prompt)
336
+
337
+ async def _ai_powered_review(self, image_data: str, original_prompt: str, enhanced_prompt: str, prompt_elements: Dict) -> Optional[Dict[str, Any]]:
338
+ """Review image using Google Gemini Vision"""
339
+ try:
340
+ # Extract image from data URL
341
+ if not image_data.startswith("data:image"):
342
+ return None
343
+
344
+ image_b64 = image_data.split(',')[1]
345
+ image_bytes = base64.b64decode(image_b64)
346
+ image = Image.open(io.BytesIO(image_bytes))
347
+
348
+ # Create detailed analysis prompt
349
+ analysis_prompt = f"""
350
+ Analyze this marketing image that was generated from: "{original_prompt}"
351
+ Enhanced prompt used: "{enhanced_prompt}"
352
+
353
+ Key elements to verify:
354
+ - Subjects: {', '.join(prompt_elements.get('subjects', []))}
355
+ - Style: {', '.join(prompt_elements.get('style', []))}
356
+ - Colors: {', '.join(prompt_elements.get('colors', []))}
357
+ - Setting: {', '.join(prompt_elements.get('settings', []))}
358
+ - Marketing elements: {', '.join(prompt_elements.get('marketing', []))}
359
+
360
+ Rate the image on:
361
+ 1. Technical Quality (0.0-1.0): clarity, composition, lighting, resolution
362
+ 2. Prompt Relevance (0.0-1.0): how well it matches the original request
363
+ 3. Marketing Effectiveness (0.0-1.0): professional appeal, brand suitability
364
+
365
+ Provide response in this format:
366
+ QUALITY_SCORE: [0.0-1.0]
367
+ RELEVANCE_SCORE: [0.0-1.0]
368
+ MARKETING_SCORE: [0.0-1.0]
369
+
370
+ STRENGTHS: [List 2-3 strong points]
371
+ ISSUES: [List 2-3 improvement areas]
372
+ RECOMMENDATIONS: [List 2-3 specific suggestions]
373
+
374
+ OVERALL_ASSESSMENT: [Brief summary of the image's marketing potential]
375
+ """
376
+
377
+ model = genai.GenerativeModel('gemini-2.0-flash-exp')
378
+ response = model.generate_content([analysis_prompt, image])
379
+ analysis_text = response.text
380
+
381
+ return self._parse_ai_review(analysis_text, original_prompt)
382
+
383
+ except Exception as e:
384
+ logger.warning(f"AI-powered review failed: {e}")
385
+ return None
386
+
387
+ def _parse_ai_review(self, analysis_text: str, original_prompt: str) -> Dict[str, Any]:
388
+ """Parse AI review response into structured feedback"""
389
+
390
+ def extract_score(text: str, score_type: str) -> float:
391
+ pattern = rf"{score_type}_SCORE:\s*([\d.]+)"
392
+ match = re.search(pattern, text, re.IGNORECASE)
393
+ if match:
394
+ try:
395
+ return min(1.0, max(0.0, float(match.group(1))))
396
+ except ValueError:
397
+ pass
398
+ return 0.7
399
+
400
+ def extract_list_section(text: str, section: str) -> List[str]:
401
+ pattern = rf"{section}:\s*(.+?)(?=\n[A-Z_]+:|$)"
402
+ match = re.search(pattern, text, re.IGNORECASE | re.DOTALL)
403
+ if match:
404
+ items_text = match.group(1).strip()
405
+ items = [item.strip().strip('-β€’*').strip()
406
+ for item in re.split(r'\n|,', items_text)
407
+ if item.strip() and len(item.strip()) > 3]
408
+ return items[:3] # Limit to 3 items
409
+ return []
410
+
411
+ try:
412
+ # Extract scores
413
+ quality_score = extract_score(analysis_text, "QUALITY")
414
+ relevance_score = extract_score(analysis_text, "RELEVANCE")
415
+ marketing_score = extract_score(analysis_text, "MARKETING")
416
+
417
+ # Extract feedback sections
418
+ strengths = extract_list_section(analysis_text, "STRENGTHS")
419
+ issues = extract_list_section(analysis_text, "ISSUES")
420
+ recommendations = extract_list_section(analysis_text, "RECOMMENDATIONS")
421
+
422
+ # Extract overall assessment
423
+ assessment_match = re.search(r"OVERALL_ASSESSMENT:\s*(.+?)(?=\n[A-Z_]+:|$)",
424
+ analysis_text, re.IGNORECASE | re.DOTALL)
425
+ overall_assessment = assessment_match.group(1).strip() if assessment_match else "Good marketing image potential"
426
+
427
+ # Calculate weighted overall score (emphasize marketing effectiveness)
428
+ overall_score = (quality_score * 0.3 + relevance_score * 0.4 + marketing_score * 0.3)
429
+
430
+ # Determine pass/fail
431
+ passed = overall_score >= 0.7 and relevance_score >= 0.6
432
+
433
+ return {
434
+ "success": True,
435
+ "overall_score": round(overall_score, 2),
436
+ "quality_score": round(quality_score, 2),
437
+ "relevance_score": round(relevance_score, 2),
438
+ "marketing_score": round(marketing_score, 2),
439
+ "passed": passed,
440
+ "strengths": strengths,
441
+ "issues": issues,
442
+ "recommendations": recommendations,
443
+ "overall_assessment": overall_assessment,
444
+ "review_method": "AI-Powered (Gemini Vision)"
445
+ }
446
+
447
+ except Exception as e:
448
+ logger.error(f"Error parsing AI review: {e}")
449
+ return self._fallback_review(original_prompt)
450
+
451
+ async def _prompt_based_review(self, original_prompt: str, enhanced_prompt: str, prompt_elements: Dict) -> Dict[str, Any]:
452
+ """Review based on prompt analysis when AI review isn't available"""
453
+
454
+ issues = []
455
+ recommendations = []
456
+ strengths = []
457
+
458
+ # Analyze prompt completeness
459
+ total_elements = sum(len(elements) for elements in prompt_elements.values())
460
+
461
+ # Base scoring
462
+ if total_elements >= 8:
463
+ base_score = 0.8
464
+ strengths.append("Comprehensive prompt with detailed specifications")
465
+ elif total_elements >= 5:
466
+ base_score = 0.7
467
+ strengths.append("Good prompt detail level")
468
+ elif total_elements >= 3:
469
+ base_score = 0.6
470
+ issues.append("Prompt could use more specific details")
471
+ else:
472
+ base_score = 0.5
473
+ issues.append("Prompt lacks sufficient detail for optimal results")
474
+ recommendations.append("Add more specific details about subjects, style, and setting")
475
+
476
+ # Check for marketing-specific elements
477
+ marketing_elements = prompt_elements.get('marketing', [])
478
+ if marketing_elements:
479
+ base_score += 0.1
480
+ strengths.append("Contains marketing-focused language")
481
+ else:
482
+ recommendations.append("Consider adding marketing-specific context")
483
+
484
+ # Check for style specification
485
+ style_elements = prompt_elements.get('style', [])
486
+ if style_elements:
487
+ strengths.append(f"Clear style direction: {', '.join(style_elements[:2])}")
488
+ else:
489
+ issues.append("No clear artistic style specified")
490
+ recommendations.append("Specify desired artistic style (realistic, artistic, etc.)")
491
+
492
+ # Check for subject clarity
493
+ subject_elements = prompt_elements.get('subjects', [])
494
+ if subject_elements:
495
+ strengths.append(f"Clear subjects identified: {', '.join(subject_elements[:2])}")
496
+ else:
497
+ issues.append("Main subjects not clearly specified")
498
+ recommendations.append("Clearly define what should be the main focus")
499
+
500
+ # Calculate scores
501
+ quality_score = min(1.0, base_score + 0.1) # Slight boost for quality
502
+ relevance_score = base_score # Based on prompt completeness
503
+ marketing_score = base_score + (0.1 if marketing_elements else -0.1)
504
+
505
+ overall_score = (quality_score * 0.3 + relevance_score * 0.4 + marketing_score * 0.3)
506
+ passed = overall_score >= 0.6
507
+
508
+ return {
509
+ "success": True,
510
+ "overall_score": round(overall_score, 2),
511
+ "quality_score": round(quality_score, 2),
512
+ "relevance_score": round(relevance_score, 2),
513
+ "marketing_score": round(marketing_score, 2),
514
+ "passed": passed,
515
+ "strengths": strengths[:3],
516
+ "issues": issues[:3],
517
+ "recommendations": recommendations[:3],
518
+ "overall_assessment": f"Prompt-based analysis shows {'good' if passed else 'moderate'} marketing image potential",
519
+ "review_method": "Prompt Analysis"
520
+ }
521
+
522
+ def _fallback_review(self, original_prompt: str) -> Dict[str, Any]:
523
+ """Fallback review when all else fails"""
524
+ word_count = len(original_prompt.split())
525
+ base_score = min(0.8, max(0.4, 0.4 + (word_count * 0.02)))
526
+
527
+ return {
528
+ "success": True,
529
+ "overall_score": base_score,
530
+ "quality_score": base_score,
531
+ "relevance_score": base_score,
532
+ "marketing_score": base_score,
533
+ "passed": base_score >= 0.6,
534
+ "strengths": ["Prompt provided for image generation"],
535
+ "issues": ["Unable to perform detailed analysis"],
536
+ "recommendations": ["Consider regenerating with more detailed prompt"],
537
+ "overall_assessment": "Basic review completed",
538
+ "review_method": "Fallback"
539
+ }
540
 
541
+ # ====== MAIN APPLICATION WORKFLOW ======
 
 
 
 
 
 
 
542
 
543
+ # Initialize agents
544
+ generator_agent = ImageGeneratorAgent()
545
+ reviewer_agent = ImageReviewerAgent()
 
 
 
 
546
 
547
+ def generate_marketing_image_with_review(
548
+ prompt: str,
549
+ style: str = "realistic",
550
+ quality_threshold: float = 0.8,
551
+ max_iterations: int = 2,
552
+ progress=gr.Progress(track_tqdm=True)
553
+ ):
554
+ """
555
+ Main workflow: Generate image and review it
556
+ """
557
+
558
+ if not prompt.strip():
559
+ return None, "Please enter a prompt to generate an image.", "❌ No Prompt", ""
560
+
561
+ try:
562
+ progress(0.1, desc="Initializing generation workflow...")
563
+
564
+ # Step 1: Generate image
565
+ progress(0.3, desc="Generating marketing image...")
566
+ generation_result = asyncio.run(generator_agent.generate_image(prompt, style))
567
+
568
+ if not generation_result.get("success"):
569
+ error_msg = f"Image generation failed: {generation_result.get('error', 'Unknown error')}"
570
+ return None, error_msg, "❌ Generation Failed", ""
571
+
572
+ image_data = generation_result["image_data"]
573
+ enhanced_prompt = generation_result["enhanced_prompt"]
574
+
575
+ # Convert base64 to PIL Image for display
576
+ if image_data.startswith("data:image"):
577
+ image_b64 = image_data.split(',')[1]
578
+ image_bytes = base64.b64decode(image_b64)
579
+ display_image = Image.open(io.BytesIO(image_bytes))
580
+ else:
581
+ display_image = None
582
+
583
+ progress(0.6, desc="Reviewing image quality...")
584
+
585
+ # Step 2: Review the generated image
586
+ review_result = asyncio.run(reviewer_agent.review_image(image_data, prompt, enhanced_prompt))
587
+
588
+ progress(0.9, desc="Finalizing results...")
589
+
590
+ # Step 3: Format results
591
+ if review_result.get("success"):
592
+ # Build quality information display
593
+ quality_info = f"""
594
+ ## 🎯 Review Results
595
+
596
+ **Overall Score:** {review_result['overall_score']:.2f}/1.0
597
+ **Status:** {'βœ… Approved' if review_result['passed'] else '⚠️ Needs Improvement'}
598
 
599
+ ### Detailed Scores
600
+ - **Quality:** {review_result['quality_score']:.2f}/1.0
601
+ - **Relevance:** {review_result['relevance_score']:.2f}/1.0
602
+ - **Marketing Appeal:** {review_result['marketing_score']:.2f}/1.0
603
+
604
+ ### πŸ’ͺ Strengths
605
+ {chr(10).join(f"β€’ {strength}" for strength in review_result.get('strengths', []))}
606
+
607
+ ### ⚠️ Areas for Improvement
608
+ {chr(10).join(f"β€’ {issue}" for issue in review_result.get('issues', []))}
609
+
610
+ ### πŸ’‘ Recommendations
611
+ {chr(10).join(f"β€’ {rec}" for rec in review_result.get('recommendations', []))}
612
+
613
+ ### πŸ“ Assessment
614
+ {review_result.get('overall_assessment', 'Review completed')}
615
+
616
+ ---
617
+ *Review Method: {review_result.get('review_method', 'Standard')}*
618
+ *Enhanced Prompt: {enhanced_prompt[:100]}...*
619
+ """
620
+
621
+ review_status = "βœ… Approved" if review_result['passed'] else "⚠️ Needs Review"
622
+
623
+ # Add generation method info
624
+ debug_info = f"""
625
+ **Generation Details:**
626
+ - Method: {generation_result.get('method', 'Unknown')}
627
+ - Original Prompt: {prompt}
628
+ - Enhanced Prompt: {enhanced_prompt}
629
+ - Style: {style}
630
+ - API Status: {'βœ… Connected' if GOOGLE_AUTH else '⚠️ Demo Mode'}
631
+ """
632
+
633
+ else:
634
+ quality_info = f"Review failed: {review_result.get('error', 'Unknown error')}"
635
+ review_status = "❌ Review Failed"
636
+ debug_info = f"Generation Method: {generation_result.get('method', 'Unknown')}"
637
+
638
+ progress(1.0, desc="Complete!")
639
+
640
+ return display_image, quality_info, review_status, debug_info
641
+
642
+ except Exception as e:
643
+ logger.error(f"Workflow error: {str(e)}")
644
+ error_msg = f"Workflow failed: {str(e)}"
645
+ return None, error_msg, "❌ Error", f"Error details: {str(e)}"
646
+
647
+ # ====== GRADIO INTERFACE ======
648
+
649
+ def create_gradio_interface():
650
+ """Create the complete Gradio interface"""
651
+
652
+ custom_css = """
653
+ .gradio-container {
654
+ max-width: 1400px !important;
655
+ margin: auto !important;
656
+ }
657
+ .header-text {
658
+ text-align: center;
659
+ color: #1f77b4;
660
+ margin-bottom: 2rem;
661
+ }
662
+ .quality-info {
663
+ background-color: #f8f9fa;
664
+ padding: 1rem;
665
+ border-radius: 0.5rem;
666
+ border-left: 4px solid #1f77b4;
667
+ font-family: monospace;
668
+ }
669
+ .status-approved { color: #28a745; font-weight: bold; }
670
+ .status-warning { color: #ffc107; font-weight: bold; }
671
+ .status-error { color: #dc3545; font-weight: bold; }
672
+ """
673
+
674
+ with gr.Blocks(css=custom_css, title="Marketing Image Generator with AI Review") as interface:
675
+
676
+ # Header
677
+ gr.Markdown("""
678
+ # 🎨 Marketing Image Generator with AI Review
679
+ ### Professional marketing images with automated quality assurance
680
+
681
+ This system combines **Google Imagen** for image generation with **Google Gemini Vision** for intelligent quality review.
682
+ Perfect for creating professional marketing materials with AI-powered feedback.
683
+ """, elem_classes=["header-text"])
684
+
685
+ # API Status indicator
686
+ api_status = "🟒 Google AI Connected" if GOOGLE_AUTH else "🟑 Demo Mode (No API Key)"
687
+ gr.Markdown(f"**Status:** {api_status}")
688
+
689
+ with gr.Row():
690
+ with gr.Column(scale=2):
691
+ # Input Section
692
+ gr.Markdown("## πŸ“ Describe Your Marketing Image")
693
+
694
+ prompt = gr.Textbox(
695
+ label="Marketing Image Description",
696
+ placeholder="e.g., A professional team of diverse colleagues collaborating in a modern office space with natural lighting, for a corporate website hero image",
697
+ lines=4,
698
+ info="Be specific about subjects, setting, style, and intended marketing use"
699
+ )
700
+
701
+ with gr.Row():
702
+ style = gr.Dropdown(
703
+ choices=["realistic", "artistic", "cartoon", "illustration", "photographic"],
704
+ value="realistic",
705
+ label="Art Style",
706
+ info="Choose the visual style that fits your brand"
707
+ )
708
+
709
+ quality_threshold = gr.Slider(
710
+ minimum=0.0,
711
+ maximum=1.0,
712
+ value=0.7,
713
+ step=0.1,
714
+ label="Quality Threshold",
715
+ info="Minimum score for approval (0.0 = lenient, 1.0 = strict)"
716
+ )
717
+
718
+ with gr.Accordion("πŸ”§ Advanced Options", open=False):
719
+ max_iterations = gr.Slider(
720
+ minimum=1,
721
+ maximum=3,
722
+ value=2,
723
+ step=1,
724
+ label="Max Review Iterations",
725
+ info="Maximum attempts to improve the image"
726
+ )
727
+
728
+ generate_btn = gr.Button(
729
+ "πŸš€ Generate & Review Marketing Image",
730
+ variant="primary",
731
+ size="lg"
732
  )
733
+
734
+ with gr.Column(scale=3):
735
+ # Output Section
736
+ gr.Markdown("## πŸ–ΌοΈ Generated Image & Analysis")
737
+
738
+ with gr.Row():
739
+ with gr.Column(scale=2):
740
+ generated_image = gr.Image(
741
+ label="Your Marketing Image",
742
+ type="pil",
743
+ interactive=False,
744
+ height=400
745
+ )
746
+
747
+ with gr.Column(scale=1):
748
+ review_status = gr.Textbox(
749
+ label="Review Status",
750
+ value="⏳ Ready to Generate",
751
+ interactive=False,
752
+ max_lines=1
753
+ )
754
+
755
+ quality_info = gr.Markdown(
756
+ label="AI Quality Analysis",
757
+ value="*Generate an image to see detailed AI quality analysis and recommendations*",
758
+ elem_classes=["quality-info"]
759
+ )
760
+
761
+ # Debug/Technical Info (Collapsible)
762
+ with gr.Accordion("πŸ”§ Technical Details", open=False):
763
+ debug_info = gr.Markdown(
764
+ value="*Technical information will appear here after generation*"
765
+ )
766
+
767
+ # Examples Section
768
+ gr.Markdown("## πŸ’‘ Example Marketing Prompts")
769
+
770
+ examples = gr.Examples(
771
+ examples=[
772
+ ["A diverse team of professionals collaborating around a modern conference table in a bright office space, corporate website hero image", "realistic"],
773
+ ["A sleek product showcase featuring a smartphone on a clean white background with dramatic lighting, for e-commerce", "photographic"],
774
+ ["A friendly customer service representative wearing a headset, smiling while helping clients in a contemporary office", "realistic"],
775
+ ["A minimalist workspace setup with laptop, coffee, and plants, perfect for productivity app marketing", "artistic"],
776
+ ["An abstract representation of data flow and connectivity, modern tech company branding", "illustration"],
777
+ ["A celebration scene with confetti and happy people, perfect for achievement or success marketing", "realistic"]
778
+ ],
779
+ inputs=[prompt, style],
780
+ label="Click any example to try it out!"
781
+ )
782
+
783
+ # Connect the workflow
784
+ generate_btn.click(
785
+ fn=generate_marketing_image_with_review,
786
+ inputs=[prompt, style, quality_threshold, max_iterations],
787
+ outputs=[generated_image, quality_info, review_status, debug_info],
788
+ show_progress=True
789
+ )
790
+
791
+ # Footer
792
+ gr.Markdown("""
793
+ ---
794
+ <div style='text-align: center; color: #666; font-size: 0.9rem;'>
795
+ <p>🎨 <strong>Marketing Image Generator with AI Review</strong></p>
796
+ <p>Powered by Google Imagen & Gemini Vision | Built for Professional Marketing Teams</p>
797
+ <p><em>Generate β†’ Review β†’ Perfect: Your AI-powered creative workflow</em></p>
798
+ </div>
799
+ """)
800
+
801
+ return interface
802
 
803
+ # ====== APPLICATION ENTRY POINT ======
804
+
805
+ # Create the interface
806
+ demo = create_gradio_interface()
 
 
 
 
 
 
 
 
 
 
 
 
807
 
808
  if __name__ == "__main__":
809
+ logger.info("Starting Marketing Image Generator with AI Review")
810
+ demo.launch(
811
+ server_name="0.0.0.0",
812
+ server_port=7860,
813
+ share=False,
814
+ show_error=True,
815
+ )