Files
ALwrity/docs/image studio/IMAGE_STUDIO_CODE_PATTERNS_REFERENCE.md

18 KiB

Image Studio: Code Patterns Reference

Purpose: Quick reference for reusable code patterns when integrating new AI models
Status: Implementation Guide - Focus on Reusability
Key Principle: Extend existing main_image_generation.py, don't duplicate


📊 Pattern Comparison: Video Studio vs. Image Studio (Existing)

Pattern 1: Unified Entry Point

Video Studio (Reference)

# backend/services/llm_providers/main_video_generation.py

async def ai_video_generate(
    prompt: Optional[str] = None,
    image_data: Optional[bytes] = None,
    operation_type: str = "text-to-video",
    provider: str = "huggingface",
    user_id: Optional[str] = None,
    progress_callback: Optional[Callable[[float, str], None]] = None,
    **kwargs,
) -> Dict[str, Any]:
    # 1. Validation
    if not user_id:
        raise RuntimeError("user_id is required")
    
    # 2. Pre-flight validation
    validate_video_generation_operations(...)
    
    # 3. Route to provider
    if operation_type == "text-to-video":
        if provider == "wavespeed":
            result = await _generate_text_to_video_wavespeed(...)
        elif provider == "huggingface":
            result = _generate_with_huggingface(...)
    elif operation_type == "image-to-video":
        if provider == "wavespeed":
            result = await _generate_image_to_video_wavespeed(...)
    
    # 4. Track usage
    track_video_usage(...)
    
    # 5. Return standardized result
    return {
        "video_bytes": result["video_bytes"],
        "prompt": result.get("prompt", prompt),
        "duration": result.get("duration", 5.0),
        "model_name": result.get("model_name", model),
        "cost": result.get("cost", 0.0),
        "provider": provider,
        "metadata": result.get("metadata", {}),
    }

Image Studio (Proposed)

# backend/services/llm_providers/main_image_operations.py

# CURRENT: main_image_generation.py (EXISTS)
def generate_image(
    prompt: str,
    options: Optional[Dict[str, Any]] = None,
    user_id: Optional[str] = None
) -> ImageGenerationResult:
    """Generate image - REUSABLE pattern for all operations."""
    # 1. Pre-flight validation (EXTRACT to helper)
    if user_id:
        _validate_image_operation(user_id, "text-to-image")
    
    # 2. Select provider (REUSABLE)
    provider_name = _select_provider(options.get("provider"))
    provider = _get_provider(provider_name)
    
    # 3. Generate
    result = provider.generate(image_options)
    
    # 4. Track usage (EXTRACT to helper)
    if user_id and result:
        _track_image_operation_usage(
            user_id=user_id,
            provider=provider_name,
            model=result.model,
            operation_type="text-to-image",
            result_bytes=result.image_bytes,
            cost=result.metadata.get("estimated_cost", 0.0),
            metadata=result.metadata
        )
    
    return result

# EXTEND: Add new operations following same pattern
def generate_image_edit(
    image_base64: str,
    prompt: str,
    model: Optional[str] = None,
    options: Optional[Dict[str, Any]] = None,
    user_id: Optional[str] = None
) -> ImageGenerationResult:
    """Edit image - REUSES same helpers."""
    # 1. REUSE: Validation helper
    if user_id:
        _validate_image_operation(user_id, "image-edit")
    
    # 2. Get provider (REUSES provider pattern)
    provider = _get_edit_provider(model or "wavespeed")
    
    # 3. Edit
    result = provider.edit(image_base64, prompt, options)
    
    # 4. REUSE: Tracking helper
    if user_id and result:
        _track_image_operation_usage(...)
    
    return result

Pattern 2: Pre-flight Validation

Video Studio (Reference)

# In main_video_generation.py

from services.subscription.preflight_validator import validate_video_generation_operations

# PRE-FLIGHT VALIDATION: Validate BEFORE API call
db = next(get_db())
try:
    pricing_service = PricingService(db)
    validate_video_generation_operations(
        pricing_service=pricing_service,
        user_id=user_id
    )
except HTTPException:
    # Re-raise immediately - don't proceed with API call
    raise
finally:
    db.close()

Image Studio (EXISTS - Extract Helper)

