@weirdfingers/baseboards 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +14 -4
  2. package/dist/index.js +13 -4
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/templates/api/ARTIFACT_RESOLUTION_GUIDE.md +148 -0
  6. package/templates/api/Dockerfile +2 -2
  7. package/templates/api/README.md +138 -6
  8. package/templates/api/config/generators.yaml +41 -7
  9. package/templates/api/docs/TESTING_LIVE_APIS.md +417 -0
  10. package/templates/api/pyproject.toml +49 -9
  11. package/templates/api/src/boards/__init__.py +1 -1
  12. package/templates/api/src/boards/auth/adapters/__init__.py +9 -2
  13. package/templates/api/src/boards/auth/factory.py +16 -2
  14. package/templates/api/src/boards/generators/__init__.py +2 -2
  15. package/templates/api/src/boards/generators/artifact_resolution.py +372 -0
  16. package/templates/api/src/boards/generators/artifacts.py +4 -4
  17. package/templates/api/src/boards/generators/base.py +8 -4
  18. package/templates/api/src/boards/generators/implementations/__init__.py +4 -2
  19. package/templates/api/src/boards/generators/implementations/fal/__init__.py +25 -0
  20. package/templates/api/src/boards/generators/implementations/fal/audio/__init__.py +4 -0
  21. package/templates/api/src/boards/generators/implementations/fal/audio/minimax_music_v2.py +173 -0
  22. package/templates/api/src/boards/generators/implementations/fal/audio/minimax_speech_2_6_turbo.py +221 -0
  23. package/templates/api/src/boards/generators/implementations/fal/image/__init__.py +17 -0
  24. package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_kontext.py +216 -0
  25. package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_ultra.py +197 -0
  26. package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview.py +191 -0
  27. package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview_fast.py +179 -0
  28. package/templates/api/src/boards/generators/implementations/fal/image/nano_banana.py +183 -0
  29. package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_edit.py +212 -0
  30. package/templates/api/src/boards/generators/implementations/fal/utils.py +61 -0
  31. package/templates/api/src/boards/generators/implementations/fal/video/__init__.py +13 -0
  32. package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_text_to_video.py +168 -0
  33. package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2.py +167 -0
  34. package/templates/api/src/boards/generators/implementations/fal/video/veo31_first_last_frame_to_video.py +180 -0
  35. package/templates/api/src/boards/generators/implementations/openai/__init__.py +1 -0
  36. package/templates/api/src/boards/generators/implementations/openai/audio/__init__.py +1 -0
  37. package/templates/api/src/boards/generators/implementations/{audio → openai/audio}/whisper.py +9 -6
  38. package/templates/api/src/boards/generators/implementations/openai/image/__init__.py +1 -0
  39. package/templates/api/src/boards/generators/implementations/{image → openai/image}/dalle3.py +8 -5
  40. package/templates/api/src/boards/generators/implementations/replicate/__init__.py +1 -0
  41. package/templates/api/src/boards/generators/implementations/replicate/image/__init__.py +1 -0
  42. package/templates/api/src/boards/generators/implementations/{image → replicate/image}/flux_pro.py +8 -5
  43. package/templates/api/src/boards/generators/implementations/replicate/video/__init__.py +1 -0
  44. package/templates/api/src/boards/generators/implementations/{video → replicate/video}/lipsync.py +9 -6
  45. package/templates/api/src/boards/generators/resolution.py +80 -20
  46. package/templates/api/src/boards/jobs/repository.py +49 -0
  47. package/templates/api/src/boards/storage/factory.py +16 -6
  48. package/templates/api/src/boards/workers/actors.py +69 -5
  49. package/templates/api/src/boards/workers/context.py +177 -21
  50. package/templates/web/package.json +2 -1
  51. package/templates/web/src/components/boards/GenerationInput.tsx +154 -52
  52. package/templates/web/src/components/boards/GeneratorSelector.tsx +57 -59
  53. package/templates/web/src/components/ui/dropdown-menu.tsx +200 -0
  54. package/templates/api/src/boards/generators/implementations/audio/__init__.py +0 -3
  55. package/templates/api/src/boards/generators/implementations/image/__init__.py +0 -3
  56. package/templates/api/src/boards/generators/implementations/video/__init__.py +0 -3
