sushilideaclan01 commited on
Commit
9474b94
·
1 Parent(s): 533051b

Refactor ad generation process to improve progress tracking and niche-specific guidance

Browse files

- Enhanced the GeneratePage component to provide detailed progress updates during ad generation, including estimated times for each step.
- Updated the generator service to process strategies in parallel and introduced niche-specific visual guidance for image generation.
- Improved the creative_designer method to incorporate niche requirements, ensuring generated images align with specific categories.
- Added error handling for progress intervals to maintain stability during ad generation.

frontend/app/generate/page.tsx CHANGED
@@ -242,42 +242,131 @@ export default function GeneratePage() {
242
  reset();
243
  setIsGenerating(true);
244
  setGenerationStartTime(Date.now());
245
- setProgress({
246
- step: "copy",
247
- progress: 10,
248
- message: "Researching psychology triggers, angles, and concepts...",
249
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
  try {
252
- // Simulate progress updates for extensive
253
- let currentProgress = 20;
254
- const progressSteps = [
255
- { step: "copy" as const, progress: 20, message: "Researching psychology triggers..." },
256
- { step: "copy" as const, progress: 35, message: "Retrieving marketing knowledge..." },
257
- { step: "copy" as const, progress: 50, message: "Creating creative strategies..." },
258
- { step: "copy" as const, progress: 70, message: "Generating image prompts and copy..." },
259
- { step: "image" as const, progress: 85, message: "Generating images..." },
260
- ];
261
- let stepIndex = 0;
262
 
263
- const progressInterval = setInterval(() => {
264
- if (stepIndex < progressSteps.length) {
265
- setProgress(progressSteps[stepIndex]);
266
- stepIndex++;
267
- } else {
268
- currentProgress = Math.min(95, currentProgress + 2);
269
- setProgress({
270
- step: "image",
271
- progress: currentProgress,
272
- message: "Generating images...",
273
- });
274
- }
275
- }, 2000);
276
 
277
  // Generate ad using extensive
278
  const result = await generateExtensiveAd(data);
279
 
280
- clearInterval(progressInterval);
 
 
 
 
281
 
282
  // Verify all images are included
283
  if (result.images && result.images.length > 0) {
@@ -292,6 +381,9 @@ export default function GeneratePage() {
292
  message: "Saving to database...",
293
  });
294
 
 
 
 
295
  setCurrentGeneration(result);
296
  setProgress({
297
  step: "complete",
@@ -301,6 +393,12 @@ export default function GeneratePage() {
301
 
302
  toast.success("Ad generated successfully using Extensive!");
303
  } catch (error: any) {
 
 
 
 
 
 
304
  setError(error.message || "Failed to generate ad");
305
  setProgress({
306
  step: "error",
 
242
  reset();
243
  setIsGenerating(true);
244
  setGenerationStartTime(Date.now());
245
+
246
+ // Calculate estimated time based on strategies and images
247
+ // Step 1 (Researcher): ~10-15 seconds
248
+ // Step 2 (Retrieve knowledge): ~15-20 seconds (parallel)
249
+ // Step 3 (Creative Director): ~20-30 seconds
250
+ // Step 4 (Process strategies): ~10-15 seconds per strategy (parallel)
251
+ // Step 5 (Generate images): ~15-30 seconds per image
252
+ const estimatedTimePerStrategy = 10; // seconds for processing strategy
253
+ const estimatedTimePerImage = 20; // seconds per image
254
+ const totalEstimatedTime =
255
+ 15 + // Step 1: Researcher
256
+ 20 + // Step 2: Retrieve knowledge
257
+ 25 + // Step 3: Creative Director
258
+ (data.num_strategies * estimatedTimePerStrategy) + // Step 4: Process strategies
259
+ (data.num_strategies * data.num_images * estimatedTimePerImage); // Step 5: Generate images
260
+
261
+ let elapsedTime = 0;
262
+ let progressInterval: NodeJS.Timeout | null = null;
263
+
264
+ // Progress steps aligned with actual backend steps
265
+ const progressSteps = [
266
+ {
267
+ step: "copy" as const,
268
+ progress: 5,
269
+ message: "🔍 Step 1: Researching psychology triggers, angles, and concepts...",
270
+ duration: 15 // seconds
271
+ },
272
+ {
273
+ step: "copy" as const,
274
+ progress: 15,
275
+ message: "📚 Step 2: Retrieving marketing knowledge...",
276
+ duration: 20 // seconds
277
+ },
278
+ {
279
+ step: "copy" as const,
280
+ progress: 30,
281
+ message: `🎨 Step 3: Creating ${data.num_strategies} creative strategy/strategies...`,
282
+ duration: 25 // seconds
283
+ },
284
+ {
285
+ step: "copy" as const,
286
+ progress: 50,
287
+ message: `⚡ Step 4: Processing ${data.num_strategies} strategies in parallel...`,
288
+ duration: data.num_strategies * estimatedTimePerStrategy // seconds
289
+ },
290
+ {
291
+ step: "image" as const,
292
+ progress: 70,
293
+ message: `🖼️ Step 5: Generating ${data.num_images} image(s) per strategy...`,
294
+ duration: data.num_strategies * data.num_images * estimatedTimePerImage // seconds
295
+ },
296
+ ];
297
+
298
+ let currentStepIndex = 0;
299
+ let stepStartTime = 0;
300
+ const progressUpdateInterval = 500; // Update every 500ms
301
+
302
+ const updateProgress = () => {
303
+ elapsedTime += progressUpdateInterval / 1000; // Convert to seconds
304
+
305
+ // Determine which step we should be on based on elapsed time
306
+ let cumulativeTime = 0;
307
+ let targetStepIndex = 0;
308
+ let stepStartCumulativeTime = 0;
309
+
310
+ for (let i = 0; i < progressSteps.length; i++) {
311
+ const stepStart = cumulativeTime;
312
+ cumulativeTime += progressSteps[i].duration;
313
+ if (elapsedTime <= cumulativeTime) {
314
+ targetStepIndex = i;
315
+ stepStartCumulativeTime = stepStart;
316
+ break;
317
+ }
318
+ targetStepIndex = i;
319
+ stepStartCumulativeTime = stepStart;
320
+ }
321
+
322
+ // Update to the correct step if we've moved forward
323
+ if (targetStepIndex > currentStepIndex) {
324
+ currentStepIndex = targetStepIndex;
325
+ stepStartTime = stepStartCumulativeTime;
326
+ }
327
+
328
+ const currentStep = progressSteps[currentStepIndex];
329
+ const stepElapsed = elapsedTime - stepStartTime;
330
+ const stepProgress = Math.min(1, Math.max(0, stepElapsed / currentStep.duration));
331
+
332
+ // Calculate overall progress
333
+ let overallProgress = currentStep.progress;
334
+ if (currentStepIndex < progressSteps.length - 1) {
335
+ // Within current step, interpolate to next step's progress
336
+ const nextStep = progressSteps[currentStepIndex + 1];
337
+ const progressRange = nextStep.progress - currentStep.progress;
338
+ overallProgress = currentStep.progress + (stepProgress * progressRange);
339
+ } else {
340
+ // Last step, interpolate from current step progress to 90%
341
+ overallProgress = currentStep.progress + (stepProgress * (90 - currentStep.progress));
342
+ }
343
+
344
+ setProgress({
345
+ step: currentStep.step,
346
+ progress: Math.min(90, overallProgress), // Cap at 90% until completion
347
+ message: currentStep.message,
348
+ });
349
+ };
350
 
351
  try {
352
+ // Start progress updates
353
+ progressInterval = setInterval(updateProgress, progressUpdateInterval);
 
 
 
 
 
 
 
 
354
 
355
+ // Set initial progress
356
+ setProgress({
357
+ step: progressSteps[0].step,
358
+ progress: progressSteps[0].progress,
359
+ message: progressSteps[0].message,
360
+ });
 
 
 
 
 
 
 
361
 
362
  // Generate ad using extensive
363
  const result = await generateExtensiveAd(data);
364
 
365
+ // Clear progress interval
366
+ if (progressInterval) {
367
+ clearInterval(progressInterval);
368
+ progressInterval = null;
369
+ }
370
 
371
  // Verify all images are included
372
  if (result.images && result.images.length > 0) {
 
381
  message: "Saving to database...",
382
  });
383
 
384
+ // Small delay to show saving step
385
+ await new Promise(resolve => setTimeout(resolve, 500));
386
+
387
  setCurrentGeneration(result);
388
  setProgress({
389
  step: "complete",
 
393
 
394
  toast.success("Ad generated successfully using Extensive!");
395
  } catch (error: any) {
396
+ // Clear progress interval on error
397
+ if (progressInterval) {
398
+ clearInterval(progressInterval);
399
+ progressInterval = null;
400
+ }
401
+
402
  setError(error.message || "Failed to generate ad");
403
  setProgress({
404
  step: "error",
services/generator.py CHANGED
@@ -1967,10 +1967,17 @@ CONCEPT: {concept['name']}
1967
  # Step 4: Process strategies in parallel (designer + copywriter)
1968
  print(f"⚡ Step 4: Processing {len(creative_strategies)} strategies in parallel...")
1969
  from concurrent.futures import ThreadPoolExecutor as TPE
 
 
 
 
 
 
 
1970
 
1971
  with TPE(max_workers=8) as executor:
1972
  strategy_results = list(executor.map(
1973
- third_flow_service.process_strategy,
1974
  creative_strategies
1975
  ))
1976
 
 
1967
  # Step 4: Process strategies in parallel (designer + copywriter)
1968
  print(f"⚡ Step 4: Processing {len(creative_strategies)} strategies in parallel...")
1969
  from concurrent.futures import ThreadPoolExecutor as TPE
1970
+ from functools import partial
1971
+
1972
+ # Create a partial function that includes the niche parameter
1973
+ process_strategy_with_niche = partial(
1974
+ third_flow_service.process_strategy,
1975
+ niche=niche_display
1976
+ )
1977
 
1978
  with TPE(max_workers=8) as executor:
1979
  strategy_results = list(executor.map(
1980
+ process_strategy_with_niche,
1981
  creative_strategies
1982
  ))
1983
 
services/third_flow.py CHANGED
@@ -447,16 +447,57 @@ class ThirdFlowService:
447
  print(f"Error in creative_director: {e}")
448
  return []
449
 
450
- def creative_designer(self, creative_strategy: CreativeStrategies) -> str:
451
  """
452
  Generate image prompt from creative strategy.
453
 
454
  Args:
455
  creative_strategy: Creative strategy object
 
456
 
457
  Returns:
458
  Image generation prompt
459
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  strategy_str = f"""Psychology Trigger: {creative_strategy.phsychologyTrigger}
461
  Angle: {creative_strategy.angle}
462
  Concept: {creative_strategy.concept}
@@ -471,13 +512,15 @@ class ThirdFlowService:
471
  "content": [
472
  {
473
  "type": "text",
474
- "text": """You are the Creative Designer for the affiliate marketing company which makes the prompt from creative strategy given for the ad images in the affiliate marketing.
475
  Nano Banana image model will be used to generate the images
476
  Affiliate marketing is a performance-based model where you promote someone else's product or service and earn a commission for each qualified action (click, lead, or sale).
477
  In affiliate marketing 'Low-production, realistic images often outperform studio creatives' runs most.
478
 
479
  For nano banana image model here's structure for the prompt: [The Hook] + [The Subject] + [The Context/Setting] + [The Technical Polish]
480
 
 
 
481
  CRITICAL: If the image includes people or faces, ensure they look like real, original people with:
482
  - Photorealistic faces with natural skin texture, visible pores, and realistic skin imperfections
483
  - Natural facial asymmetry (no perfectly symmetrical faces)
@@ -498,7 +541,7 @@ class ThirdFlowService:
498
  "type": "text",
499
  "text": f"""Following is the creative strategy:
500
  {strategy_str}
501
- Provide the image prompt for the given creative strategy."""
502
  }
503
  ]
504
  }
@@ -611,18 +654,20 @@ class ThirdFlowService:
611
 
612
  def process_strategy(
613
  self,
614
- creative_strategy: CreativeStrategies
 
615
  ) -> tuple[str, str, str, str]:
616
  """
617
  Process a single creative strategy to generate prompt and copy.
618
 
619
  Args:
620
  creative_strategy: Creative strategy object
 
621
 
622
  Returns:
623
  Tuple of (prompt, title, body, description)
624
  """
625
- prompt = self.creative_designer(creative_strategy)
626
  ad_copy = self.copy_writer(creative_strategy)
627
  return (
628
  prompt,
 
447
  print(f"Error in creative_director: {e}")
448
  return []
449
 
450
+ def creative_designer(self, creative_strategy: CreativeStrategies, niche: str = "Home Insurance") -> str:
451
  """
452
  Generate image prompt from creative strategy.
453
 
454
  Args:
455
  creative_strategy: Creative strategy object
456
+ niche: Niche category (Home Insurance or GLP-1)
457
 
458
  Returns:
459
  Image generation prompt
460
  """
461
+ # Import niche visual guidance
462
+ from data.visuals import get_niche_visual_guidance
463
+
464
+ # Get niche-specific visual guidance
465
+ niche_lower = niche.lower().replace(" ", "_").replace("-", "_")
466
+ niche_guidance_data = get_niche_visual_guidance(niche_lower)
467
+
468
+ # Build niche-specific guidance text
469
+ if niche_guidance_data:
470
+ niche_guidance = f"""
471
+ NICHE-SPECIFIC REQUIREMENTS ({niche}):
472
+ SUBJECTS TO INCLUDE: {', '.join(niche_guidance_data.get('subjects', []))}
473
+ PROPS TO INCLUDE: {', '.join(niche_guidance_data.get('props', []))}
474
+ AVOID: {', '.join(niche_guidance_data.get('avoid', []))}
475
+ COLOR PREFERENCE: {niche_guidance_data.get('color_preference', 'balanced')}
476
+
477
+ CRITICAL: The image MUST be appropriate for {niche} niche. Ensure all visual elements match this niche category."""
478
+ elif niche_lower == "home_insurance":
479
+ niche_guidance = """
480
+ NICHE-SPECIFIC REQUIREMENTS (Home Insurance):
481
+ SUBJECTS TO INCLUDE: family in front of home, house exterior, homeowner looking confident, couple reviewing papers
482
+ PROPS TO INCLUDE: insurance documents, house keys, tablet showing coverage, family photos
483
+ AVOID: disasters, fire or floods, stressed expressions, dark settings
484
+ COLOR PREFERENCE: trust
485
+
486
+ CRITICAL: The image MUST show home insurance-related content. Show REAL American suburban homes, homeowners, and insurance-related elements."""
487
+ elif niche_lower == "glp1":
488
+ niche_guidance = """
489
+ NICHE-SPECIFIC REQUIREMENTS (GLP-1):
490
+ SUBJECTS TO INCLUDE: confident person smiling, active lifestyle scenes, healthy meal preparation, doctor consultation
491
+ PROPS TO INCLUDE: fitness equipment, healthy food, comfortable clothing
492
+ AVOID: before/after weight comparisons, measuring tapes, scales prominently, needle close-ups
493
+ COLOR PREFERENCE: health
494
+
495
+ CRITICAL: The image MUST be appropriate for GLP-1/weight loss niche. Show lifestyle, health, and confidence-related content, NOT home insurance content."""
496
+ else:
497
+ niche_guidance = f"""
498
+ NICHE-SPECIFIC REQUIREMENTS ({niche}):
499
+ CRITICAL: The image MUST be appropriate for {niche} niche."""
500
+
501
  strategy_str = f"""Psychology Trigger: {creative_strategy.phsychologyTrigger}
502
  Angle: {creative_strategy.angle}
503
  Concept: {creative_strategy.concept}
 
512
  "content": [
513
  {
514
  "type": "text",
515
+ "text": f"""You are the Creative Designer for the affiliate marketing company which makes the prompt from creative strategy given for the ad images in the affiliate marketing.
516
  Nano Banana image model will be used to generate the images
517
  Affiliate marketing is a performance-based model where you promote someone else's product or service and earn a commission for each qualified action (click, lead, or sale).
518
  In affiliate marketing 'Low-production, realistic images often outperform studio creatives' runs most.
519
 
520
  For nano banana image model here's structure for the prompt: [The Hook] + [The Subject] + [The Context/Setting] + [The Technical Polish]
521
 
522
+ {niche_guidance}
523
+
524
  CRITICAL: If the image includes people or faces, ensure they look like real, original people with:
525
  - Photorealistic faces with natural skin texture, visible pores, and realistic skin imperfections
526
  - Natural facial asymmetry (no perfectly symmetrical faces)
 
541
  "type": "text",
542
  "text": f"""Following is the creative strategy:
543
  {strategy_str}
544
+ Provide the image prompt for the given creative strategy. Make sure the prompt follows the NICHE-SPECIFIC REQUIREMENTS above."""
545
  }
546
  ]
547
  }
 
654
 
655
  def process_strategy(
656
  self,
657
+ creative_strategy: CreativeStrategies,
658
+ niche: str = "Home Insurance"
659
  ) -> tuple[str, str, str, str]:
660
  """
661
  Process a single creative strategy to generate prompt and copy.
662
 
663
  Args:
664
  creative_strategy: Creative strategy object
665
+ niche: Niche category (Home Insurance or GLP-1)
666
 
667
  Returns:
668
  Tuple of (prompt, title, body, description)
669
  """
670
+ prompt = self.creative_designer(creative_strategy, niche=niche)
671
  ad_copy = self.copy_writer(creative_strategy)
672
  return (
673
  prompt,