# CURRENT: In main_image_generation.py (lines 58-83)
if user_id:
    db = next(get_db())
    try:
        pricing_service = PricingService(db)
        validate_image_generation_operations(...)
    finally:
        db.close()

# EXTRACT: Reusable helper (REUSE across all operations)
def _validate_image_operation(
    user_id: Optional[str],
    operation_type: str,
    num_operations: int = 1
) -> None:
    """REUSABLE validation helper - extracted from generate_image()."""
    if not user_id:
        logger.warning("No user_id - skipping validation")
        return
    
    from services.database import get_db
    from services.subscription import PricingService
    from services.subscription.preflight_validator import validate_image_generation_operations
    
    db = next(get_db())
    try:
        pricing_service = PricingService(db)
        validate_image_generation_operations(
            pricing_service=pricing_service,
            user_id=user_id,
            num_images=num_operations
        )
    finally:
        db.close()

# USE: In all operation functions
def generate_image_edit(...):
    _validate_image_operation(user_id, "image-edit")  # ✅ REUSE
    # ... rest of function

Pattern 3: Provider Handler

Video Studio (Reference)

async def _generate_image_to_video_wavespeed(
    image_data: Optional[bytes] = None,
    image_base64: Optional[str] = None,
    prompt: str = "",
    duration: int = 5,
    resolution: str = "720p",
    model: str = "alibaba/wan-2.5/image-to-video",
    **kwargs
) -> Dict[str, Any]:
    """Generate video from image using WaveSpeed."""
    from services.image_studio.wan25_service import WAN25Service
    
    wan25_service = WAN25Service()
    result = await wan25_service.generate_video(
        image_base64=image_base64,
        prompt=prompt,
        resolution=resolution,
        duration=duration,
        **kwargs
    )
    
    return {
        "video_bytes": result["video_bytes"],
        "prompt": result.get("prompt", prompt),
        "duration": result.get("duration", float(duration)),
        "model_name": result.get("model_name", model),
        "cost": result.get("cost", 0.0),
        "provider": "wavespeed",
        "resolution": result.get("resolution", resolution),
        "width": result.get("width", 1280),
        "height": result.get("height", 720),
        "metadata": result.get("metadata", {}),
    }

Image Studio (EXISTS - Extend Pattern)

# CURRENT: WaveSpeedImageProvider (EXISTS)
# backend/services/llm_providers/image_generation/wavespeed_provider.py

class WaveSpeedImageProvider(ImageGenerationProvider):
    """REUSABLE provider pattern."""
    
    SUPPORTED_MODELS = {
        "ideogram-v3-turbo": {
            "model_path": "ideogram-ai/ideogram-v3-turbo",
            "cost": 0.10,
        },
        "qwen-image": {...}
    }
    
    def __init__(self, api_key: Optional[str] = None):
        self.client = WaveSpeedClient(api_key=api_key)  # REUSE client
    
    def generate(self, options: ImageGenerationOptions) -> ImageGenerationResult:
        # REUSABLE pattern
        model_info = self.SUPPORTED_MODELS.get(options.model)
        image_bytes = self.client.generate_image(
            model=model_info["model_path"],
            prompt=options.prompt,
            **options.to_dict()
        )
        return ImageGenerationResult(...)

# EXTEND: New provider following same pattern
class WaveSpeedEditProvider(ImageEditProvider):
    """REUSES same pattern as WaveSpeedImageProvider."""
    
    SUPPORTED_MODELS = {
        "qwen-edit": {
            "model_path": "wavespeed-ai/qwen-image/edit",
            "cost": 0.02,
        },
        # ... 12 editing models
    }
    
    def __init__(self, api_key: Optional[str] = None):
        self.client = WaveSpeedClient(api_key=api_key)  # ✅ REUSE client
    
    def edit(self, image_base64: str, prompt: str, ...) -> ImageGenerationResult:
        # ✅ REUSES same client call pattern
        model_info = self.SUPPORTED_MODELS.get(model)
        image_bytes = self.client.edit_image(
            model=model_info["model_path"],
            image_base64=image_base64,
            prompt=prompt,
            **options
        )
        return ImageGenerationResult(...)  # ✅ REUSES same result format

Pattern 4: Usage Tracking

Video Studio (Reference)