@@ -0,0 +1,197 @@
1
+ """
2
+ fal.ai FLUX1.1 [pro] ultra text-to-image generator.
3
+
4
+ High-quality image generation using fal.ai's FLUX1.1 [pro] ultra model with support for
5
+ batch outputs and advanced controls.
6
+ """
7
+
8
+ import os
9
+ from typing import Literal
10
+
11
+ from pydantic import BaseModel, Field
12
+
13
+ from ....base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
14
+
15
+
16
+ class FluxProUltraInput(BaseModel):
17
+ """Input schema for FLUX1.1 [pro] ultra image generation."""
18
+
19
+ prompt: str = Field(description="Text prompt for image generation")
20
+ aspect_ratio: Literal[
21
+ "21:9",
22
+ "16:9",
23
+ "4:3",
24
+ "3:2",
25
+ "1:1",
26
+ "2:3",
27
+ "3:4",
28
+ "9:16",
29
+ "9:21",
30
+ ] = Field(
31
+ default="16:9",
32
+ description="Image aspect ratio",
33
+ )
34
+ num_images: int = Field(
35
+ default=1,
36
+ ge=1,
37
+ le=4,
38
+ description="Number of images to generate in batch (max 4)",
39
+ )
40
+ enable_safety_checker: bool = Field(
41
+ default=True,
42
+ description="Enable safety checker to filter unsafe content",
43
+ )
44
+ safety_tolerance: int = Field(
45
+ default=2,
46
+ ge=1,
47
+ le=6,
48
+ description="Safety tolerance level (1 = most strict, 6 = most permissive)",
49
+ )
50
+ seed: int | None = Field(
51
+ default=None,
52
+ description="Random seed for reproducibility (optional)",
53
+ )
54
+ output_format: Literal["jpeg", "png"] = Field(
55
+ default="jpeg",
56
+ description="Output image format",
57
+ )
58
+ enhance_prompt: bool = Field(
59
+ default=False,
60
+ description="Whether to enhance the prompt for better results",
61
+ )
62
+ raw: bool = Field(
63
+ default=False,
64
+ description="Generate less processed, more natural-looking images",
65
+ )
66
+ sync_mode: bool = Field(
67
+ default=True,
68
+ description="Use synchronous mode (wait for completion)",
69
+ )
70
+
71
+
72
+ class FalFluxProUltraGenerator(BaseGenerator):
73
+ """FLUX1.1 [pro] ultra image generator using fal.ai."""
74
+
75
+ name = "fal-flux-pro-ultra"
76
+ artifact_type = "image"
77
+ description = (
78
+ "Fal: FLUX1.1 [pro] ultra - high-quality text-to-image generation with advanced controls"
79
+ )
80
+
81
+ def get_input_schema(self) -> type[FluxProUltraInput]:
82
+ return FluxProUltraInput
83
+
84
+ async def generate(
85
+ self, inputs: FluxProUltraInput, context: GeneratorExecutionContext
86
+ ) -> GeneratorResult:
87
+ """Generate images using fal.ai FLUX1.1 [pro] ultra model."""
88
+ # Check for API key (fal-client uses FAL_KEY environment variable)
89
+ if not os.getenv("FAL_KEY"):
90
+ raise ValueError("API configuration invalid. Missing FAL_KEY environment variable")
91
+
92
+ # Import fal_client
93
+ try:
94
+ import fal_client
95
+ except ImportError as e:
96
+ raise ImportError(
97
+ "fal.ai SDK is required for FalFluxProUltraGenerator. "
98
+ "Install with: pip install weirdfingers-boards[generators-fal]"
99
+ ) from e
100
+
101
+ # Prepare arguments for fal.ai API
102
+ arguments = {
103
+ "prompt": inputs.prompt,
104
+ "aspect_ratio": inputs.aspect_ratio,
105
+ "num_images": inputs.num_images,
106
+ "enable_safety_checker": inputs.enable_safety_checker,
107
+ "safety_tolerance": inputs.safety_tolerance,
108
+ "output_format": inputs.output_format,
109
+ "enhance_prompt": inputs.enhance_prompt,
110
+ "raw": inputs.raw,
111
+ "sync_mode": inputs.sync_mode,
112
+ }
113
+
114
+ # Add seed if provided
115
+ if inputs.seed is not None:
116
+ arguments["seed"] = inputs.seed
117
+
118
+ # Submit async job and get handler
119
+ handler = await fal_client.submit_async(
120
+ "fal-ai/flux-pro/v1.1-ultra",
121
+ arguments=arguments,
122
+ )
123
+
124
+ # Store the external job ID for tracking
125
+ await context.set_external_job_id(handler.request_id)
126
+
127
+ # Stream progress updates (sample every 3rd event to avoid spam)
128
+ from .....progress.models import ProgressUpdate
129
+
130
+ event_count = 0
131
+ async for event in handler.iter_events(with_logs=True):
132
+ event_count += 1
133
+
134
+ # Process every 3rd event to provide feedback without overwhelming
135
+ if event_count % 3 == 0:
136
+ # Extract logs if available
137
+ logs = getattr(event, "logs", None)
138
+ if logs:
139
+ # Join log entries into a single message
140
+ if isinstance(logs, list):
141
+ message = " | ".join(str(log) for log in logs if log)
142
+ else:
143
+ message = str(logs)
144
+
145
+ if message:
146
+ await context.publish_progress(
147
+ ProgressUpdate(
148
+ job_id=handler.request_id,
149
+ status="processing",
150
+ progress=50.0, # Approximate mid-point progress
151
+ phase="processing",
152
+ message=message,
153
+ )
154
+ )
155
+
156
+ # Get final result
157
+ result = await handler.get()
158
+
159
+ # Extract image URLs from result
160
+ # fal.ai returns: {"images": [{"url": "...", "width": ..., "height": ...}, ...]}
161
+ images = result.get("images", [])
162
+ if not images:
163
+ raise ValueError("No images returned from fal.ai API")
164
+
165
+ # Store each image using output_index
166
+ artifacts = []
167
+ for idx, image_data in enumerate(images):
168
+ image_url = image_data.get("url")
169
+ width = image_data.get("width", 1024)
170
+ height = image_data.get("height", 1024)
171
+
172
+ if not image_url:
173
+ raise ValueError(f"Image {idx} missing URL in fal.ai response")
174
+
175
+ # Store with appropriate output_index
176
+ artifact = await context.store_image_result(
177
+ storage_url=image_url,
178
+ format=inputs.output_format,
179
+ width=width,
180
+ height=height,
181
+ output_index=idx,
182
+ )
183
+ artifacts.append(artifact)
184
+
185
+ return GeneratorResult(outputs=artifacts)
186
+
187
+ async def estimate_cost(self, inputs: FluxProUltraInput) -> float:
188
+ """Estimate cost for FLUX1.1 [pro] ultra generation.
189
+
190
+ FLUX1.1 [pro] ultra billing is based on megapixels (rounded up).
191
+ The aspect ratios map to different resolutions, all roughly in the 2-4MP range.
192
+ Estimated at approximately $0.04 per image based on typical resolutions.
193
+ """
194
+ # Approximate cost per image (varies slightly by resolution/megapixels)
195
+ # Most aspect ratios result in 2-4 megapixels
196
+ cost_per_image = 0.04
197
+ return cost_per_image * inputs.num_images
@@ -0,0 +1,191 @@
1
+ """
2
+ fal.ai Imagen 4 Preview text-to-image generator.
3
+
4
+ Google's highest quality image generation model with support for multiple aspect ratios
5
+ and resolutions.
6
+ """
7
+
8
+ import os
9
+ from typing import Literal
10
+
11
+ from pydantic import BaseModel, Field
12
+
13
+ from ....base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
14
+
15
+
16
+ class Imagen4PreviewInput(BaseModel):
17
+ """Input schema for Imagen 4 Preview image generation."""
18
+
19
+ prompt: str = Field(description="Text description of desired image")
20
+ aspect_ratio: Literal["1:1", "16:9", "9:16", "3:4", "4:3"] = Field(
21
+ default="1:1",
22
+ description="Image aspect ratio",
23
+ )
24
+ resolution: Literal["1K", "2K"] = Field(
25
+ default="1K",
26
+ description="Image resolution (1K or 2K)",
27
+ )
28
+ num_images: int = Field(
29
+ default=1,
30
+ ge=1,
31
+ le=4,
32
+ description="Number of images to generate (max 4)",
33
+ )
34
+ seed: int | None = Field(
35
+ default=None,
36
+ description="Random seed for reproducibility (optional)",
37
+ )
38
+ negative_prompt: str = Field(
39
+ default="",
40
+ description="Content to exclude from generation",
41
+ )
42
+
43
+
44
+ class FalImagen4PreviewGenerator(BaseGenerator):
45
+ """Imagen 4 Preview image generator using fal.ai."""
46
+
47
+ name = "fal-imagen4-preview"
48
+ artifact_type = "image"
49
+ description = "Fal: Imagen 4 - Google's highest quality text-to-image generation model"
50
+
51
+ def get_input_schema(self) -> type[Imagen4PreviewInput]:
52
+ return Imagen4PreviewInput
53
+
54
+ def _calculate_dimensions(self, aspect_ratio: str, resolution: str) -> tuple[int, int]:
55
+ """Calculate image dimensions based on aspect ratio and resolution.
56
+
57
+ Args:
58
+ aspect_ratio: Image aspect ratio (e.g., "16:9")
59
+ resolution: Image resolution ("1K" or "2K")
60
+
61
+ Returns:
62
+ Tuple of (width, height)
63
+ """
64
+ # Base size for each resolution
65
+ base_size = 1024 if resolution == "1K" else 2048
66
+
67
+ # Dimension mapping for each aspect ratio
68
+ dimensions = {
69
+ "1:1": (base_size, base_size),
70
+ "16:9": (base_size, int(base_size * 9 / 16)),
71
+ "9:16": (int(base_size * 9 / 16), base_size),
72
+ "4:3": (base_size, int(base_size * 3 / 4)),
73
+ "3:4": (int(base_size * 3 / 4), base_size),
74
+ }
75
+
76
+ return dimensions.get(aspect_ratio, (base_size, base_size))
77
+
78
+ async def generate(
79
+ self, inputs: Imagen4PreviewInput, context: GeneratorExecutionContext
80
+ ) -> GeneratorResult:
81
+ """Generate images using fal.ai Imagen 4 Preview model."""
82
+ # Check for API key (fal-client uses FAL_KEY environment variable)
83
+ if not os.getenv("FAL_KEY"):
84
+ raise ValueError("API configuration invalid. Missing FAL_KEY environment variable")
85
+
86
+ # Import fal_client
87
+ try:
88
+ import fal_client
89
+ except ImportError as e:
90
+ raise ImportError(
91
+ "fal.ai SDK is required for FalImagen4PreviewGenerator. "
92
+ "Install with: pip install weirdfingers-boards[generators-fal]"
93
+ ) from e
94
+
95
+ # Prepare arguments for fal.ai API
96
+ arguments = {
97
+ "prompt": inputs.prompt,
98
+ "aspect_ratio": inputs.aspect_ratio,
99
+ "resolution": inputs.resolution,
100
+ "num_images": inputs.num_images,
101
+ "negative_prompt": inputs.negative_prompt,
102
+ }
103
+
104
+ # Add seed if provided
105
+ if inputs.seed is not None:
106
+ arguments["seed"] = inputs.seed
107
+
108
+ # Submit async job and get handler
109
+ handler = await fal_client.submit_async(
110
+ "fal-ai/imagen4/preview",
111
+ arguments=arguments,
112
+ )
113
+
114
+ # Store the external job ID for tracking
115
+ await context.set_external_job_id(handler.request_id)
116
+
117
+ # Stream progress updates (sample every 3rd event to avoid spam)
118
+ from .....progress.models import ProgressUpdate
119
+
120
+ event_count = 0
121
+ async for event in handler.iter_events(with_logs=True):
122
+ event_count += 1
123
+
124
+ # Process every 3rd event to provide feedback without overwhelming
125
+ if event_count % 3 == 0:
126
+ # Extract logs if available
127
+ logs = getattr(event, "logs", None)
128
+ if logs:
129
+ # Join log entries into a single message
130
+ if isinstance(logs, list):
131
+ message = " | ".join(str(log) for log in logs if log)
132
+ else:
133
+ message = str(logs)
134
+
135
+ if message:
136
+ await context.publish_progress(
137
+ ProgressUpdate(
138
+ job_id=handler.request_id,
139
+ status="processing",
140
+ progress=50.0, # Approximate mid-point progress
141
+ phase="processing",
142
+ message=message,
143
+ )
144
+ )
145
+
146
+ # Get final result
147
+ result = await handler.get()
148
+
149
+ # Extract image URLs from result
150
+ # fal.ai returns: {"images": [{"url": "...", "content_type": "...", ...}, ...]}
151
+ images = result.get("images", [])
152
+ if not images:
153
+ raise ValueError("No images returned from fal.ai API")
154
+
155
+ # Calculate dimensions based on inputs
156
+ width, height = self._calculate_dimensions(inputs.aspect_ratio, inputs.resolution)
157
+
158
+ # Store each image using output_index
159
+ artifacts = []
160
+ for idx, image_data in enumerate(images):
161
+ image_url = image_data.get("url")
162
+
163
+ if not image_url:
164
+ raise ValueError(f"Image {idx} missing URL in fal.ai response")
165
+
166
+ # Detect format from content_type or URL
167
+ content_type = image_data.get("content_type", "")
168
+ if "png" in content_type.lower():
169
+ format = "png"
170
+ else:
171
+ format = "jpeg"
172
+
173
+ # Store with appropriate output_index
174
+ artifact = await context.store_image_result(
175
+ storage_url=image_url,
176
+ format=format,
177
+ width=width,
178
+ height=height,
179
+ output_index=idx,
180
+ )
181
+ artifacts.append(artifact)
182
+
183
+ return GeneratorResult(outputs=artifacts)
184
+
185
+ async def estimate_cost(self, inputs: Imagen4PreviewInput) -> float:
186
+ """Estimate cost for Imagen 4 Preview generation.
187
+
188
+ Imagen 4 Preview costs $0.04 per image.
189
+ """
190
+ cost_per_image = 0.04
191
+ return cost_per_image * inputs.num_images
@@ -0,0 +1,179 @@
1
+ """
2
+ Google Imagen 4 fast text-to-image generator.
3
+
4
+ Google's highest quality image generation model with support for multiple aspect ratios
5
+ and batch generation.
6
+
7
+ Based on Fal AI's fal-ai/imagen4/preview/fast model.
8
+ See: https://fal.ai/models/fal-ai/imagen4/preview/fast
9
+ """
10
+
11
+ import os
12
+ from typing import Literal
13
+
14
+ from pydantic import BaseModel, Field
15
+
16
+ from ....base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
17
+
18
+
19
+ class Imagen4PreviewFastInput(BaseModel):
20
+ """Input schema for Google Imagen 4 fast image generation.
21
+
22
+ All parameters are simple types - no artifact inputs needed.
23
+ """
24
+
25
+ prompt: str = Field(description="The text prompt describing what you want to see")
26
+ negative_prompt: str = Field(
27
+ default="",
28
+ description="Discourage specific elements in generated images",
29
+ )
30
+ aspect_ratio: Literal["1:1", "16:9", "9:16", "3:4", "4:3"] = Field(
31
+ default="1:1",
32
+ description="Image aspect ratio",
33
+ )
34
+ num_images: int = Field(
35
+ default=1,
36
+ ge=1,
37
+ le=4,
38
+ description="Number of images to generate (1-4)",
39
+ )
40
+ seed: int | None = Field(
41
+ default=None,
42
+ description="Random seed for reproducible generation (optional)",
43
+ )
44
+
45
+
46
+ class FalImagen4PreviewFastGenerator(BaseGenerator):
47
+ """Google Imagen 4 fast image generator using fal.ai."""
48
+
49
+ name = "fal-imagen4-preview-fast"
50
+ artifact_type = "image"
51
+ description = "Fal: Google Imagen 4 - highest quality text-to-image generation"
52
+
53
+ def get_input_schema(self) -> type[Imagen4PreviewFastInput]:
54
+ return Imagen4PreviewFastInput
55
+
56
+ async def generate(
57
+ self, inputs: Imagen4PreviewFastInput, context: GeneratorExecutionContext
58
+ ) -> GeneratorResult:
59
+ """Generate images using Google Imagen 4 via fal.ai."""
60
+ # Check for API key (fal-client uses FAL_KEY environment variable)
61
+ if not os.getenv("FAL_KEY"):
62
+ raise ValueError("API configuration invalid. Missing FAL_KEY environment variable")
63
+
64
+ # Import fal_client
65
+ try:
66
+ import fal_client
67
+ except ImportError as e:
68
+ raise ImportError(
69
+ "fal.ai SDK is required for FalImagen4PreviewFastGenerator. "
70
+ "Install with: pip install weirdfingers-boards[generators-fal]"
71
+ ) from e
72
+
73
+ # Prepare arguments for fal.ai API
74
+ arguments = {
75
+ "prompt": inputs.prompt,
76
+ "negative_prompt": inputs.negative_prompt,
77
+ "aspect_ratio": inputs.aspect_ratio,
78
+ "num_images": inputs.num_images,
79
+ }
80
+
81
+ # Add seed if provided
82
+ if inputs.seed is not None:
83
+ arguments["seed"] = inputs.seed
84
+
85
+ # Submit async job and get handler
86
+ handler = await fal_client.submit_async(
87
+ "fal-ai/imagen4/preview/fast",
88
+ arguments=arguments,
89
+ )
90
+
91
+ # Store the external job ID for tracking
92
+ await context.set_external_job_id(handler.request_id)
93
+
94
+ # Stream progress updates (sample every 3rd event to avoid spam)
95
+ from .....progress.models import ProgressUpdate
96
+
97
+ event_count = 0
98
+ async for event in handler.iter_events(with_logs=True):
99
+ event_count += 1
100
+
101
+ # Process every 3rd event to provide feedback without overwhelming
102
+ if event_count % 3 == 0:
103
+ # Extract logs if available
104
+ logs = getattr(event, "logs", None)
105
+ if logs:
106
+ # Join log entries into a single message
107
+ if isinstance(logs, list):
108
+ message = " | ".join(str(log) for log in logs if log)
109
+ else:
110
+ message = str(logs)
111
+
112
+ if message:
113
+ await context.publish_progress(
114
+ ProgressUpdate(
115
+ job_id=handler.request_id,
116
+ status="processing",
117
+ progress=50.0, # Approximate mid-point progress
118
+ phase="processing",
119
+ message=message,
120
+ )
121
+ )
122
+
123
+ # Get final result
124
+ result = await handler.get()
125
+
126
+ # Extract image URLs from result
127
+ # fal.ai returns: {"images": [{"url": "...", "content_type": "...", ...}, ...], "seed": ...}
128
+ images = result.get("images", [])
129
+ if not images:
130
+ raise ValueError("No images returned from fal.ai API")
131
+
132
+ # Store each image using output_index
133
+ artifacts = []
134
+ for idx, image_data in enumerate(images):
135
+ image_url = image_data.get("url")
136
+
137
+ if not image_url:
138
+ raise ValueError(f"Image {idx} missing URL in fal.ai response")
139
+
140
+ # Imagen 4 returns PNG images
141
+ # We don't have width/height in the response, so use defaults based on aspect ratio
142
+ # This is a simplification - actual dimensions may vary
143
+ width, height = self._get_dimensions_for_aspect_ratio(inputs.aspect_ratio)
144
+
145
+ # Store with appropriate output_index
146
+ artifact = await context.store_image_result(
147
+ storage_url=image_url,
148
+ format="png",
149
+ width=width,
150
+ height=height,
151
+ output_index=idx,
152
+ )
153
+ artifacts.append(artifact)
154
+
155
+ return GeneratorResult(outputs=artifacts)
156
+
157
+ def _get_dimensions_for_aspect_ratio(self, aspect_ratio: str) -> tuple[int, int]:
158
+ """Get approximate dimensions for a given aspect ratio.
159
+
160
+ Returns (width, height) tuple. Uses 1024 as base for 1:1 ratio.
161
+ """
162
+ aspect_map = {
163
+ "1:1": (1024, 1024),
164
+ "16:9": (1360, 768),
165
+ "9:16": (768, 1360),
166
+ "3:4": (896, 1152),
167
+ "4:3": (1152, 896),
168
+ }
169
+ return aspect_map.get(aspect_ratio, (1024, 1024))
170
+
171
+ async def estimate_cost(self, inputs: Imagen4PreviewFastInput) -> float:
172
+ """Estimate cost for Google Imagen 4 generation.
173
+
174
+ Pricing information was not available in the documentation.
175
+ Using estimated cost of $0.04 per image based on similar high-quality models.
176
+ """
177
+ # Estimated cost per image (actual pricing may vary)
178
+ cost_per_image = 0.04
179
+ return cost_per_image * inputs.num_images