@weirdfingers/baseboards 0.9.6 → 0.9.7

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 (237) hide show
  1. package/dist/index.js +560 -469
  2. package/dist/index.js.map +1 -1
  3. package/package.json +2 -5
  4. package/templates/README.md +0 -122
  5. package/templates/api/.env.example +0 -65
  6. package/templates/api/ARTIFACT_RESOLUTION_GUIDE.md +0 -148
  7. package/templates/api/Dockerfile +0 -32
  8. package/templates/api/README.md +0 -264
  9. package/templates/api/alembic/env.py +0 -114
  10. package/templates/api/alembic/script.py.mako +0 -28
  11. package/templates/api/alembic/versions/20250101_000000_initial_schema.py +0 -506
  12. package/templates/api/alembic/versions/20251022_174729_remove_provider_name_from_generations.py +0 -75
  13. package/templates/api/alembic/versions/20251023_165852_switch_to_declarative_base_and_mapping.py +0 -467
  14. package/templates/api/alembic/versions/20251202_000000_add_artifact_lineage.py +0 -134
  15. package/templates/api/alembic/versions/2025925_62735_add_seed_data_for_default_tenant.py +0 -88
  16. package/templates/api/alembic.ini +0 -36
  17. package/templates/api/config/generators.yaml +0 -237
  18. package/templates/api/config/storage_config.yaml +0 -26
  19. package/templates/api/docs/ADDING_GENERATORS.md +0 -409
  20. package/templates/api/docs/GENERATORS_API.md +0 -502
  21. package/templates/api/docs/MIGRATIONS.md +0 -472
  22. package/templates/api/docs/TESTING_LIVE_APIS.md +0 -417
  23. package/templates/api/docs/storage_providers.md +0 -337
  24. package/templates/api/pyproject.toml +0 -205
  25. package/templates/api/src/boards/__init__.py +0 -10
  26. package/templates/api/src/boards/api/app.py +0 -172
  27. package/templates/api/src/boards/api/auth.py +0 -75
  28. package/templates/api/src/boards/api/endpoints/__init__.py +0 -3
  29. package/templates/api/src/boards/api/endpoints/jobs.py +0 -76
  30. package/templates/api/src/boards/api/endpoints/setup.py +0 -505
  31. package/templates/api/src/boards/api/endpoints/sse.py +0 -129
  32. package/templates/api/src/boards/api/endpoints/storage.py +0 -155
  33. package/templates/api/src/boards/api/endpoints/tenant_registration.py +0 -296
  34. package/templates/api/src/boards/api/endpoints/uploads.py +0 -149
  35. package/templates/api/src/boards/api/endpoints/webhooks.py +0 -13
  36. package/templates/api/src/boards/auth/__init__.py +0 -15
  37. package/templates/api/src/boards/auth/adapters/__init__.py +0 -27
  38. package/templates/api/src/boards/auth/adapters/auth0.py +0 -220
  39. package/templates/api/src/boards/auth/adapters/base.py +0 -73
  40. package/templates/api/src/boards/auth/adapters/clerk.py +0 -172
  41. package/templates/api/src/boards/auth/adapters/jwt.py +0 -122
  42. package/templates/api/src/boards/auth/adapters/none.py +0 -102
  43. package/templates/api/src/boards/auth/adapters/oidc.py +0 -284
  44. package/templates/api/src/boards/auth/adapters/supabase.py +0 -110
  45. package/templates/api/src/boards/auth/context.py +0 -35
  46. package/templates/api/src/boards/auth/factory.py +0 -129
  47. package/templates/api/src/boards/auth/middleware.py +0 -221
  48. package/templates/api/src/boards/auth/provisioning.py +0 -129
  49. package/templates/api/src/boards/auth/tenant_extraction.py +0 -278
  50. package/templates/api/src/boards/cli.py +0 -354
  51. package/templates/api/src/boards/config.py +0 -131
  52. package/templates/api/src/boards/database/__init__.py +0 -7
  53. package/templates/api/src/boards/database/cli.py +0 -110
  54. package/templates/api/src/boards/database/connection.py +0 -292
  55. package/templates/api/src/boards/database/models.py +0 -19
  56. package/templates/api/src/boards/database/seed_data.py +0 -182
  57. package/templates/api/src/boards/dbmodels/__init__.py +0 -441
  58. package/templates/api/src/boards/generators/__init__.py +0 -57
  59. package/templates/api/src/boards/generators/artifact_resolution.py +0 -405
  60. package/templates/api/src/boards/generators/artifacts.py +0 -53
  61. package/templates/api/src/boards/generators/base.py +0 -144
  62. package/templates/api/src/boards/generators/implementations/__init__.py +0 -14
  63. package/templates/api/src/boards/generators/implementations/fal/__init__.py +0 -25
  64. package/templates/api/src/boards/generators/implementations/fal/audio/__init__.py +0 -23
  65. package/templates/api/src/boards/generators/implementations/fal/audio/beatoven_music_generation.py +0 -171
  66. package/templates/api/src/boards/generators/implementations/fal/audio/beatoven_sound_effect_generation.py +0 -167
  67. package/templates/api/src/boards/generators/implementations/fal/audio/chatterbox_text_to_speech.py +0 -176
  68. package/templates/api/src/boards/generators/implementations/fal/audio/chatterbox_tts_turbo.py +0 -195
  69. package/templates/api/src/boards/generators/implementations/fal/audio/elevenlabs_sound_effects_v2.py +0 -194
  70. package/templates/api/src/boards/generators/implementations/fal/audio/elevenlabs_tts_eleven_v3.py +0 -209
  71. package/templates/api/src/boards/generators/implementations/fal/audio/fal_elevenlabs_tts_turbo_v2_5.py +0 -206
  72. package/templates/api/src/boards/generators/implementations/fal/audio/fal_minimax_speech_26_hd.py +0 -237
  73. package/templates/api/src/boards/generators/implementations/fal/audio/minimax_music_v2.py +0 -173
  74. package/templates/api/src/boards/generators/implementations/fal/audio/minimax_speech_2_6_turbo.py +0 -221
  75. package/templates/api/src/boards/generators/implementations/fal/image/__init__.py +0 -63
  76. package/templates/api/src/boards/generators/implementations/fal/image/bytedance_seedream_v45_edit.py +0 -219
  77. package/templates/api/src/boards/generators/implementations/fal/image/clarity_upscaler.py +0 -220
  78. package/templates/api/src/boards/generators/implementations/fal/image/crystal_upscaler.py +0 -173
  79. package/templates/api/src/boards/generators/implementations/fal/image/fal_ideogram_character.py +0 -227
  80. package/templates/api/src/boards/generators/implementations/fal/image/flux_2.py +0 -203
  81. package/templates/api/src/boards/generators/implementations/fal/image/flux_2_edit.py +0 -230
  82. package/templates/api/src/boards/generators/implementations/fal/image/flux_2_pro.py +0 -204
  83. package/templates/api/src/boards/generators/implementations/fal/image/flux_2_pro_edit.py +0 -221
  84. package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_kontext.py +0 -216
  85. package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_ultra.py +0 -197
  86. package/templates/api/src/boards/generators/implementations/fal/image/gemini_25_flash_image.py +0 -177
  87. package/templates/api/src/boards/generators/implementations/fal/image/gemini_25_flash_image_edit.py +0 -208
  88. package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_15_edit.py +0 -216
  89. package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_5.py +0 -177
  90. package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_edit_image.py +0 -182
  91. package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_mini.py +0 -167
  92. package/templates/api/src/boards/generators/implementations/fal/image/ideogram_character_edit.py +0 -299
  93. package/templates/api/src/boards/generators/implementations/fal/image/ideogram_v2.py +0 -190
  94. package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview.py +0 -191
  95. package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview_fast.py +0 -179
  96. package/templates/api/src/boards/generators/implementations/fal/image/nano_banana.py +0 -183
  97. package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_edit.py +0 -212
  98. package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_pro.py +0 -179
  99. package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_pro_edit.py +0 -226
  100. package/templates/api/src/boards/generators/implementations/fal/image/qwen_image.py +0 -249
  101. package/templates/api/src/boards/generators/implementations/fal/image/qwen_image_edit.py +0 -244
  102. package/templates/api/src/boards/generators/implementations/fal/image/reve_edit.py +0 -178
  103. package/templates/api/src/boards/generators/implementations/fal/image/reve_text_to_image.py +0 -155
  104. package/templates/api/src/boards/generators/implementations/fal/image/seedream_v45_text_to_image.py +0 -180
  105. package/templates/api/src/boards/generators/implementations/fal/utils.py +0 -61
  106. package/templates/api/src/boards/generators/implementations/fal/video/__init__.py +0 -77
  107. package/templates/api/src/boards/generators/implementations/fal/video/bytedance_seedance_v1_pro_text_to_video.py +0 -209
  108. package/templates/api/src/boards/generators/implementations/fal/video/creatify_lipsync.py +0 -161
  109. package/templates/api/src/boards/generators/implementations/fal/video/fal_bytedance_seedance_v1_pro_image_to_video.py +0 -222
  110. package/templates/api/src/boards/generators/implementations/fal/video/fal_minimax_hailuo_02_standard_text_to_video.py +0 -152
  111. package/templates/api/src/boards/generators/implementations/fal/video/fal_pixverse_lipsync.py +0 -197
  112. package/templates/api/src/boards/generators/implementations/fal/video/fal_sora_2_text_to_video.py +0 -173
  113. package/templates/api/src/boards/generators/implementations/fal/video/infinitalk.py +0 -221
  114. package/templates/api/src/boards/generators/implementations/fal/video/kling_video_ai_avatar_v2_pro.py +0 -168
  115. package/templates/api/src/boards/generators/implementations/fal/video/kling_video_ai_avatar_v2_standard.py +0 -159
  116. package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_image_to_video.py +0 -175
  117. package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_text_to_video.py +0 -168
  118. package/templates/api/src/boards/generators/implementations/fal/video/minimax_hailuo_2_3_pro_image_to_video.py +0 -153
  119. package/templates/api/src/boards/generators/implementations/fal/video/sora2_image_to_video.py +0 -172
  120. package/templates/api/src/boards/generators/implementations/fal/video/sora_2_image_to_video_pro.py +0 -175
  121. package/templates/api/src/boards/generators/implementations/fal/video/sora_2_text_to_video_pro.py +0 -163
  122. package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2.py +0 -167
  123. package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2_pro.py +0 -155
  124. package/templates/api/src/boards/generators/implementations/fal/video/veed_fabric_1_0.py +0 -180
  125. package/templates/api/src/boards/generators/implementations/fal/video/veed_lipsync.py +0 -174
  126. package/templates/api/src/boards/generators/implementations/fal/video/veo3.py +0 -194
  127. package/templates/api/src/boards/generators/implementations/fal/video/veo31.py +0 -190
  128. package/templates/api/src/boards/generators/implementations/fal/video/veo31_fast.py +0 -190
  129. package/templates/api/src/boards/generators/implementations/fal/video/veo31_fast_image_to_video.py +0 -191
  130. package/templates/api/src/boards/generators/implementations/fal/video/veo31_first_last_frame_to_video.py +0 -187
  131. package/templates/api/src/boards/generators/implementations/fal/video/veo31_image_to_video.py +0 -183
  132. package/templates/api/src/boards/generators/implementations/fal/video/veo31_reference_to_video.py +0 -172
  133. package/templates/api/src/boards/generators/implementations/fal/video/wan_25_preview_image_to_video.py +0 -212
  134. package/templates/api/src/boards/generators/implementations/fal/video/wan_25_preview_text_to_video.py +0 -208
  135. package/templates/api/src/boards/generators/implementations/fal/video/wan_pro_image_to_video.py +0 -158
  136. package/templates/api/src/boards/generators/implementations/kie/__init__.py +0 -11
  137. package/templates/api/src/boards/generators/implementations/kie/base.py +0 -316
  138. package/templates/api/src/boards/generators/implementations/kie/image/__init__.py +0 -3
  139. package/templates/api/src/boards/generators/implementations/kie/image/nano_banana_edit.py +0 -190
  140. package/templates/api/src/boards/generators/implementations/kie/utils.py +0 -98
  141. package/templates/api/src/boards/generators/implementations/kie/video/__init__.py +0 -8
  142. package/templates/api/src/boards/generators/implementations/kie/video/veo3.py +0 -161
  143. package/templates/api/src/boards/generators/implementations/openai/__init__.py +0 -1
  144. package/templates/api/src/boards/generators/implementations/openai/audio/__init__.py +0 -1
  145. package/templates/api/src/boards/generators/implementations/openai/audio/whisper.py +0 -69
  146. package/templates/api/src/boards/generators/implementations/openai/image/__init__.py +0 -1
  147. package/templates/api/src/boards/generators/implementations/openai/image/dalle3.py +0 -96
  148. package/templates/api/src/boards/generators/implementations/replicate/__init__.py +0 -1
  149. package/templates/api/src/boards/generators/implementations/replicate/image/__init__.py +0 -1
  150. package/templates/api/src/boards/generators/implementations/replicate/image/flux_pro.py +0 -88
  151. package/templates/api/src/boards/generators/implementations/replicate/video/__init__.py +0 -1
  152. package/templates/api/src/boards/generators/implementations/replicate/video/lipsync.py +0 -73
  153. package/templates/api/src/boards/generators/loader.py +0 -253
  154. package/templates/api/src/boards/generators/registry.py +0 -114
  155. package/templates/api/src/boards/generators/resolution.py +0 -632
  156. package/templates/api/src/boards/generators/testmods/class_gen.py +0 -34
  157. package/templates/api/src/boards/generators/testmods/import_side_effect.py +0 -35
  158. package/templates/api/src/boards/graphql/__init__.py +0 -7
  159. package/templates/api/src/boards/graphql/access_control.py +0 -136
  160. package/templates/api/src/boards/graphql/mutations/root.py +0 -148
  161. package/templates/api/src/boards/graphql/queries/root.py +0 -116
  162. package/templates/api/src/boards/graphql/resolvers/__init__.py +0 -8
  163. package/templates/api/src/boards/graphql/resolvers/auth.py +0 -12
  164. package/templates/api/src/boards/graphql/resolvers/board.py +0 -1053
  165. package/templates/api/src/boards/graphql/resolvers/generation.py +0 -666
  166. package/templates/api/src/boards/graphql/resolvers/generator.py +0 -50
  167. package/templates/api/src/boards/graphql/resolvers/lineage.py +0 -381
  168. package/templates/api/src/boards/graphql/resolvers/upload.py +0 -463
  169. package/templates/api/src/boards/graphql/resolvers/user.py +0 -25
  170. package/templates/api/src/boards/graphql/schema.py +0 -81
  171. package/templates/api/src/boards/graphql/types/board.py +0 -102
  172. package/templates/api/src/boards/graphql/types/generation.py +0 -166
  173. package/templates/api/src/boards/graphql/types/generator.py +0 -17
  174. package/templates/api/src/boards/graphql/types/user.py +0 -47
  175. package/templates/api/src/boards/jobs/repository.py +0 -153
  176. package/templates/api/src/boards/logging.py +0 -195
  177. package/templates/api/src/boards/middleware.py +0 -339
  178. package/templates/api/src/boards/progress/__init__.py +0 -4
  179. package/templates/api/src/boards/progress/models.py +0 -25
  180. package/templates/api/src/boards/progress/publisher.py +0 -64
  181. package/templates/api/src/boards/py.typed +0 -0
  182. package/templates/api/src/boards/redis_pool.py +0 -118
  183. package/templates/api/src/boards/storage/__init__.py +0 -52
  184. package/templates/api/src/boards/storage/base.py +0 -363
  185. package/templates/api/src/boards/storage/config.py +0 -187
  186. package/templates/api/src/boards/storage/factory.py +0 -288
  187. package/templates/api/src/boards/storage/implementations/__init__.py +0 -27
  188. package/templates/api/src/boards/storage/implementations/gcs.py +0 -340
  189. package/templates/api/src/boards/storage/implementations/local.py +0 -201
  190. package/templates/api/src/boards/storage/implementations/s3.py +0 -294
  191. package/templates/api/src/boards/storage/implementations/supabase.py +0 -218
  192. package/templates/api/src/boards/tenant_isolation.py +0 -446
  193. package/templates/api/src/boards/validation.py +0 -262
  194. package/templates/api/src/boards/workers/__init__.py +0 -1
  195. package/templates/api/src/boards/workers/actors.py +0 -274
  196. package/templates/api/src/boards/workers/cli.py +0 -125
  197. package/templates/api/src/boards/workers/context.py +0 -348
  198. package/templates/api/src/boards/workers/middleware.py +0 -58
  199. package/templates/api/src/py.typed +0 -0
  200. package/templates/compose.web.yaml +0 -35
  201. package/templates/compose.yaml +0 -116
  202. package/templates/docker/env.example +0 -23
  203. package/templates/web/.env.example +0 -28
  204. package/templates/web/Dockerfile +0 -51
  205. package/templates/web/components.json +0 -22
  206. package/templates/web/imageLoader.js +0 -18
  207. package/templates/web/next-env.d.ts +0 -5
  208. package/templates/web/next.config.js +0 -36
  209. package/templates/web/package.json +0 -41
  210. package/templates/web/postcss.config.mjs +0 -7
  211. package/templates/web/public/favicon.ico +0 -0
  212. package/templates/web/src/app/boards/[boardId]/page.tsx +0 -353
  213. package/templates/web/src/app/globals.css +0 -123
  214. package/templates/web/src/app/layout.tsx +0 -31
  215. package/templates/web/src/app/lineage/[generationId]/page.tsx +0 -235
  216. package/templates/web/src/app/page.tsx +0 -35
  217. package/templates/web/src/app/providers.tsx +0 -18
  218. package/templates/web/src/components/boards/ArtifactInputSlots.tsx +0 -206
  219. package/templates/web/src/components/boards/ArtifactPreview.tsx +0 -466
  220. package/templates/web/src/components/boards/GenerationGrid.tsx +0 -282
  221. package/templates/web/src/components/boards/GenerationInput.tsx +0 -370
  222. package/templates/web/src/components/boards/GeneratorSelector.tsx +0 -272
  223. package/templates/web/src/components/boards/UploadArtifact.tsx +0 -563
  224. package/templates/web/src/components/header.tsx +0 -32
  225. package/templates/web/src/components/theme-provider.tsx +0 -10
  226. package/templates/web/src/components/theme-toggle.tsx +0 -75
  227. package/templates/web/src/components/ui/alert-dialog.tsx +0 -157
  228. package/templates/web/src/components/ui/button.tsx +0 -58
  229. package/templates/web/src/components/ui/card.tsx +0 -92
  230. package/templates/web/src/components/ui/dropdown-menu.tsx +0 -200
  231. package/templates/web/src/components/ui/navigation-menu.tsx +0 -168
  232. package/templates/web/src/components/ui/toast.tsx +0 -128
  233. package/templates/web/src/components/ui/toaster.tsx +0 -35
  234. package/templates/web/src/components/ui/use-toast.ts +0 -187
  235. package/templates/web/src/hooks/useGeneratorMRU.ts +0 -57
  236. package/templates/web/src/lib/utils.ts +0 -6
  237. package/templates/web/tsconfig.json +0 -41
