ghmk's picture
Initial deployment of Character Forge
5b6e956
"""
Generation Request Model
========================
Data model for image generation requests.
Provides type-safe structure for all generation parameters.
"""
from dataclasses import dataclass, field
from typing import List, Optional
from PIL import Image
@dataclass
class GenerationRequest:
"""
Represents a request for image generation.
This is the standard interface for all generation requests,
regardless of backend or generation type.
Attributes:
prompt: Text prompt describing desired image
backend: Backend to use ("Gemini API (Cloud)" or "OmniGen2 (Local)")
aspect_ratio: Aspect ratio (e.g. "16:9", "3:4")
temperature: Temperature/creativity parameter (0.0-1.0)
input_images: Optional list of input images for image-to-image
is_character_sheet: Mark input images as character sheets
metadata: Additional metadata for tracking
"""
# Required fields
prompt: str
backend: str
aspect_ratio: str
temperature: float
# Optional fields
input_images: Optional[List[Image.Image]] = None
is_character_sheet: List[bool] = field(default_factory=list)
metadata: dict = field(default_factory=dict)
negative_prompt: Optional[str] = None
seed: Optional[int] = None
def __post_init__(self):
"""Validate and normalize data after initialization."""
# Ensure prompt is string
if not isinstance(self.prompt, str):
raise TypeError("prompt must be a string")
# Strip whitespace from prompt
self.prompt = self.prompt.strip()
# Ensure temperature is numeric
if not isinstance(self.temperature, (int, float)):
raise TypeError("temperature must be numeric")
# Convert temperature to float
self.temperature = float(self.temperature)
# Normalize input_images (convert None to empty list)
if self.input_images is None:
self.input_images = []
# Ensure is_character_sheet matches input_images length
if len(self.is_character_sheet) < len(self.input_images):
# Pad with False
self.is_character_sheet.extend(
[False] * (len(self.input_images) - len(self.is_character_sheet))
)
@property
def has_input_images(self) -> bool:
"""Check if request has input images."""
return self.input_images is not None and len(self.input_images) > 0
@property
def image_count(self) -> int:
"""Get number of input images."""
return len(self.input_images) if self.input_images else 0
@property
def is_text_to_image(self) -> bool:
"""Check if this is a text-to-image request (no input images)."""
return not self.has_input_images
@property
def is_image_to_image(self) -> bool:
"""Check if this is an image-to-image request (has input images)."""
return self.has_input_images
def to_dict(self) -> dict:
"""
Convert request to dictionary (for metadata/logging).
Note: Images are not included in dict (only count).
Returns:
Dictionary representation
"""
return {
"prompt": self.prompt,
"backend": self.backend,
"aspect_ratio": self.aspect_ratio,
"temperature": self.temperature,
"input_image_count": self.image_count,
"is_character_sheet": self.is_character_sheet,
"negative_prompt": self.negative_prompt,
"seed": self.seed,
"metadata": self.metadata
}
def __repr__(self) -> str:
"""String representation for debugging."""
return (
f"GenerationRequest("
f"prompt='{self.prompt[:50]}...', "
f"backend='{self.backend}', "
f"aspect_ratio='{self.aspect_ratio}', "
f"temperature={self.temperature}, "
f"input_images={self.image_count})"
)