def track_video_usage(
    *,
    user_id: str,
    provider: str,
    model_name: str,
    prompt: str,
    video_bytes: bytes,
    cost_override: Optional[float] = None,
) -> Dict[str, Any]:
    """Track subscription usage for video generation."""
    from services.database import get_db
    from models.subscription_models import APIProvider, APIUsageLog, UsageSummary
    
    db = next(get_db())
    try:
        pricing_service = PricingService(db)
        current_period = pricing_service.get_current_billing_period(user_id)
        
        # Get or create usage summary
        usage_summary = get_or_create_usage_summary(user_id, current_period)
        
        # Calculate cost
        cost = cost_override or calculate_video_cost(provider, model_name)
        
        # Update usage summary
        usage_summary.video_calls += 1
        usage_summary.video_cost += cost
        
        # Log API usage
        usage_log = APIUsageLog(
            user_id=user_id,
            provider=APIProvider.VIDEO,
            model_used=model_name,
            cost_total=cost,
            response_size=len(video_bytes),
        )
        db.add(usage_log)
        db.commit()
        
        return {
            "current_calls": usage_summary.video_calls,
            "cost": cost,
        }
    finally:
        db.close()

Image Studio (EXISTS - Extract Helper)

# CURRENT: In main_image_generation.py (lines 117-265)
# EXTRACT: Reusable tracking helper