@@ -1,158 +0,0 @@
1
- """
2
- WAN-Pro 2.1 image-to-video generator.
3
-
4
- A premium image-to-video model that generates high-quality 1080p videos at 30fps
5
- with up to 6 seconds duration, converting static images into dynamic video content
6
- with exceptional motion diversity.
7
-
8
- Based on Fal AI's fal-ai/wan-pro/image-to-video model.
9
- See: https://fal.ai/models/fal-ai/wan-pro/image-to-video
10
- """
11
-
12
- import os
13
-
14
- from pydantic import BaseModel, Field
15
-
16
- from ....artifacts import ImageArtifact
17
- from ....base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
18
-
19
-
20
- class WanProImageToVideoInput(BaseModel):
21
- """Input schema for WAN-Pro 2.1 image-to-video generation.
22
-
23
- Artifact fields (image) are automatically detected via type introspection
24
- and resolved from generation IDs to ImageArtifact objects.
25
- """
26
-
27
- image: ImageArtifact = Field(description="The image to generate the video from")
28
- prompt: str = Field(description="Text prompt describing the desired video content and motion")
29
- seed: int | None = Field(
30
- default=None,
31
- description="Random seed for reproducibility. If not specified, a random seed will be used",
32
- )
33
- enable_safety_checker: bool = Field(
34
- default=True,
35
- description="Whether to enable the safety checker for content moderation",
36
- )
37
-
38
-
39
- class FalWanProImageToVideoGenerator(BaseGenerator):
40
- """Generator for creating videos from static images using WAN-Pro 2.1."""
41
-
42
- name = "fal-wan-pro-image-to-video"
43
- description = "Fal: WAN-Pro 2.1 - Generate high-quality 1080p videos from static images"
44
- artifact_type = "video"
45
-
46
- def get_input_schema(self) -> type[WanProImageToVideoInput]:
47
- """Return the input schema for this generator."""
48
- return WanProImageToVideoInput
49
-
50
- async def generate(
51
- self, inputs: WanProImageToVideoInput, context: GeneratorExecutionContext
52
- ) -> GeneratorResult:
53
- """Generate video using fal.ai wan-pro/image-to-video."""
54
- # Check for API key
55
- if not os.getenv("FAL_KEY"):
56
- raise ValueError("API configuration invalid. Missing FAL_KEY environment variable")
57
-
58
- # Import fal_client
59
- try:
60
- import fal_client
61
- except ImportError as e:
62
- raise ImportError(
63
- "fal.ai SDK is required for FalWanProImageToVideoGenerator. "
64
- "Install with: pip install weirdfingers-boards[generators-fal]"
65
- ) from e
66
-
67
- # Upload image artifact to Fal's public storage
68
- # Fal API requires publicly accessible URLs, but our storage_url might be:
69
- # - Localhost URLs (not publicly accessible)
70
- # - Private S3 buckets (not publicly accessible)
71
- # So we upload to Fal's temporary storage first
72
- from ..utils import upload_artifacts_to_fal
73
-
74
- image_urls = await upload_artifacts_to_fal([inputs.image], context)
75
-
76
- # Prepare arguments for fal.ai API
77
- arguments = {
78
- "image_url": image_urls[0],
79
- "prompt": inputs.prompt,
80
- "enable_safety_checker": inputs.enable_safety_checker,
81
- }
82
-
83
- # Only add seed if provided (allow API to use random if not specified)
84
- if inputs.seed is not None:
85
- arguments["seed"] = inputs.seed
86
-
87
- # Submit async job
88
- handler = await fal_client.submit_async(
89
- "fal-ai/wan-pro/image-to-video",
90
- arguments=arguments,
91
- )
92
-
93
- # Store external job ID
94
- await context.set_external_job_id(handler.request_id)
95
-
96
- # Stream progress updates
97
- from .....progress.models import ProgressUpdate
98
-
99
- event_count = 0
100
- async for event in handler.iter_events(with_logs=True):
101
- event_count += 1
102
- # Sample every 3rd event to avoid spam
103
- if event_count % 3 == 0:
104
- # Extract logs if available
105
- logs = getattr(event, "logs", None)
106
- if logs:
107
- # Join log entries into a single message
108
- if isinstance(logs, list):
109
- message = " | ".join(str(log) for log in logs if log)
110
- else:
111
- message = str(logs)
112
-
113
- if message:
114
- await context.publish_progress(
115
- ProgressUpdate(
116
- job_id=handler.request_id,
117
- status="processing",
118
- progress=50.0,
119
- phase="processing",
120
- message=message,
121
- )
122
- )
123
-
124
- # Get final result
125
- result = await handler.get()
126
-
127
- # Extract video from result
128
- # Expected structure: {"video": {"url": "...", "content_type": "...", ...}}
129
- video_data = result.get("video")
130
- if not video_data:
131
- raise ValueError("No video returned from fal.ai API")
132
-
133
- video_url = video_data.get("url")
134
- if not video_url:
135
- raise ValueError("Video missing URL in fal.ai response")
136
-
137
- # Store video result
138
- # WAN-Pro generates 1080p videos at 30fps with up to 6 seconds duration
139
- artifact = await context.store_video_result(
140
- storage_url=video_url,
141
- format="mp4",
142
- width=1920,
143
- height=1080,
144
- duration=6, # Maximum duration
145
- fps=30,
146
- output_index=0,
147
- )
148
-
149
- return GeneratorResult(outputs=[artifact])
150
-
151
- async def estimate_cost(self, inputs: WanProImageToVideoInput) -> float:
152
- """Estimate cost for this generation in USD.
153
-
154
- Note: Pricing information not available in Fal documentation.
155
- Using placeholder value that should be updated with actual pricing.
156
- """
157
- # TODO: Update with actual pricing from Fal when available
158
- return 0.10 # Placeholder estimate for premium image-to-video generation
@@ -1,11 +0,0 @@
1
- """Kie.ai generator implementations."""
2
-
3
- from .image.nano_banana_edit import KieNanoBananaEditGenerator, NanoBananaEditInput
4
- from .video.veo3 import KieVeo3Generator, KieVeo3Input
5
-
6
- __all__ = [
7
- "KieNanoBananaEditGenerator",
8
- "NanoBananaEditInput",
9
- "KieVeo3Generator",
10
- "KieVeo3Input",
11
- ]
@@ -1,316 +0,0 @@
1
- """Base classes for Kie.ai generators.
2
-
3
- Provides common functionality shared across all Kie.ai generator implementations,
4
- including API key validation, HTTP client setup, response validation, and polling logic.
5
-
6
- Kie.ai supports two API patterns:
7
- - Market API: Unified endpoint for 30+ models using /api/v1/jobs endpoints
8
- - Dedicated API: Model-specific endpoints with custom paths
9
- """
10
-
11
- import asyncio
12
- import os
13
- from abc import abstractmethod
14
- from typing import Any, ClassVar, Literal
15
-
16
- import httpx
17
-
18
- from ....progress.models import ProgressUpdate
19
- from ...base import BaseGenerator, GeneratorExecutionContext
20
-
21
-
22
- class KieBaseGenerator(BaseGenerator):
23
- """Base class for all Kie.ai generators with common functionality.
24
-
25
- Provides shared methods for API key management, HTTP requests,
26
- response validation, and external job ID storage.
27
-
28
- Subclasses must define:
29
- - api_pattern: Either "market" or "dedicated"
30
- - model_id: Model identifier (for market) or endpoint path (for dedicated)
31
- """
32
-
33
- # Subclasses must define these
34
- api_pattern: ClassVar[Literal["market", "dedicated"]]
35
- model_id: str
36
-
37
- def _get_api_key(self) -> str:
38
- """Get and validate KIE_API_KEY from environment.
39
-
40
- Returns:
41
- The API key string
42
-
43
- Raises:
44
- ValueError: If KIE_API_KEY is not set
45
- """
46
- api_key = os.getenv("KIE_API_KEY")
47
- if not api_key:
48
- raise ValueError("API configuration invalid. Missing KIE_API_KEY environment variable")
49
- return api_key
50
-
51
- def _validate_response(self, response: dict[str, Any]) -> None:
52
- """Validate standard Kie.ai response structure.
53
-
54
- All Kie.ai APIs return responses with a "code" field where 200 indicates success.
55
-
56
- Args:
57
- response: The JSON response from Kie.ai API
58
-
59
- Raises:
60
- ValueError: If the response code is not 200
61
- """
62
- if response.get("code") != 200:
63
- error_msg = response.get("msg", "Unknown error")
64
- raise ValueError(f"Kie.ai API error: {error_msg}")
65
-
66
- async def _make_request(
67
- self,
68
- url: str,
69
- method: Literal["GET", "POST"],
70
- api_key: str,
71
- json: dict[str, Any] | None = None,
72
- timeout: float = 30.0,
73
- ) -> dict[str, Any]:
74
- """Make HTTP request to Kie.ai API with standard error handling.
75
-
76
- Args:
77
- url: Full URL to request
78
- method: HTTP method (GET or POST)
79
- api_key: API key for authorization
80
- json: Request body for POST requests
81
- timeout: Request timeout in seconds
82
-
83
- Returns:
84
- The validated JSON response
85
-
86
- Raises:
87
- ValueError: If the request fails or returns an error response
88
- """
89
- async with httpx.AsyncClient() as client:
90
- if method == "POST":
91
- response = await client.post(
92
- url,
93
- json=json,
94
- headers={
95
- "Authorization": f"Bearer {api_key}",
96
- "Content-Type": "application/json",
97
- },
98
- timeout=timeout,
99
- )
100
- else:
101
- response = await client.get(
102
- url,
103
- headers={"Authorization": f"Bearer {api_key}"},
104
- timeout=timeout,
105
- )
106
-
107
- if response.status_code != 200:
108
- raise ValueError(
109
- f"Kie.ai API request failed: {response.status_code} {response.text}"
110
- )
111
-
112
- result = response.json()
113
- self._validate_response(result)
114
- return result
115
-
116
- @abstractmethod
117
- async def _poll_for_completion(
118
- self,
119
- task_id: str,
120
- api_key: str,
121
- context: GeneratorExecutionContext,
122
- ) -> dict[str, Any]:
123
- """Poll for task completion.
124
-
125
- Subclasses implement this based on their API pattern (Market vs Dedicated).
126
-
127
- Args:
128
- task_id: The task ID to poll
129
- api_key: API key for authorization
130
- context: Generator execution context for progress updates
131
-
132
- Returns:
133
- The completed task data containing results
134
-
135
- Raises:
136
- ValueError: If polling fails or task fails
137
- """
138
- pass
139
-
140
-
141
- class KieMarketAPIGenerator(KieBaseGenerator):
142
- """Base class for Kie.ai Market API generators.
143
-
144
- Market API is used for 30+ models through a unified endpoint.
145
- - Submit: POST /api/v1/jobs/createTask with model parameter
146
- - Status: GET /api/v1/jobs/recordInfo?taskId={id}
147
- - Status field: "state" with values: "waiting", "pending", "processing", "success", "failed"
148
- """
149
-
150
- api_pattern: ClassVar[Literal["market"]] = "market"
151
-
152
- async def _poll_for_completion(
153
- self,
154
- task_id: str,
155
- api_key: str,
156
- context: GeneratorExecutionContext,
157
- max_polls: int = 120,
158
- poll_interval: int = 10,
159
- ) -> dict[str, Any]:
160
- """Poll Market API for task completion using state field.
161
-
162
- Args:
163
- task_id: The task ID to poll
164
- api_key: API key for authorization
165
- context: Generator execution context for progress updates
166
- max_polls: Maximum number of polling attempts (default: 120 = 20 minutes)
167
- poll_interval: Seconds between polls (default: 10)
168
-
169
- Returns:
170
- The completed task data from the "data" field
171
-
172
- Raises:
173
- ValueError: If task fails or times out
174
- """
175
- status_url = f"https://api.kie.ai/api/v1/jobs/recordInfo?taskId={task_id}"
176
-
177
- async with httpx.AsyncClient() as client:
178
- for poll_count in range(max_polls):
179
- # Don't sleep on first poll - check status immediately
180
- if poll_count > 0:
181
- await asyncio.sleep(poll_interval)
182
-
183
- status_response = await client.get(
184
- status_url,
185
- headers={"Authorization": f"Bearer {api_key}"},
186
- timeout=30.0,
187
- )
188
-
189
- if status_response.status_code != 200:
190
- raise ValueError(
191
- f"Status check failed: {status_response.status_code} {status_response.text}"
192
- )
193
-
194
- status_result = status_response.json()
195
- self._validate_response(status_result)
196
-
197
- task_data = status_result.get("data", {})
198
- state = task_data.get("state")
199
-
200
- if state == "success":
201
- return task_data
202
- elif state == "failed":
203
- error_msg = task_data.get("failMsg", "Unknown error")
204
- raise ValueError(f"Generation failed: {error_msg}")
205
- elif state not in ["waiting", "pending", "processing", None]:
206
- raise ValueError(
207
- f"Unknown state '{state}' from Kie.ai API. Full response: {status_result}"
208
- )
209
-
210
- # Publish progress
211
- progress = min(90, (poll_count / max_polls) * 100)
212
- await context.publish_progress(
213
- ProgressUpdate(
214
- job_id=task_id,
215
- status="processing",
216
- progress=progress,
217
- phase="processing",
218
- )
219
- )
220
- else:
221
- timeout_minutes = (max_polls * poll_interval) / 60
222
- raise ValueError(f"Generation timed out after {timeout_minutes} minutes")
223
-
224
-
225
- class KieDedicatedAPIGenerator(KieBaseGenerator):
226
- """Base class for Kie.ai Dedicated API generators.
227
-
228
- Dedicated APIs have model-specific endpoints with custom paths.
229
- - Submit: POST /api/v1/{model}/generate (no model parameter in body)
230
- - Status: GET /api/v1/{model}/record-info?taskId={id}
231
- - Status field: "successFlag" with values: 0 (processing), 1 (success), 2/3 (failed)
232
- """
233
-
234
- api_pattern: ClassVar[Literal["dedicated"]] = "dedicated"
235
-
236
- @abstractmethod
237
- def _get_status_url(self, task_id: str) -> str:
238
- """Get the status check URL for this specific dedicated API.
239
-
240
- Each dedicated API has its own status endpoint path.
241
-
242
- Args:
243
- task_id: The task ID to check status for
244
-
245
- Returns:
246
- Full URL for status checking
247
- """
248
- pass
249
-
250
- async def _poll_for_completion(
251
- self,
252
- task_id: str,
253
- api_key: str,
254
- context: GeneratorExecutionContext,
255
- max_polls: int = 180,
256
- poll_interval: int = 10,
257
- ) -> dict[str, Any]:
258
- """Poll Dedicated API for task completion using successFlag field.
259
-
260
- Args:
261
- task_id: The task ID to poll
262
- api_key: API key for authorization
263
- context: Generator execution context for progress updates
264
- max_polls: Maximum number of polling attempts (default: 180 = 30 minutes)
265
- poll_interval: Seconds between polls (default: 10)
266
-
267
- Returns:
268
- The completed task data from the "data" field
269
-
270
- Raises:
271
- ValueError: If task fails or times out
272
- """
273
- status_url = self._get_status_url(task_id)
274
-
275
- async with httpx.AsyncClient() as client:
276
- for poll_count in range(max_polls):
277
- # Don't sleep on first poll - check status immediately
278
- if poll_count > 0:
279
- await asyncio.sleep(poll_interval)
280
-
281
- status_response = await client.get(
282
- status_url,
283
- headers={"Authorization": f"Bearer {api_key}"},
284
- timeout=30.0,
285
- )
286
-
287
- if status_response.status_code != 200:
288
- raise ValueError(
289
- f"Status check failed: {status_response.status_code} {status_response.text}"
290
- )
291
-
292
- status_result = status_response.json()
293
- self._validate_response(status_result)
294
-
295
- task_data = status_result.get("data", {})
296
- success_flag = task_data.get("successFlag")
297
-
298
- if success_flag == 1:
299
- return task_data
300
- elif success_flag in [2, 3]:
301
- error_msg = task_data.get("errorMsg", "Unknown error")
302
- raise ValueError(f"Generation failed: {error_msg}")
303
-
304
- # Publish progress
305
- progress = min(90, (poll_count / max_polls) * 100)
306
- await context.publish_progress(
307
- ProgressUpdate(
308
- job_id=task_id,
309
- status="processing",
310
- progress=progress,
311
- phase="processing",
312
- )
313
- )
314
- else:
315
- timeout_minutes = (max_polls * poll_interval) / 60
316
- raise ValueError(f"Generation timed out after {timeout_minutes} minutes")
@@ -1,3 +0,0 @@
1
- from .nano_banana_edit import KieNanoBananaEditGenerator
2
-
3
- __all__ = ["KieNanoBananaEditGenerator"]
@@ -1,190 +0,0 @@
1
- """
2
- Kie.ai nano-banana image-to-image editing generator.
3
-
4
- Edit images using Kie.ai's google/nano-banana-edit model (powered by Google Gemini).
5
- Supports editing multiple input images with a text prompt.
6
-
7
- Based on Kie.ai's google/nano-banana-edit model.
8
- See: https://docs.kie.ai/market/google/nano-banana-edit
9
- """
10
-
11
- from typing import Literal
12
-
13
- from pydantic import BaseModel, Field
14
-
15
- from ....artifacts import ImageArtifact
16
- from ....base import GeneratorExecutionContext, GeneratorResult
17
- from ..base import KieMarketAPIGenerator
18
-
19
-
20
- class NanoBananaEditInput(BaseModel):
21
- """Input schema for nano-banana image editing.
22
-
23
- Artifact fields (like image_sources) are automatically detected via type
24
- introspection and resolved from generation IDs to ImageArtifact objects.
25
- """
26
-
27
- prompt: str = Field(
28
- description="The prompt for image editing",
29
- max_length=5000,
30
- )
31
- image_sources: list[ImageArtifact] = Field(
32
- description="List of input images for editing (from previous generations)",
33
- min_length=1,
34
- max_length=10,
35
- )
36
- output_format: Literal["png", "jpeg"] = Field(
37
- default="png",
38
- description="Output image format",
39
- )
40
- image_size: Literal[
41
- "1:1",
42
- "9:16",
43
- "16:9",
44
- "3:4",
45
- "4:3",
46
- "3:2",
47
- "2:3",
48
- "5:4",
49
- "4:5",
50
- "21:9",
51
- "auto",
52
- ] = Field(
53
- default="1:1",
54
- description="Output image aspect ratio",
55
- )
56
-
57
-
58
- class KieNanoBananaEditGenerator(KieMarketAPIGenerator):
59
- """nano-banana image editing generator using Kie.ai Market API."""
60
-
61
- name = "kie-nano-banana-edit"
62
- artifact_type = "image"
63
- description = "Kie.ai: Google nano-banana edit - AI-powered image editing with Gemini"
64
-
65
- # Market API configuration
66
- model_id = "google/nano-banana-edit"
67
-
68
- def get_input_schema(self) -> type[NanoBananaEditInput]:
69
- return NanoBananaEditInput
70
-
71
- async def generate(
72
- self, inputs: NanoBananaEditInput, context: GeneratorExecutionContext
73
- ) -> GeneratorResult:
74
- """Edit images using Kie.ai google/nano-banana-edit model."""
75
- # Get API key using base class method
76
- api_key = self._get_api_key()
77
-
78
- # Upload image artifacts to Kie.ai's public storage
79
- # Kie.ai API requires publicly accessible URLs, but our storage_url might be:
80
- # - Localhost URLs (not publicly accessible)
81
- # - Private S3 buckets (not publicly accessible)
82
- # So we upload to Kie.ai's temporary storage first
83
- from ..utils import upload_artifacts_to_kie
84
-
85
- image_urls = await upload_artifacts_to_kie(inputs.image_sources, context)
86
-
87
- # Prepare request body for Market API
88
- body = {
89
- "model": self.model_id,
90
- "input": {
91
- "prompt": inputs.prompt,
92
- "image_urls": image_urls,
93
- "output_format": inputs.output_format,
94
- "image_size": inputs.image_size,
95
- },
96
- }
97
-
98
- # Submit task using base class method
99
- submit_url = "https://api.kie.ai/api/v1/jobs/createTask"
100
- result = await self._make_request(submit_url, "POST", api_key, json=body)
101
-
102
- # Extract task ID with safe dictionary access
103
- data = result.get("data", {})
104
- task_id = data.get("taskId")
105
-
106
- if not task_id:
107
- raise ValueError(f"No taskId returned from Kie.ai API. Response: {result}")
108
-
109
- # Store external job ID
110
- await context.set_external_job_id(task_id)
111
-
112
- # Poll for completion using base class method
113
- task_data = await self._poll_for_completion(task_id, api_key, context)
114
-
115
- # Extract outputs from resultJson
116
- result_json = task_data.get("resultJson")
117
- if result_json:
118
- import json
119
-
120
- result_data = json.loads(result_json)
121
- else:
122
- result_data = task_data.get("result")
123
-
124
- if not result_data:
125
- raise ValueError("No result data returned from Kie.ai API")
126
-
127
- # Extract image URLs from result
128
- # The response structure may vary, but typically contains image URLs
129
- # Based on the API pattern, result should contain the generated images
130
- images = result_data.get("images", [])
131
-
132
- if not images:
133
- # Sometimes the result might directly contain URLs in different structure
134
- # Try to extract from common patterns
135
- if isinstance(result_data, dict):
136
- # Check for common response patterns
137
- if "resultUrls" in result_data:
138
- # Market API returns resultUrls as array of strings
139
- images = [{"url": url} for url in result_data["resultUrls"]]
140
- elif "image_urls" in result_data:
141
- images = [{"url": url} for url in result_data["image_urls"]]
142
- elif "url" in result_data:
143
- images = [{"url": result_data["url"]}]
144
- else:
145
- raise ValueError(f"No images found in result: {result_data}")
146
- else:
147
- raise ValueError("No images returned from Kie.ai API")
148
-
149
- # Store each image using output_index
150
- artifacts = []
151
- for idx, image_data in enumerate(images):
152
- if isinstance(image_data, str):
153
- image_url = image_data
154
- width = 1024
155
- height = 1024
156
- elif isinstance(image_data, dict):
157
- image_url = image_data.get("url")
158
- # Extract dimensions if available, otherwise use sensible defaults
159
- width = image_data.get("width", 1024)
160
- height = image_data.get("height", 1024)
161
- else:
162
- raise ValueError(f"Unexpected image data format: {type(image_data)}")
163
-
164
- if not image_url:
165
- raise ValueError(f"Image {idx} missing URL in Kie.ai response")
166
-
167
- # Store with appropriate output_index
168
- artifact = await context.store_image_result(
169
- storage_url=image_url,
170
- format=inputs.output_format,
171
- width=width,
172
- height=height,
173
- output_index=idx,
174
- )
175
- artifacts.append(artifact)
176
-
177
- return GeneratorResult(outputs=artifacts)
178
-
179
- async def estimate_cost(self, inputs: NanoBananaEditInput) -> float:
180
- """Estimate cost for nano-banana edit generation.
181
-
182
- nano-banana/edit uses Google Gemini for image editing.
183
- Estimated at $0.025 per image (approximately 35% cheaper than Fal's $0.039).
184
-
185
- Note: Actual pricing should be verified at https://kie.ai/pricing
186
- """
187
- # Cost per image - this is an estimate and should be updated with actual pricing
188
- per_image_cost = 0.025
189
- num_images = len(inputs.image_sources)
190
- return per_image_cost * num_images