sushilideaclan01 commited on
Commit
254d33c
·
1 Parent(s): e5a550b

Refactor concepts and update header branding

Browse files

- Removed the "Review Stars Visual" entry from the concepts in data/concepts.py to streamline visual options.
- Updated the header component in Header.tsx to change the displayed text from "Creative Breakthrough" to "PsyAdGenesis" for consistent branding across the application.
- Enhanced the generator service in generator.py with improved import organization and optimized data retrieval methods for better performance.

data/concepts.py CHANGED
@@ -116,7 +116,6 @@ CONCEPTS = {
116
  "name": "Social Proof",
117
  "concepts": [
118
  {"key": "testimonial_screenshot", "name": "Testimonial Screenshot", "structure": "Screenshot format", "visual": "Authentic testimonial"},
119
- {"key": "review_stars", "name": "Review Stars Visual", "structure": "Stars dominate", "visual": "Large stars, rating"},
120
  {"key": "quote_card", "name": "Quote Card", "structure": "Quote as main visual", "visual": "Clear, readable"},
121
  {"key": "case_study_frame", "name": "Case Study Frame", "structure": "Case study format", "visual": "Results visible"},
122
  {"key": "crowd", "name": "Crowd Visual", "structure": "Many people visible", "visual": "Popular feel"},
 
116
  "name": "Social Proof",
117
  "concepts": [
118
  {"key": "testimonial_screenshot", "name": "Testimonial Screenshot", "structure": "Screenshot format", "visual": "Authentic testimonial"},
 
119
  {"key": "quote_card", "name": "Quote Card", "structure": "Quote as main visual", "visual": "Clear, readable"},
120
  {"key": "case_study_frame", "name": "Case Study Frame", "structure": "Case study format", "visual": "Results visible"},
121
  {"key": "crowd", "name": "Crowd Visual", "structure": "Many people visible", "visual": "Popular feel"},
frontend/components/layout/Header.tsx CHANGED
@@ -35,7 +35,7 @@ export const Header: React.FC = () => {
35
  <div className="absolute inset-0 bg-blue-500/20 rounded-full blur-xl group-hover:bg-cyan-500/20 transition-colors duration-300"></div>
36
  </div>
37
  <span className="text-2xl font-bold gradient-text group-hover:scale-105 transition-transform duration-300">
38
- Creative Breakthrough
39
  </span>
40
  </Link>
41
  </div>
 
35
  <div className="absolute inset-0 bg-blue-500/20 rounded-full blur-xl group-hover:bg-cyan-500/20 transition-colors duration-300"></div>
36
  </div>
37
  <span className="text-2xl font-bold gradient-text group-hover:scale-105 transition-transform duration-300">
38
+ PsyAdGenesis
39
  </span>
40
  </Link>
41
  </div>
services/generator.py CHANGED
@@ -5,33 +5,35 @@ Uses professional prompting techniques for PsyAdGenesis
5
  Saves ad creatives to Neon database with image URLs
6
  """
7
 
 
 
 
 
 
8
  import os
9
  import sys
10
-
11
- # Add parent directory to path for imports
12
- sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
13
-
14
  import random
15
  import uuid
16
  import json
17
  import asyncio
18
  from datetime import datetime
19
  from typing import Dict, Any, List, Optional
20
- from concurrent.futures import ThreadPoolExecutor
21
 
 
 
 
 
22
  from config import settings
23
  from services.llm import llm_service
24
  from services.image import image_service
25
 
26
- # Optional database import (for when asyncpg is available)
27
  try:
28
  from services.database import db_service
29
  except ImportError:
30
- # Database service not available (asyncpg not installed)
31
  db_service = None
32
  print("Note: Database service not available (asyncpg not installed). Ads will be generated but not saved to database.")
33
 
34
- # Optional R2 storage import
35
  try:
36
  from services.r2_storage import get_r2_storage
37
  r2_storage_available = True
@@ -39,7 +41,6 @@ except ImportError:
39
  r2_storage_available = False
40
  print("Note: R2 storage not available. Images will only be saved locally.")
41
 
42
- # Optional extensive import
43
  try:
44
  from services.third_flow import third_flow_service
45
  third_flow_available = True
@@ -47,6 +48,7 @@ except ImportError:
47
  third_flow_available = False
48
  print("Note: Extensive service not available.")
49
 
 
50
  from data import home_insurance, glp1
51
  from services.matrix import matrix_service
52
  from data.frameworks import get_frameworks_for_niche, get_framework_hook_examples, get_all_frameworks
@@ -61,6 +63,9 @@ from data.visuals import (
61
  get_random_composition, get_random_mood, get_color_palette, get_niche_visual_guidance
62
  )
63
 
 
 
 
64
 
65
  # Niche data loaders
66
  NICHE_DATA = {
@@ -68,10 +73,6 @@ NICHE_DATA = {
68
  "glp1": glp1.get_niche_data,
69
  }
70
 
71
- # Note: Frameworks are now loaded from data/frameworks.py
72
- # This provides comprehensive framework data with hooks, visual styles, and niche-specific recommendations
73
-
74
-
75
  # Age brackets for identity targeting (proven high-CTR pattern)
76
  AGE_BRACKETS = [
77
  {"label": "21-40", "color": "yellow/gold button"},
@@ -82,17 +83,7 @@ AGE_BRACKETS = [
82
  {"label": "70+ years old", "color": "gray box"},
83
  ]
84
 
85
- # Note: Prices are no longer hardcoded. The AI will decide contextually
86
- # whether to include prices and what specific amounts to use based on the ad format and strategy.
87
-
88
- # Note: Containers are now loaded from data/containers.py
89
- # This provides comprehensive container data with visual guidance, colors, fonts, and authenticity tips
90
- # Native containers (iMessage, WhatsApp, SMS, etc.) are preferred for ad-blindness bypass
91
-
92
- # Note: CAMERA_ANGLES, LIGHTING_STYLES, and COMPOSITIONS are now imported from data/visuals.py
93
- # This eliminates duplication and ensures consistency across the codebase
94
-
95
- # Old footage / vintage film styles
96
  VINTAGE_FILM_STYLES = [
97
  "grainy 8mm home movie footage from the 1970s, warm faded colors, light leaks",
98
  "old VHS tape recording with tracking lines, fuzzy edges, dated look",
@@ -126,10 +117,16 @@ class AdGenerator:
126
  Implements professional prompting techniques.
127
  """
128
 
 
 
 
 
129
  def __init__(self):
130
  """Initialize the generator."""
131
  self.output_dir = settings.output_dir
132
  os.makedirs(self.output_dir, exist_ok=True)
 
 
133
 
134
  def _should_save_locally(self) -> bool:
135
  """
@@ -168,11 +165,22 @@ class AdGenerator:
168
  print(f"Warning: Failed to save image locally: {e}")
169
  return None
170
 
 
 
 
 
171
  def _get_niche_data(self, niche: str) -> Dict[str, Any]:
172
- """Load data for a specific niche."""
173
  if niche not in NICHE_DATA:
174
  raise ValueError(f"Unsupported niche: {niche}. Supported: {list(NICHE_DATA.keys())}")
175
- return NICHE_DATA[niche]()
 
 
 
 
 
 
 
176
 
177
  def _get_framework_strategy_compatibility(self, framework_key: str, strategy_name: str) -> float:
178
  """
@@ -289,11 +297,14 @@ class AdGenerator:
289
  return [niche_data["strategies"][name] for name in strategy_names]
290
 
291
  def _random_hooks(self, strategies: List[Dict], count: int = 3) -> List[str]:
292
- """Randomly select hooks from the chosen strategies."""
293
- all_hooks = []
294
- for strategy in strategies:
295
- all_hooks.extend(strategy["hooks"])
296
- return random.sample(all_hooks, min(count, len(all_hooks)))
 
 
 
297
 
298
  def _get_visual_library_for_niche(self, niche: str) -> Dict[str, List[str]]:
299
  """
@@ -384,11 +395,9 @@ class AdGenerator:
384
  if visuals:
385
  selected_visuals.extend(random.sample(visuals, min(1, len(visuals))))
386
 
387
- # If we need more, add random from any category
388
  if len(selected_visuals) < count:
389
- all_visuals = []
390
- for visuals in visual_library.values():
391
- all_visuals.extend(visuals)
392
  remaining = count - len(selected_visuals)
393
  if all_visuals:
394
  selected_visuals.extend(random.sample(all_visuals, min(remaining, len(all_visuals))))
@@ -408,28 +417,26 @@ class AdGenerator:
408
  niche: Niche name for visual library access
409
  use_library: Whether to use comprehensive visual library (improvement)
410
  """
411
- all_styles = []
412
-
413
  # Get strategy-specific visuals
414
- for strategy in strategies:
415
- all_styles.extend(strategy.get("visual_styles", []))
416
 
417
  # Add visual library visuals (improvement)
418
  if use_library and niche:
419
- for strategy in strategies:
420
- library_visuals = self._select_visuals_from_library(niche, strategy.get("name", ""), count=1)
421
- all_styles.extend(library_visuals)
422
-
423
- # Remove duplicates while preserving order
424
- seen = set()
425
- unique_styles = []
426
- for style in all_styles:
427
- if style not in seen:
428
- seen.add(style)
429
- unique_styles.append(style)
430
 
 
 
431
  return random.sample(unique_styles, min(count, len(unique_styles))) if unique_styles else []
432
 
 
 
 
 
433
  def _get_niche_specific_guidance(self, niche: str) -> str:
434
  """Get niche-specific guidance for the prompt."""
435
  if niche == "home_insurance":
@@ -531,6 +538,10 @@ NICHE-SPECIFIC REQUIREMENTS (GLP-1 / WEIGHT LOSS):
531
  # Return compatible first, then others
532
  return compatible + [c for c in all_containers if c not in compatible]
533
 
 
 
 
 
534
  def _select_container(self, prefer_native: bool = True, strategy: str = "balanced", framework_key: Optional[str] = None) -> Dict[str, Any]:
535
  """
536
  Select a container type for native-looking ad format.
@@ -578,6 +589,10 @@ NICHE-SPECIFIC REQUIREMENTS (GLP-1 / WEIGHT LOSS):
578
 
579
  return container if container else get_random_container()
580
 
 
 
 
 
581
  def _build_copy_prompt(
582
  self,
583
  niche: str,
@@ -1904,22 +1919,20 @@ CONCEPT: {concept['name']}
1904
  if not researcher_output:
1905
  raise ValueError("Researcher returned no results")
1906
 
1907
- # Step 2: Retrieve knowledge (in parallel)
1908
  print("📚 Step 2: Retrieving marketing knowledge...")
1909
- from concurrent.futures import ThreadPoolExecutor
1910
 
1911
- with ThreadPoolExecutor(max_workers=2) as executor:
1912
- book_future = executor.submit(
 
1913
  third_flow_service.retrieve_search,
1914
  target_audience, offer, niche_display
1915
- )
1916
- ads_future = executor.submit(
1917
  third_flow_service.retrieve_ads,
1918
  target_audience, offer, niche_display
1919
  )
1920
-
1921
- book_knowledge = book_future.result()
1922
- ads_knowledge = ads_future.result()
1923
 
1924
  # Step 3: Creative Director
1925
  print(f"🎨 Step 3: Creating {num_strategies} creative strategy/strategies...")
@@ -1941,22 +1954,19 @@ CONCEPT: {concept['name']}
1941
  creative_strategies = creative_strategies[:num_strategies]
1942
  print(f"📊 Using {len(creative_strategies)} strategy/strategies (requested: {num_strategies})")
1943
 
1944
- # Step 4: Process strategies in parallel (designer + copywriter)
1945
  print(f"⚡ Step 4: Processing {len(creative_strategies)} strategies in parallel...")
1946
- from concurrent.futures import ThreadPoolExecutor as TPE
1947
- from functools import partial
1948
-
1949
- # Create a partial function that includes the niche parameter
1950
- process_strategy_with_niche = partial(
1951
- third_flow_service.process_strategy,
1952
- niche=niche_display
1953
- )
1954
 
1955
- with TPE(max_workers=8) as executor:
1956
- strategy_results = list(executor.map(
1957
- process_strategy_with_niche,
1958
- creative_strategies
1959
- ))
 
 
 
 
 
1960
 
1961
  # Step 5: Generate images for each strategy
1962
  # Ensure we only process the requested number of strategies
@@ -2162,6 +2172,10 @@ CONCEPT: {concept['name']}
2162
  else:
2163
  raise ValueError("No ads generated from extensive")
2164
 
 
 
 
 
2165
  def _build_matrix_ad_prompt(
2166
  self,
2167
  niche: str,
 
5
  Saves ad creatives to Neon database with image URLs
6
  """
7
 
8
+ # ============================================================================
9
+ # IMPORTS
10
+ # ============================================================================
11
+
12
+ # Standard library
13
  import os
14
  import sys
 
 
 
 
15
  import random
16
  import uuid
17
  import json
18
  import asyncio
19
  from datetime import datetime
20
  from typing import Dict, Any, List, Optional
 
21
 
22
+ # Add parent directory to path for imports
23
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
24
+
25
+ # Local application imports
26
  from config import settings
27
  from services.llm import llm_service
28
  from services.image import image_service
29
 
30
+ # Optional service imports
31
  try:
32
  from services.database import db_service
33
  except ImportError:
 
34
  db_service = None
35
  print("Note: Database service not available (asyncpg not installed). Ads will be generated but not saved to database.")
36
 
 
37
  try:
38
  from services.r2_storage import get_r2_storage
39
  r2_storage_available = True
 
41
  r2_storage_available = False
42
  print("Note: R2 storage not available. Images will only be saved locally.")
43
 
 
44
  try:
45
  from services.third_flow import third_flow_service
46
  third_flow_available = True
 
48
  third_flow_available = False
49
  print("Note: Extensive service not available.")
50
 
51
+ # Data module imports
52
  from data import home_insurance, glp1
53
  from services.matrix import matrix_service
54
  from data.frameworks import get_frameworks_for_niche, get_framework_hook_examples, get_all_frameworks
 
63
  get_random_composition, get_random_mood, get_color_palette, get_niche_visual_guidance
64
  )
65
 
66
+ # ============================================================================
67
+ # CONSTANTS
68
+ # ============================================================================
69
 
70
  # Niche data loaders
71
  NICHE_DATA = {
 
73
  "glp1": glp1.get_niche_data,
74
  }
75
 
 
 
 
 
76
  # Age brackets for identity targeting (proven high-CTR pattern)
77
  AGE_BRACKETS = [
78
  {"label": "21-40", "color": "yellow/gold button"},
 
83
  {"label": "70+ years old", "color": "gray box"},
84
  ]
85
 
86
+ # Vintage film visual styles
 
 
 
 
 
 
 
 
 
 
87
  VINTAGE_FILM_STYLES = [
88
  "grainy 8mm home movie footage from the 1970s, warm faded colors, light leaks",
89
  "old VHS tape recording with tracking lines, fuzzy edges, dated look",
 
117
  Implements professional prompting techniques.
118
  """
119
 
120
+ # ========================================================================
121
+ # INITIALIZATION & UTILITY METHODS
122
+ # ========================================================================
123
+
124
  def __init__(self):
125
  """Initialize the generator."""
126
  self.output_dir = settings.output_dir
127
  os.makedirs(self.output_dir, exist_ok=True)
128
+ # Cache niche data to avoid repeated function calls
129
+ self._niche_data_cache: Dict[str, Dict[str, Any]] = {}
130
 
131
  def _should_save_locally(self) -> bool:
132
  """
 
165
  print(f"Warning: Failed to save image locally: {e}")
166
  return None
167
 
168
+ # ========================================================================
169
+ # DATA RETRIEVAL & CACHING METHODS
170
+ # ========================================================================
171
+
172
  def _get_niche_data(self, niche: str) -> Dict[str, Any]:
173
+ """Load data for a specific niche (cached for performance)."""
174
  if niche not in NICHE_DATA:
175
  raise ValueError(f"Unsupported niche: {niche}. Supported: {list(NICHE_DATA.keys())}")
176
+ # Use cache to avoid repeated function calls
177
+ if niche not in self._niche_data_cache:
178
+ self._niche_data_cache[niche] = NICHE_DATA[niche]()
179
+ return self._niche_data_cache[niche]
180
+
181
+ # ========================================================================
182
+ # STRATEGY & COMPATIBILITY METHODS
183
+ # ========================================================================
184
 
185
  def _get_framework_strategy_compatibility(self, framework_key: str, strategy_name: str) -> float:
186
  """
 
297
  return [niche_data["strategies"][name] for name in strategy_names]
298
 
299
  def _random_hooks(self, strategies: List[Dict], count: int = 3) -> List[str]:
300
+ """Randomly select hooks from the chosen strategies (optimized list building)."""
301
+ # Optimized: use list comprehension instead of extend in loop
302
+ all_hooks = [hook for strategy in strategies for hook in strategy["hooks"]]
303
+ return random.sample(all_hooks, min(count, len(all_hooks))) if all_hooks else []
304
+
305
+ # ========================================================================
306
+ # VISUAL SELECTION METHODS
307
+ # ========================================================================
308
 
309
  def _get_visual_library_for_niche(self, niche: str) -> Dict[str, List[str]]:
310
  """
 
395
  if visuals:
396
  selected_visuals.extend(random.sample(visuals, min(1, len(visuals))))
397
 
398
+ # If we need more, add random from any category (optimized: use list comprehension)
399
  if len(selected_visuals) < count:
400
+ all_visuals = [v for visuals in visual_library.values() for v in visuals]
 
 
401
  remaining = count - len(selected_visuals)
402
  if all_visuals:
403
  selected_visuals.extend(random.sample(all_visuals, min(remaining, len(all_visuals))))
 
417
  niche: Niche name for visual library access
418
  use_library: Whether to use comprehensive visual library (improvement)
419
  """
420
+ # Optimized: use list comprehension instead of extend in loops
 
421
  # Get strategy-specific visuals
422
+ all_styles = [style for strategy in strategies for style in strategy.get("visual_styles", [])]
 
423
 
424
  # Add visual library visuals (improvement)
425
  if use_library and niche:
426
+ library_visuals = [
427
+ v for strategy in strategies
428
+ for v in self._select_visuals_from_library(niche, strategy.get("name", ""), count=1)
429
+ ]
430
+ all_styles.extend(library_visuals)
 
 
 
 
 
 
431
 
432
+ # Remove duplicates while preserving order (optimized using dict.fromkeys)
433
+ unique_styles = list(dict.fromkeys(all_styles))
434
  return random.sample(unique_styles, min(count, len(unique_styles))) if unique_styles else []
435
 
436
+ # ========================================================================
437
+ # NICHE & CONTENT CONFIGURATION METHODS
438
+ # ========================================================================
439
+
440
  def _get_niche_specific_guidance(self, niche: str) -> str:
441
  """Get niche-specific guidance for the prompt."""
442
  if niche == "home_insurance":
 
538
  # Return compatible first, then others
539
  return compatible + [c for c in all_containers if c not in compatible]
540
 
541
+ # ========================================================================
542
+ # CONTAINER SELECTION METHODS
543
+ # ========================================================================
544
+
545
  def _select_container(self, prefer_native: bool = True, strategy: str = "balanced", framework_key: Optional[str] = None) -> Dict[str, Any]:
546
  """
547
  Select a container type for native-looking ad format.
 
589
 
590
  return container if container else get_random_container()
591
 
592
+ # ========================================================================
593
+ # PROMPT BUILDING METHODS
594
+ # ========================================================================
595
+
596
  def _build_copy_prompt(
597
  self,
598
  niche: str,
 
1919
  if not researcher_output:
1920
  raise ValueError("Researcher returned no results")
1921
 
1922
+ # Step 2: Retrieve knowledge (in parallel) - Optimized: use asyncio.to_thread instead of blocking .result()
1923
  print("📚 Step 2: Retrieving marketing knowledge...")
 
1924
 
1925
+ # Run synchronous operations in thread pool without blocking event loop
1926
+ book_knowledge, ads_knowledge = await asyncio.gather(
1927
+ asyncio.to_thread(
1928
  third_flow_service.retrieve_search,
1929
  target_audience, offer, niche_display
1930
+ ),
1931
+ asyncio.to_thread(
1932
  third_flow_service.retrieve_ads,
1933
  target_audience, offer, niche_display
1934
  )
1935
+ )
 
 
1936
 
1937
  # Step 3: Creative Director
1938
  print(f"🎨 Step 3: Creating {num_strategies} creative strategy/strategies...")
 
1954
  creative_strategies = creative_strategies[:num_strategies]
1955
  print(f"📊 Using {len(creative_strategies)} strategy/strategies (requested: {num_strategies})")
1956
 
1957
+ # Step 4: Process strategies in parallel (designer + copywriter) - Optimized: use asyncio.to_thread instead of ThreadPoolExecutor
1958
  print(f"⚡ Step 4: Processing {len(creative_strategies)} strategies in parallel...")
 
 
 
 
 
 
 
 
1959
 
1960
+ # Process strategies concurrently using asyncio (non-blocking)
1961
+ strategy_tasks = [
1962
+ asyncio.to_thread(
1963
+ third_flow_service.process_strategy,
1964
+ strategy,
1965
+ niche=niche_display
1966
+ )
1967
+ for strategy in creative_strategies
1968
+ ]
1969
+ strategy_results = await asyncio.gather(*strategy_tasks)
1970
 
1971
  # Step 5: Generate images for each strategy
1972
  # Ensure we only process the requested number of strategies
 
2172
  else:
2173
  raise ValueError("No ads generated from extensive")
2174
 
2175
+ # ========================================================================
2176
+ # MATRIX-SPECIFIC PROMPT METHODS
2177
+ # ========================================================================
2178
+
2179
  def _build_matrix_ad_prompt(
2180
  self,
2181
  niche: str,