fix(product-marketing): route image generation through unified subscription validation

Backend:
- product_image_service.py: Replaced direct wavespeed_client.generate_image()
  with generate_image() from main_image_generation (unified entry point)
- This ensures subscription pre-flight validation (_validate_image_operation)
  and usage tracking (_track_image_operation_usage) are enforced
- Removed _generate_image_with_retry method and WaveSpeedClient dependency
- Animation/video/avatar services already route through ImageStudioManager - no changes needed

Frontend:
- useProductMarketing.ts: Added formatError() helper for 402/429 detection
  across all 8 API operations
- useCampaignCreator.ts: Added formatError() helper for 402/429 detection
  across all 13 API operations
- All error messages now surface subscription limits with upgrade prompts
This commit is contained in:
ajaysi
2026-05-09 17:18:16 +05:30
parent 93a1985d9f
commit 7385100017
3 changed files with 56 additions and 110 deletions

View File

@@ -13,7 +13,7 @@ from dataclasses import dataclass
from pathlib import Path
from loguru import logger
from services.wavespeed.client import WaveSpeedClient
from services.llm_providers.main_image_generation import generate_image
from utils.asset_tracker import save_asset_to_library
from services.database import SessionLocal
from fastapi import HTTPException
@@ -113,12 +113,7 @@ class ProductImageService:
def __init__(self):
"""Initialize Product Image Service."""
try:
self.wavespeed_client = WaveSpeedClient()
logger.info("[Product Image Service] Initialized")
except Exception as e:
logger.error(f"[Product Image Service] Failed to initialize WaveSpeed client: {str(e)}")
raise ProductImageServiceError(f"Failed to initialize service: {str(e)}") from e
logger.info("[Product Image Service] Initialized")
def validate_request(self, request: ProductImageRequest) -> None:
"""
@@ -260,77 +255,7 @@ class ProductImageService:
return full_prompt
def _generate_image_with_retry(
self,
model: str,
prompt: str,
width: int,
height: int,
max_retries: int = 3,
retry_delay: float = 2.0
) -> bytes:
"""
Generate image with retry logic for transient failures.
Args:
model: Model to use
prompt: Generation prompt
width: Image width
height: Image height
max_retries: Maximum number of retries
retry_delay: Delay between retries in seconds
Returns:
Generated image bytes
Raises:
ImageGenerationError: If generation fails after retries
"""
last_error = None
for attempt in range(max_retries):
try:
logger.info(f"[Product Image Service] Image generation attempt {attempt + 1}/{max_retries}")
image_bytes = self.wavespeed_client.generate_image(
model=model,
prompt=prompt,
width=width,
height=height,
enable_sync_mode=True,
timeout=120,
)
if not image_bytes:
raise ValueError("Image generation returned empty result")
if len(image_bytes) < 100: # Sanity check: image should be at least 100 bytes
raise ValueError(f"Generated image too small: {len(image_bytes)} bytes")
logger.info(f"[Product Image Service] ✅ Image generated successfully: {len(image_bytes)} bytes")
return image_bytes
except Exception as e:
last_error = e
error_msg = str(e)
logger.warning(f"[Product Image Service] Attempt {attempt + 1} failed: {error_msg}")
# Don't retry on validation errors or client errors (4xx)
if "4" in error_msg or "validation" in error_msg.lower() or "invalid" in error_msg.lower():
logger.error(f"[Product Image Service] Non-retryable error: {error_msg}")
raise ImageGenerationError(f"Image generation failed: {error_msg}") from e
# Retry on transient errors
if attempt < max_retries - 1:
logger.info(f"[Product Image Service] Retrying in {retry_delay} seconds...")
time.sleep(retry_delay)
retry_delay *= 1.5 # Exponential backoff
else:
logger.error(f"[Product Image Service] All retry attempts failed")
raise ImageGenerationError(f"Image generation failed after {max_retries} attempts: {str(last_error)}") from last_error
async def generate_product_image(
def generate_product_image(
self,
request: ProductImageRequest,
user_id: str,
@@ -374,15 +299,18 @@ class ProductImageService:
# Generate image using WaveSpeed with retry logic
try:
image_bytes = self._generate_image_with_retry(
model=model,
result = generate_image(
prompt=prompt,
width=width,
height=height,
max_retries=3,
retry_delay=2.0
options={
"provider": "wavespeed",
"model": model,
"width": width,
"height": height,
},
user_id=user_id,
)
except ImageGenerationError as e:
image_bytes = result.image_bytes
except Exception as e:
logger.error(f"[Product Image Service] Image generation failed: {str(e)}")
generation_time = time.time() - start_time
return ProductImageResult(