def _track_image_operation_usage(
    user_id: str,
    provider: str,
    model: str,
    operation_type: str,
    result_bytes: bytes,
    cost: float,
    prompt: Optional[str] = None,
    metadata: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
    """
    REUSABLE tracking helper - extracted from generate_image().
    Used by ALL image operation functions.
    """
    from services.database import get_db
    from models.subscription_models import UsageSummary, APIUsageLog, APIProvider
    from services.subscription import PricingService
    
    db = next(get_db())
    try:
        pricing = PricingService(db)
        current_period = pricing.get_current_billing_period(user_id) or datetime.now().strftime("%Y-%m")
        
        # REUSE: Same summary lookup pattern
        summary = db.query(UsageSummary).filter(
            UsageSummary.user_id == user_id,
            UsageSummary.billing_period == current_period
        ).first()
        
        if not summary:
            summary = UsageSummary(user_id=user_id, billing_period=current_period)
            db.add(summary)
            db.flush()
        
        # REUSE: Same update pattern
        current_calls = getattr(summary, "stability_calls", 0) or 0
        current_cost = getattr(summary, "stability_cost", 0.0) or 0.0
        
        from sqlalchemy import text as sql_text
        db.execute(sql_text("""
            UPDATE usage_summaries 
            SET stability_calls = :new_calls, stability_cost = :new_cost
            WHERE user_id = :user_id AND billing_period = :period
        """), {
            'new_calls': current_calls + 1,
            'new_cost': current_cost + cost,
            'user_id': user_id,
            'period': current_period
        })
        
        # REUSE: Same logging pattern
        usage_log = APIUsageLog(
            user_id=user_id,
            provider=APIProvider.STABILITY,
            model_used=model,
            cost_total=cost,
            response_size=len(result_bytes),
            billing_period=current_period,
        )
        db.add(usage_log)
        db.commit()
        
        return {"current_calls": current_calls + 1, "cost": cost}
    finally:
        db.close()

# USE: In all operation functions
def generate_image_edit(...):
    result = provider.edit(...)
    if user_id and result:
        _track_image_operation_usage(...)  # ✅ REUSE
    return result

Pattern 5: Service Integration

Video Studio (Reference)

# backend/services/video_studio/video_studio_service.py

class VideoStudioService:
    async def generate_image_to_video(
        self,
        image_data: bytes,
        provider: str = "wavespeed",
        model: str = "alibaba/wan-2.5",
        user_id: str = None,
        **kwargs
    ) -> Dict[str, Any]:
        """Generate video from image."""
        from services.llm_providers.main_video_generation import ai_video_generate
        
        # Use unified entry point
        result = ai_video_generate(
            image_data=image_data,
            operation_type="image-to-video",
            provider=provider,
            user_id=user_id,
            model=model,
            **kwargs
        )
        
        # Save video file
        save_result = self._save_video_file(
            video_bytes=result["video_bytes"],
            operation_type="image-to-video",
            user_id=user_id,
        )
        
        return {
            "video_url": save_result["file_url"],
            "cost": result["cost"],
            "metadata": result["metadata"],
        }

Image Studio (Proposed)

# backend/services/image_studio/create_service.py

class CreateStudioService:
    async def generate(
        self,
        request: CreateStudioRequest,
        user_id: Optional[str] = None,
    ) -> Dict[str, Any]:
        """Generate image using unified entry point."""
        from services.llm_providers.main_image_operations import ai_image_generate
        
        # Use unified entry point
        result = await ai_image_generate(
            prompt=request.prompt,
            operation_type="text-to-image",
            provider=request.provider or "auto",
            model=request.model,
            user_id=user_id,
            width=request.width,
            height=request.height,
            **request.to_kwargs(),
        )
        
        # Save to asset library
        asset = save_to_asset_library(
            image_bytes=result["image_bytes"],
            user_id=user_id,
            module="create_studio",
            metadata=result["metadata"],
        )
        
        return {
            "images": [result["image_bytes"]],
            "asset_id": asset.id,
            "cost": result["cost"],
            "metadata": result["metadata"],
        }

🔑 Key Differences to Note

1. Operation Types

  • Video: text-to-video, image-to-video
  • Image: text-to-image, image-edit, image-upscale, image-to-3d, face-swap, etc.

2. Return Formats

  • Video: Always returns video_bytes
  • Image: Returns image_bytes (but may also return 3D models, etc.)

3. Cost Calculation

  • Video: Based on duration, resolution
  • Image: Based on model, operation type, resolution

4. Usage Tracking

  • Video: Tracks video_calls, video_cost
  • Image: Tracks stability_calls, image_edit_calls, etc. based on operation type

📝 Checklist for Adding New Model (REUSABLE PATTERN)

Step 1: Add to Provider (REUSES existing pattern)

  • Add model to provider's SUPPORTED_MODELS dict
    # In WaveSpeedEditProvider
    SUPPORTED_MODELS["new-model"] = {
        "model_path": "wavespeed-ai/new-model",
        "cost": 0.05,
    }
    

Step 2: Register in Model Registry (REUSES registry)

  • Add to ImageModelRegistry.MODELS
    ImageModelRegistry.MODELS["new-model"] = ImageModel(
        id="new-model",
        provider="wavespeed",
        model_path="wavespeed-ai/new-model",
        cost=0.05,  # From provider
        category="editing",
    )
    

Step 3: Use in Service (REUSES unified entry)

  • Call unified entry point (validation/tracking automatic)
    result = generate_image_edit(
        model="new-model",  # ✅ Just specify model ID
        image_base64=image,
        prompt=prompt,
        user_id=user_id,
    )
    

Key Reusability Points

  • No new validation code - reuses _validate_image_operation()
  • No new tracking code - reuses _track_image_operation_usage()
  • No new provider base - follows ImageEditProvider protocol
  • No new client code - reuses WaveSpeedClient
  • Consistent pattern - same as existing models

🔄 Reusability Quick Reference

Existing Code to Reuse

  • main_image_generation.py - Extend this file (don't create new)
  • ImageGenerationProvider protocol - Extend this pattern
  • WaveSpeedClient - Reuse for all WaveSpeed operations
  • Validation logic - Extract to helper
  • Tracking logic - Extract to helper

Pattern to Follow

# 1. Extract helpers from existing code
def _validate_image_operation(...):  # Extract from generate_image()
def _track_image_operation_usage(...):  # Extract from generate_image()

# 2. Extend existing file
def generate_image_edit(...):  # Add to main_image_generation.py
    _validate_image_operation(...)  # REUSE
    result = provider.edit(...)
    _track_image_operation_usage(...)  # REUSE
    return result

# 3. Extend provider protocol
class ImageEditProvider(Protocol):  # Add to base.py
    def edit(...) -> ImageGenerationResult: ...

# 4. Create provider following pattern
class WaveSpeedEditProvider(ImageEditProvider):
    def __init__(self):
        self.client = WaveSpeedClient()  # REUSE client
    
    def edit(...):
        return self.client.edit_image(...)  # REUSE client

Document Version: 2.0
Last Updated: Current Session
Status: Implementation Reference - Reusability Focus