@weirdfingers/baseboards 0.5.3 → 0.6.1
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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/templates/api/alembic/env.py +9 -1
- package/templates/api/alembic/versions/20250101_000000_initial_schema.py +107 -49
- package/templates/api/alembic/versions/20251022_174729_remove_provider_name_from_generations.py +7 -3
- package/templates/api/alembic/versions/20251023_165852_switch_to_declarative_base_and_mapping.py +57 -1
- package/templates/api/alembic/versions/20251202_000000_add_artifact_lineage.py +134 -0
- package/templates/api/alembic/versions/2025925_62735_add_seed_data_for_default_tenant.py +8 -5
- package/templates/api/config/generators.yaml +111 -0
- package/templates/api/src/boards/__init__.py +1 -1
- package/templates/api/src/boards/api/app.py +2 -1
- package/templates/api/src/boards/api/endpoints/tenant_registration.py +1 -1
- package/templates/api/src/boards/api/endpoints/uploads.py +150 -0
- package/templates/api/src/boards/auth/factory.py +1 -1
- package/templates/api/src/boards/dbmodels/__init__.py +8 -22
- package/templates/api/src/boards/generators/artifact_resolution.py +45 -12
- package/templates/api/src/boards/generators/implementations/fal/audio/__init__.py +16 -1
- package/templates/api/src/boards/generators/implementations/fal/audio/beatoven_music_generation.py +171 -0
- package/templates/api/src/boards/generators/implementations/fal/audio/beatoven_sound_effect_generation.py +167 -0
- package/templates/api/src/boards/generators/implementations/fal/audio/elevenlabs_sound_effects_v2.py +194 -0
- package/templates/api/src/boards/generators/implementations/fal/audio/elevenlabs_tts_eleven_v3.py +209 -0
- package/templates/api/src/boards/generators/implementations/fal/audio/fal_elevenlabs_tts_turbo_v2_5.py +206 -0
- package/templates/api/src/boards/generators/implementations/fal/audio/fal_minimax_speech_26_hd.py +237 -0
- package/templates/api/src/boards/generators/implementations/fal/audio/minimax_speech_2_6_turbo.py +1 -1
- package/templates/api/src/boards/generators/implementations/fal/image/__init__.py +30 -0
- package/templates/api/src/boards/generators/implementations/fal/image/clarity_upscaler.py +220 -0
- package/templates/api/src/boards/generators/implementations/fal/image/crystal_upscaler.py +173 -0
- package/templates/api/src/boards/generators/implementations/fal/image/fal_ideogram_character.py +227 -0
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2.py +203 -0
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2_edit.py +230 -0
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2_pro.py +204 -0
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2_pro_edit.py +221 -0
- package/templates/api/src/boards/generators/implementations/fal/image/gemini_25_flash_image.py +177 -0
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_edit_image.py +182 -0
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_mini.py +167 -0
- package/templates/api/src/boards/generators/implementations/fal/image/ideogram_character_edit.py +299 -0
- package/templates/api/src/boards/generators/implementations/fal/image/ideogram_v2.py +190 -0
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_pro_edit.py +226 -0
- package/templates/api/src/boards/generators/implementations/fal/image/qwen_image.py +249 -0
- package/templates/api/src/boards/generators/implementations/fal/image/qwen_image_edit.py +244 -0
- package/templates/api/src/boards/generators/implementations/fal/video/__init__.py +42 -0
- package/templates/api/src/boards/generators/implementations/fal/video/bytedance_seedance_v1_pro_text_to_video.py +209 -0
- package/templates/api/src/boards/generators/implementations/fal/video/creatify_lipsync.py +161 -0
- package/templates/api/src/boards/generators/implementations/fal/video/fal_bytedance_seedance_v1_pro_image_to_video.py +222 -0
- package/templates/api/src/boards/generators/implementations/fal/video/fal_minimax_hailuo_02_standard_text_to_video.py +152 -0
- package/templates/api/src/boards/generators/implementations/fal/video/fal_pixverse_lipsync.py +197 -0
- package/templates/api/src/boards/generators/implementations/fal/video/fal_sora_2_text_to_video.py +173 -0
- package/templates/api/src/boards/generators/implementations/fal/video/infinitalk.py +221 -0
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_image_to_video.py +175 -0
- package/templates/api/src/boards/generators/implementations/fal/video/minimax_hailuo_2_3_pro_image_to_video.py +153 -0
- package/templates/api/src/boards/generators/implementations/fal/video/sora2_image_to_video.py +172 -0
- package/templates/api/src/boards/generators/implementations/fal/video/sora_2_image_to_video_pro.py +175 -0
- package/templates/api/src/boards/generators/implementations/fal/video/sora_2_text_to_video_pro.py +163 -0
- package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2_pro.py +155 -0
- package/templates/api/src/boards/generators/implementations/fal/video/veed_lipsync.py +174 -0
- package/templates/api/src/boards/generators/implementations/fal/video/veo3.py +194 -0
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_first_last_frame_to_video.py +1 -1
- package/templates/api/src/boards/generators/implementations/fal/video/wan_pro_image_to_video.py +158 -0
- package/templates/api/src/boards/graphql/access_control.py +1 -1
- package/templates/api/src/boards/graphql/mutations/root.py +16 -4
- package/templates/api/src/boards/graphql/resolvers/board.py +0 -2
- package/templates/api/src/boards/graphql/resolvers/generation.py +10 -233
- package/templates/api/src/boards/graphql/resolvers/lineage.py +381 -0
- package/templates/api/src/boards/graphql/resolvers/upload.py +463 -0
- package/templates/api/src/boards/graphql/types/generation.py +62 -26
- package/templates/api/src/boards/middleware.py +1 -1
- package/templates/api/src/boards/storage/factory.py +2 -2
- package/templates/api/src/boards/tenant_isolation.py +9 -9
- package/templates/api/src/boards/workers/actors.py +10 -1
- package/templates/web/package.json +1 -1
- package/templates/web/src/app/boards/[boardId]/page.tsx +14 -5
- package/templates/web/src/app/lineage/[generationId]/page.tsx +233 -0
- package/templates/web/src/components/boards/ArtifactPreview.tsx +20 -1
- package/templates/web/src/components/boards/UploadArtifact.tsx +253 -0
|
@@ -11,24 +11,99 @@ allow_unlisted: false
|
|
|
11
11
|
# NOTE: Please keep generators in alphabetical order by class name for easier maintenance
|
|
12
12
|
generators:
|
|
13
13
|
# Fal.ai generators
|
|
14
|
+
- class: "boards.generators.implementations.fal.audio.beatoven_music_generation.FalBeatovenMusicGenerationGenerator"
|
|
15
|
+
enabled: true
|
|
16
|
+
|
|
17
|
+
- class: "boards.generators.implementations.fal.audio.beatoven_sound_effect_generation.FalBeatovenSoundEffectGenerationGenerator"
|
|
18
|
+
enabled: true
|
|
19
|
+
|
|
20
|
+
- class: "boards.generators.implementations.fal.image.clarity_upscaler.FalClarityUpscalerGenerator"
|
|
21
|
+
enabled: true
|
|
22
|
+
|
|
23
|
+
- class: "boards.generators.implementations.fal.image.crystal_upscaler.FalCrystalUpscalerGenerator"
|
|
24
|
+
enabled: true
|
|
25
|
+
|
|
26
|
+
- class: "boards.generators.implementations.fal.video.creatify_lipsync.FalCreatifyLipsyncGenerator"
|
|
27
|
+
enabled: true
|
|
28
|
+
|
|
29
|
+
- class: "boards.generators.implementations.fal.video.fal_bytedance_seedance_v1_pro_image_to_video.FalBytedanceSeedanceV1ProImageToVideoGenerator"
|
|
30
|
+
enabled: true
|
|
31
|
+
|
|
32
|
+
- class: "boards.generators.implementations.fal.video.bytedance_seedance_v1_pro_text_to_video.FalBytedanceSeedanceV1ProTextToVideoGenerator"
|
|
33
|
+
enabled: true
|
|
34
|
+
|
|
35
|
+
- class: "boards.generators.implementations.fal.audio.elevenlabs_tts_eleven_v3.FalElevenlabsTtsElevenV3Generator"
|
|
36
|
+
enabled: true
|
|
37
|
+
|
|
38
|
+
- class: "boards.generators.implementations.fal.audio.fal_elevenlabs_tts_turbo_v2_5.FalElevenlabsTtsTurboV25Generator"
|
|
39
|
+
enabled: true
|
|
40
|
+
|
|
41
|
+
- class: "boards.generators.implementations.fal.image.flux_2.FalFlux2Generator"
|
|
42
|
+
enabled: true
|
|
43
|
+
|
|
44
|
+
- class: "boards.generators.implementations.fal.image.flux_2_edit.FalFlux2EditGenerator"
|
|
45
|
+
enabled: true
|
|
46
|
+
|
|
47
|
+
- class: "boards.generators.implementations.fal.image.flux_2_pro.FalFlux2ProGenerator"
|
|
48
|
+
enabled: true
|
|
49
|
+
|
|
50
|
+
- class: "boards.generators.implementations.fal.image.flux_2_pro_edit.FalFlux2ProEditGenerator"
|
|
51
|
+
enabled: true
|
|
52
|
+
|
|
14
53
|
- class: "boards.generators.implementations.fal.image.flux_pro_kontext.FalFluxProKontextGenerator"
|
|
15
54
|
enabled: true
|
|
16
55
|
|
|
17
56
|
- class: "boards.generators.implementations.fal.image.flux_pro_ultra.FalFluxProUltraGenerator"
|
|
18
57
|
enabled: true
|
|
19
58
|
|
|
59
|
+
- class: "boards.generators.implementations.fal.image.gemini_25_flash_image.FalGemini25FlashImageGenerator"
|
|
60
|
+
enabled: true
|
|
61
|
+
|
|
62
|
+
- class: "boards.generators.implementations.fal.image.gpt_image_1_edit_image.FalGptImage1EditImageGenerator"
|
|
63
|
+
enabled: true
|
|
64
|
+
|
|
65
|
+
- class: "boards.generators.implementations.fal.image.gpt_image_1_mini.FalGptImage1MiniGenerator"
|
|
66
|
+
enabled: true
|
|
67
|
+
|
|
68
|
+
- class: "boards.generators.implementations.fal.image.fal_ideogram_character.FalIdeogramCharacterGenerator"
|
|
69
|
+
enabled: true
|
|
70
|
+
|
|
71
|
+
- class: "boards.generators.implementations.fal.image.ideogram_character_edit.FalIdeogramCharacterEditGenerator"
|
|
72
|
+
enabled: true
|
|
73
|
+
|
|
74
|
+
- class: "boards.generators.implementations.fal.image.ideogram_v2.FalIdeogramV2Generator"
|
|
75
|
+
enabled: true
|
|
76
|
+
|
|
20
77
|
- class: "boards.generators.implementations.fal.image.imagen4_preview_fast.FalImagen4PreviewFastGenerator"
|
|
21
78
|
enabled: true
|
|
22
79
|
|
|
23
80
|
- class: "boards.generators.implementations.fal.image.imagen4_preview.FalImagen4PreviewGenerator"
|
|
24
81
|
enabled: true
|
|
25
82
|
|
|
83
|
+
- class: "boards.generators.implementations.fal.audio.elevenlabs_sound_effects_v2.FalElevenlabsSoundEffectsV2Generator"
|
|
84
|
+
enabled: true
|
|
85
|
+
|
|
86
|
+
- class: "boards.generators.implementations.fal.video.infinitalk.FalInfinitalkGenerator"
|
|
87
|
+
enabled: true
|
|
88
|
+
|
|
89
|
+
- class: "boards.generators.implementations.fal.video.kling_video_v2_5_turbo_pro_image_to_video.FalKlingVideoV25TurboProImageToVideoGenerator"
|
|
90
|
+
enabled: true
|
|
91
|
+
|
|
26
92
|
- class: "boards.generators.implementations.fal.video.kling_video_v2_5_turbo_pro_text_to_video.FalKlingVideoV25TurboProTextToVideoGenerator"
|
|
27
93
|
enabled: true
|
|
28
94
|
|
|
95
|
+
- class: "boards.generators.implementations.fal.video.fal_minimax_hailuo_02_standard_text_to_video.FalMinimaxHailuo02StandardTextToVideoGenerator"
|
|
96
|
+
enabled: true
|
|
97
|
+
|
|
98
|
+
- class: "boards.generators.implementations.fal.video.minimax_hailuo_2_3_pro_image_to_video.FalMinimaxHailuo23ProImageToVideoGenerator"
|
|
99
|
+
enabled: true
|
|
100
|
+
|
|
29
101
|
- class: "boards.generators.implementations.fal.audio.minimax_music_v2.FalMinimaxMusicV2Generator"
|
|
30
102
|
enabled: true
|
|
31
103
|
|
|
104
|
+
- class: "boards.generators.implementations.fal.audio.fal_minimax_speech_26_hd.FalMinimaxSpeech26HdGenerator"
|
|
105
|
+
enabled: true
|
|
106
|
+
|
|
32
107
|
- class: "boards.generators.implementations.fal.audio.minimax_speech_2_6_turbo.FalMinimaxSpeech26TurboGenerator"
|
|
33
108
|
enabled: true
|
|
34
109
|
|
|
@@ -41,9 +116,42 @@ generators:
|
|
|
41
116
|
- class: "boards.generators.implementations.fal.image.nano_banana_pro.FalNanoBananaProGenerator"
|
|
42
117
|
enabled: true
|
|
43
118
|
|
|
119
|
+
- class: "boards.generators.implementations.fal.image.nano_banana_pro_edit.FalNanoBananaProEditGenerator"
|
|
120
|
+
enabled: true
|
|
121
|
+
|
|
122
|
+
- class: "boards.generators.implementations.fal.video.fal_pixverse_lipsync.FalPixverseLipsyncGenerator"
|
|
123
|
+
enabled: true
|
|
124
|
+
|
|
125
|
+
- class: "boards.generators.implementations.fal.image.qwen_image_edit.FalQwenImageEditGenerator"
|
|
126
|
+
enabled: true
|
|
127
|
+
|
|
128
|
+
- class: "boards.generators.implementations.fal.image.qwen_image.FalQwenImageGenerator"
|
|
129
|
+
enabled: true
|
|
130
|
+
|
|
131
|
+
- class: "boards.generators.implementations.fal.video.sora_2_text_to_video_pro.FalSora2TextToVideoProGenerator"
|
|
132
|
+
enabled: true
|
|
133
|
+
|
|
134
|
+
- class: "boards.generators.implementations.fal.video.fal_sora_2_text_to_video.FalSora2TextToVideoGenerator"
|
|
135
|
+
enabled: true
|
|
136
|
+
|
|
137
|
+
- class: "boards.generators.implementations.fal.video.sora2_image_to_video.FalSora2ImageToVideoGenerator"
|
|
138
|
+
enabled: true
|
|
139
|
+
|
|
140
|
+
- class: "boards.generators.implementations.fal.video.sora_2_image_to_video_pro.FalSora2ImageToVideoProGenerator"
|
|
141
|
+
enabled: true
|
|
142
|
+
|
|
44
143
|
- class: "boards.generators.implementations.fal.video.sync_lipsync_v2.FalSyncLipsyncV2Generator"
|
|
45
144
|
enabled: true
|
|
46
145
|
|
|
146
|
+
- class: "boards.generators.implementations.fal.video.veed_lipsync.FalVeedLipsyncGenerator"
|
|
147
|
+
enabled: true
|
|
148
|
+
|
|
149
|
+
- class: "boards.generators.implementations.fal.video.sync_lipsync_v2_pro.FalSyncLipsyncV2ProGenerator"
|
|
150
|
+
enabled: true
|
|
151
|
+
|
|
152
|
+
- class: "boards.generators.implementations.fal.video.veo3.FalVeo3Generator"
|
|
153
|
+
enabled: true
|
|
154
|
+
|
|
47
155
|
- class: "boards.generators.implementations.fal.video.veo31_first_last_frame_to_video.FalVeo31FirstLastFrameToVideoGenerator"
|
|
48
156
|
enabled: true
|
|
49
157
|
|
|
@@ -53,6 +161,9 @@ generators:
|
|
|
53
161
|
- class: "boards.generators.implementations.fal.video.veo31_reference_to_video.FalVeo31ReferenceToVideoGenerator"
|
|
54
162
|
enabled: true
|
|
55
163
|
|
|
164
|
+
- class: "boards.generators.implementations.fal.video.wan_pro_image_to_video.FalWanProImageToVideoGenerator"
|
|
165
|
+
enabled: true
|
|
166
|
+
|
|
56
167
|
# OpenAI generators
|
|
57
168
|
- class: "boards.generators.implementations.openai.image.dalle3.OpenAIDallE3Generator"
|
|
58
169
|
enabled: true
|
|
@@ -142,13 +142,14 @@ def create_app() -> FastAPI:
|
|
|
142
142
|
raise
|
|
143
143
|
|
|
144
144
|
# REST API endpoints (for SSE, webhooks, etc.)
|
|
145
|
-
from .endpoints import jobs, setup, sse, storage, tenant_registration, webhooks
|
|
145
|
+
from .endpoints import jobs, setup, sse, storage, tenant_registration, uploads, webhooks
|
|
146
146
|
|
|
147
147
|
app.include_router(sse.router, prefix="/api/sse", tags=["SSE"])
|
|
148
148
|
app.include_router(jobs.router, prefix="/api/jobs", tags=["Jobs"])
|
|
149
149
|
app.include_router(webhooks.router, prefix="/api/webhooks", tags=["Webhooks"])
|
|
150
150
|
app.include_router(storage.router, prefix="/api/storage", tags=["Storage"])
|
|
151
151
|
app.include_router(setup.router, prefix="/api/setup", tags=["Setup"])
|
|
152
|
+
app.include_router(uploads.router, prefix="/api", tags=["Uploads"])
|
|
152
153
|
app.include_router(
|
|
153
154
|
tenant_registration.router, prefix="/api/tenants", tags=["Tenant Registration"]
|
|
154
155
|
)
|
|
@@ -69,7 +69,7 @@ class TenantRegistrationRequest(BaseModel):
|
|
|
69
69
|
if v is not None:
|
|
70
70
|
valid_sizes = {"small", "medium", "large", "enterprise"}
|
|
71
71
|
if v.lower() not in valid_sizes:
|
|
72
|
-
raise ValueError(f
|
|
72
|
+
raise ValueError(f"Organization size must be one of: {', '.join(valid_sizes)}")
|
|
73
73
|
return v.lower() if v else None
|
|
74
74
|
|
|
75
75
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""File upload endpoints for artifact uploads."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
|
|
8
|
+
|
|
9
|
+
from ...auth import get_auth_context
|
|
10
|
+
from ...auth.context import AuthContext
|
|
11
|
+
from ...config import settings
|
|
12
|
+
from ...logging import get_logger
|
|
13
|
+
|
|
14
|
+
router = APIRouter(prefix="/uploads", tags=["uploads"])
|
|
15
|
+
logger = get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@router.post("/artifact")
|
|
19
|
+
async def upload_artifact_file(
|
|
20
|
+
board_id: Annotated[str, Form()],
|
|
21
|
+
artifact_type: Annotated[str, Form()], # image, video, audio, text
|
|
22
|
+
file: UploadFile = File(...),
|
|
23
|
+
user_description: Annotated[str | None, Form()] = None,
|
|
24
|
+
parent_generation_id: Annotated[str | None, Form()] = None,
|
|
25
|
+
auth_context: AuthContext = Depends(get_auth_context),
|
|
26
|
+
) -> dict:
|
|
27
|
+
"""
|
|
28
|
+
Upload artifact file (synchronous).
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
board_id: UUID of the board to upload to
|
|
32
|
+
artifact_type: Type of artifact (image, video, audio, text)
|
|
33
|
+
file: The file to upload
|
|
34
|
+
user_description: Optional description provided by user
|
|
35
|
+
parent_generation_id: Optional parent generation UUID
|
|
36
|
+
auth_context: Authentication context
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Generation object as JSON
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
HTTPException: If validation fails or upload errors occur
|
|
43
|
+
"""
|
|
44
|
+
from ...graphql.resolvers.upload import upload_artifact_from_file
|
|
45
|
+
|
|
46
|
+
# Validate authentication
|
|
47
|
+
if not auth_context.is_authenticated or not auth_context.user_id:
|
|
48
|
+
raise HTTPException(
|
|
49
|
+
status_code=401,
|
|
50
|
+
detail="Authentication required",
|
|
51
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Validate artifact type
|
|
55
|
+
valid_types = {"image", "video", "audio", "text"}
|
|
56
|
+
if artifact_type not in valid_types:
|
|
57
|
+
raise HTTPException(
|
|
58
|
+
status_code=400,
|
|
59
|
+
detail=f"Invalid artifact_type. Must be one of: {', '.join(valid_types)}",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Read file content
|
|
63
|
+
try:
|
|
64
|
+
content = await file.read()
|
|
65
|
+
except Exception as e:
|
|
66
|
+
logger.error("Failed to read uploaded file", error=str(e), filename=file.filename)
|
|
67
|
+
raise HTTPException(
|
|
68
|
+
status_code=400,
|
|
69
|
+
detail="Failed to read uploaded file",
|
|
70
|
+
) from e
|
|
71
|
+
|
|
72
|
+
# Validate file size
|
|
73
|
+
if len(content) > settings.max_upload_size:
|
|
74
|
+
raise HTTPException(
|
|
75
|
+
status_code=413,
|
|
76
|
+
detail=(
|
|
77
|
+
f"File size {len(content)} bytes exceeds maximum allowed size "
|
|
78
|
+
f"of {settings.max_upload_size} bytes"
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Validate extension
|
|
83
|
+
file_ext = os.path.splitext(file.filename or "")[1].lower()
|
|
84
|
+
if file_ext and file_ext not in settings.allowed_upload_extensions:
|
|
85
|
+
allowed_exts = ", ".join(settings.allowed_upload_extensions)
|
|
86
|
+
raise HTTPException(
|
|
87
|
+
status_code=400,
|
|
88
|
+
detail=(
|
|
89
|
+
f"File extension '{file_ext}' is not allowed. "
|
|
90
|
+
f"Allowed extensions: {allowed_exts}"
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Parse UUIDs
|
|
95
|
+
try:
|
|
96
|
+
board_uuid = UUID(board_id)
|
|
97
|
+
parent_uuid = UUID(parent_generation_id) if parent_generation_id else None
|
|
98
|
+
except ValueError as e:
|
|
99
|
+
logger.warning("Invalid UUID provided", board_id=board_id, error=str(e))
|
|
100
|
+
raise HTTPException(
|
|
101
|
+
status_code=400,
|
|
102
|
+
detail="Invalid board_id or parent_generation_id format",
|
|
103
|
+
) from e
|
|
104
|
+
|
|
105
|
+
# Call resolver
|
|
106
|
+
try:
|
|
107
|
+
generation = await upload_artifact_from_file(
|
|
108
|
+
auth_context=auth_context,
|
|
109
|
+
board_id=board_uuid,
|
|
110
|
+
artifact_type=artifact_type,
|
|
111
|
+
file_content=content,
|
|
112
|
+
filename=file.filename,
|
|
113
|
+
content_type=file.content_type,
|
|
114
|
+
user_description=user_description,
|
|
115
|
+
parent_generation_id=parent_uuid,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
logger.info(
|
|
119
|
+
"File upload successful",
|
|
120
|
+
generation_id=str(generation.id),
|
|
121
|
+
artifact_type=artifact_type,
|
|
122
|
+
file_size=len(content),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
"id": str(generation.id),
|
|
127
|
+
"status": generation.status.value,
|
|
128
|
+
"storageUrl": generation.storage_url,
|
|
129
|
+
"thumbnailUrl": generation.thumbnail_url,
|
|
130
|
+
"artifactType": generation.artifact_type.value,
|
|
131
|
+
"generatorName": generation.generator_name,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
except RuntimeError as e:
|
|
135
|
+
# These are expected errors (permission denied, board not found, etc.)
|
|
136
|
+
# Pass through the message since these are safe, user-facing errors
|
|
137
|
+
logger.warning("Upload failed", error=str(e))
|
|
138
|
+
raise HTTPException(status_code=400, detail=str(e)) from e
|
|
139
|
+
except Exception as e:
|
|
140
|
+
# Unexpected errors - don't expose internal details
|
|
141
|
+
logger.error(
|
|
142
|
+
"Unexpected error during upload",
|
|
143
|
+
error=str(e),
|
|
144
|
+
board_id=board_id,
|
|
145
|
+
artifact_type=artifact_type,
|
|
146
|
+
)
|
|
147
|
+
raise HTTPException(
|
|
148
|
+
status_code=500,
|
|
149
|
+
detail="An unexpected error occurred during upload",
|
|
150
|
+
) from e
|
|
@@ -75,7 +75,7 @@ def get_auth_adapter() -> AuthAdapter:
|
|
|
75
75
|
secret_key = config.get("secret_key") or os.getenv("CLERK_SECRET_KEY")
|
|
76
76
|
if not secret_key:
|
|
77
77
|
raise ValueError(
|
|
78
|
-
"Clerk secret key is required.
|
|
78
|
+
"Clerk secret key is required. Set CLERK_SECRET_KEY or provide in config."
|
|
79
79
|
)
|
|
80
80
|
|
|
81
81
|
return ClerkAuthAdapter(
|
|
@@ -11,7 +11,6 @@ from typing import Any
|
|
|
11
11
|
from uuid import UUID
|
|
12
12
|
|
|
13
13
|
from sqlalchemy import (
|
|
14
|
-
ARRAY,
|
|
15
14
|
Boolean,
|
|
16
15
|
CheckConstraint,
|
|
17
16
|
Column,
|
|
@@ -43,7 +42,7 @@ naming_convention = {
|
|
|
43
42
|
class Base(DeclarativeBase):
|
|
44
43
|
"""Base class for all database models with type checking support."""
|
|
45
44
|
|
|
46
|
-
metadata = MetaData(naming_convention=naming_convention)
|
|
45
|
+
metadata = MetaData(naming_convention=naming_convention, schema="boards")
|
|
47
46
|
|
|
48
47
|
|
|
49
48
|
class Tenants(Base):
|
|
@@ -322,11 +321,6 @@ class Generations(Base):
|
|
|
322
321
|
ondelete="CASCADE",
|
|
323
322
|
name="generations_board_id_fkey",
|
|
324
323
|
),
|
|
325
|
-
ForeignKeyConstraint(
|
|
326
|
-
["parent_generation_id"],
|
|
327
|
-
["generations.id"],
|
|
328
|
-
name="generations_parent_generation_id_fkey",
|
|
329
|
-
),
|
|
330
324
|
ForeignKeyConstraint(
|
|
331
325
|
["tenant_id"],
|
|
332
326
|
["tenants.id"],
|
|
@@ -341,10 +335,14 @@ class Generations(Base):
|
|
|
341
335
|
),
|
|
342
336
|
PrimaryKeyConstraint("id", name="generations_pkey"),
|
|
343
337
|
Index("idx_generations_board", "board_id"),
|
|
344
|
-
Index("idx_generations_lineage", "parent_generation_id"),
|
|
345
338
|
Index("idx_generations_status", "status"),
|
|
346
339
|
Index("idx_generations_tenant", "tenant_id"),
|
|
347
340
|
Index("idx_generations_user", "user_id"),
|
|
341
|
+
Index(
|
|
342
|
+
"idx_generations_input_artifacts_gin",
|
|
343
|
+
"input_artifacts",
|
|
344
|
+
postgresql_using="gin",
|
|
345
|
+
),
|
|
348
346
|
)
|
|
349
347
|
|
|
350
348
|
id: Mapped[UUID] = mapped_column(Uuid, server_default=text("uuid_generate_v4()"))
|
|
@@ -363,9 +361,8 @@ class Generations(Base):
|
|
|
363
361
|
output_metadata: Mapped[dict[str, Any]] = mapped_column(
|
|
364
362
|
JSONB, server_default=text("'{}'::jsonb")
|
|
365
363
|
)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
ARRAY(Uuid()), server_default=text("'{}'::uuid[]")
|
|
364
|
+
input_artifacts: Mapped[list[dict[str, Any]]] = mapped_column(
|
|
365
|
+
JSONB, server_default=text("'[]'::jsonb")
|
|
369
366
|
)
|
|
370
367
|
external_job_id: Mapped[str | None] = mapped_column(String(255))
|
|
371
368
|
progress: Mapped[Decimal] = mapped_column(Numeric(5, 2), server_default=text("0.0"))
|
|
@@ -380,17 +377,6 @@ class Generations(Base):
|
|
|
380
377
|
)
|
|
381
378
|
|
|
382
379
|
board: Mapped["Boards"] = relationship("Boards", back_populates="generations")
|
|
383
|
-
parent_generation: Mapped["Generations | None"] = relationship(
|
|
384
|
-
"Generations",
|
|
385
|
-
remote_side="Generations.id",
|
|
386
|
-
back_populates="parent_generation_reverse",
|
|
387
|
-
)
|
|
388
|
-
parent_generation_reverse: Mapped[list["Generations"]] = relationship(
|
|
389
|
-
"Generations",
|
|
390
|
-
uselist=True,
|
|
391
|
-
remote_side="Generations.parent_generation_id",
|
|
392
|
-
back_populates="parent_generation",
|
|
393
|
-
)
|
|
394
380
|
tenant: Mapped["Tenants"] = relationship("Tenants", back_populates="generations")
|
|
395
381
|
user: Mapped["Users"] = relationship("Users", back_populates="generations")
|
|
396
382
|
credit_transactions: Mapped[list["CreditTransactions"]] = relationship(
|
|
@@ -59,7 +59,9 @@ def _extract_artifact_type(annotation: Any) -> type[TArtifact] | None:
|
|
|
59
59
|
return None
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
def extract_artifact_fields(
|
|
62
|
+
def extract_artifact_fields(
|
|
63
|
+
schema: type[BaseModel],
|
|
64
|
+
) -> dict[str, tuple[type[TArtifact], bool]]:
|
|
63
65
|
"""Automatically extract artifact fields from a Pydantic schema.
|
|
64
66
|
|
|
65
67
|
Inspects the schema's field annotations and returns a mapping of field names
|
|
@@ -93,7 +95,14 @@ def extract_artifact_fields(schema: type[BaseModel]) -> dict[str, tuple[type[TAr
|
|
|
93
95
|
return artifact_fields
|
|
94
96
|
|
|
95
97
|
|
|
96
|
-
def _get_artifact_type_name[
|
|
98
|
+
def _get_artifact_type_name[
|
|
99
|
+
T: (
|
|
100
|
+
ImageArtifact,
|
|
101
|
+
VideoArtifact,
|
|
102
|
+
AudioArtifact,
|
|
103
|
+
TextArtifact,
|
|
104
|
+
)
|
|
105
|
+
](
|
|
97
106
|
artifact_class: type[T],
|
|
98
107
|
) -> str:
|
|
99
108
|
"""Get the database artifact_type string for an artifact class."""
|
|
@@ -109,9 +118,14 @@ def _get_artifact_type_name[T: (ImageArtifact, VideoArtifact, AudioArtifact, Tex
|
|
|
109
118
|
return artifact_type
|
|
110
119
|
|
|
111
120
|
|
|
112
|
-
def _generation_to_artifact[
|
|
113
|
-
|
|
114
|
-
|
|
121
|
+
def _generation_to_artifact[
|
|
122
|
+
T: (
|
|
123
|
+
ImageArtifact,
|
|
124
|
+
VideoArtifact,
|
|
125
|
+
AudioArtifact,
|
|
126
|
+
TextArtifact,
|
|
127
|
+
)
|
|
128
|
+
](generation: Generations, artifact_class: type[T]) -> T:
|
|
115
129
|
"""Convert a Generations database record to an artifact object.
|
|
116
130
|
|
|
117
131
|
Args:
|
|
@@ -183,7 +197,12 @@ def _generation_to_artifact[T: (ImageArtifact, VideoArtifact, AudioArtifact, Tex
|
|
|
183
197
|
|
|
184
198
|
|
|
185
199
|
async def resolve_generation_ids_to_artifacts[
|
|
186
|
-
T: (
|
|
200
|
+
T: (
|
|
201
|
+
ImageArtifact,
|
|
202
|
+
VideoArtifact,
|
|
203
|
+
AudioArtifact,
|
|
204
|
+
TextArtifact,
|
|
205
|
+
)
|
|
187
206
|
](
|
|
188
207
|
generation_ids: list[str | UUID],
|
|
189
208
|
artifact_class: type[T],
|
|
@@ -251,7 +270,7 @@ async def resolve_input_artifacts(
|
|
|
251
270
|
schema: type[BaseModel],
|
|
252
271
|
session: AsyncSession,
|
|
253
272
|
tenant_id: UUID,
|
|
254
|
-
) -> dict[str, Any]:
|
|
273
|
+
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
|
255
274
|
"""Resolve generation IDs to artifact objects in input parameters.
|
|
256
275
|
|
|
257
276
|
This function automatically detects artifact fields from the Pydantic schema
|
|
@@ -271,7 +290,7 @@ async def resolve_input_artifacts(
|
|
|
271
290
|
|
|
272
291
|
Usage in actors.py:
|
|
273
292
|
# Artifacts are automatically detected and resolved
|
|
274
|
-
resolved_params = await resolve_input_artifacts(
|
|
293
|
+
resolved_params, lineage_metadata = await resolve_input_artifacts(
|
|
275
294
|
input_params,
|
|
276
295
|
MyGeneratorInput, # Just pass the schema class
|
|
277
296
|
session,
|
|
@@ -287,7 +306,9 @@ async def resolve_input_artifacts(
|
|
|
287
306
|
tenant_id: Tenant ID for access validation
|
|
288
307
|
|
|
289
308
|
Returns:
|
|
290
|
-
|
|
309
|
+
Tuple of (resolved_params, lineage_metadata):
|
|
310
|
+
- resolved_params: Updated input_params dictionary with generation IDs resolved to artifacts
|
|
311
|
+
- lineage_metadata: List of dicts with generation_id, role, and artifact_type for lineage
|
|
291
312
|
|
|
292
313
|
Raises:
|
|
293
314
|
ValueError: If any generation ID cannot be resolved or validated
|
|
@@ -295,13 +316,14 @@ async def resolve_input_artifacts(
|
|
|
295
316
|
# Automatically extract artifact fields from schema
|
|
296
317
|
artifact_field_map = extract_artifact_fields(schema)
|
|
297
318
|
|
|
298
|
-
# If no artifact fields, just return original params
|
|
319
|
+
# If no artifact fields, just return original params with empty lineage
|
|
299
320
|
if not artifact_field_map:
|
|
300
|
-
return input_params
|
|
321
|
+
return input_params, []
|
|
301
322
|
|
|
302
323
|
# Create a new dict with resolved artifacts
|
|
303
324
|
# Use dict constructor to avoid shallow copy issues
|
|
304
325
|
resolved_params = dict(input_params)
|
|
326
|
+
lineage_metadata: list[dict[str, Any]] = []
|
|
305
327
|
|
|
306
328
|
for field_name, (artifact_class, expects_list) in artifact_field_map.items():
|
|
307
329
|
field_value = resolved_params.get(field_name)
|
|
@@ -343,6 +365,17 @@ async def resolve_input_artifacts(
|
|
|
343
365
|
except ValueError as e:
|
|
344
366
|
raise ValueError(f"Failed to resolve field '{field_name}': {e}") from e
|
|
345
367
|
|
|
368
|
+
# Capture lineage metadata for each artifact
|
|
369
|
+
artifact_type_name = _get_artifact_type_name(artifact_class)
|
|
370
|
+
for gen_id in generation_ids:
|
|
371
|
+
lineage_metadata.append(
|
|
372
|
+
{
|
|
373
|
+
"generation_id": str(gen_id),
|
|
374
|
+
"role": field_name, # Field name IS the role!
|
|
375
|
+
"artifact_type": artifact_type_name,
|
|
376
|
+
}
|
|
377
|
+
)
|
|
378
|
+
|
|
346
379
|
# Update params with resolved artifacts
|
|
347
380
|
# If field expects a single artifact (not a list), unwrap the first artifact
|
|
348
381
|
# Otherwise, keep as a list (even if there's only one artifact)
|
|
@@ -369,4 +402,4 @@ async def resolve_input_artifacts(
|
|
|
369
402
|
|
|
370
403
|
resolved_params[field_name] = resolved_value
|
|
371
404
|
|
|
372
|
-
return resolved_params
|
|
405
|
+
return resolved_params, lineage_metadata
|
|
@@ -1,4 +1,19 @@
|
|
|
1
|
+
from .beatoven_music_generation import FalBeatovenMusicGenerationGenerator
|
|
2
|
+
from .beatoven_sound_effect_generation import FalBeatovenSoundEffectGenerationGenerator
|
|
3
|
+
from .elevenlabs_sound_effects_v2 import FalElevenlabsSoundEffectsV2Generator
|
|
4
|
+
from .elevenlabs_tts_eleven_v3 import FalElevenlabsTtsElevenV3Generator
|
|
5
|
+
from .fal_elevenlabs_tts_turbo_v2_5 import FalElevenlabsTtsTurboV25Generator
|
|
6
|
+
from .fal_minimax_speech_26_hd import FalMinimaxSpeech26HdGenerator
|
|
1
7
|
from .minimax_music_v2 import FalMinimaxMusicV2Generator
|
|
2
8
|
from .minimax_speech_2_6_turbo import FalMinimaxSpeech26TurboGenerator
|
|
3
9
|
|
|
4
|
-
__all__ = [
|
|
10
|
+
__all__ = [
|
|
11
|
+
"FalBeatovenMusicGenerationGenerator",
|
|
12
|
+
"FalBeatovenSoundEffectGenerationGenerator",
|
|
13
|
+
"FalElevenlabsSoundEffectsV2Generator",
|
|
14
|
+
"FalElevenlabsTtsElevenV3Generator",
|
|
15
|
+
"FalElevenlabsTtsTurboV25Generator",
|
|
16
|
+
"FalMinimaxMusicV2Generator",
|
|
17
|
+
"FalMinimaxSpeech26HdGenerator",
|
|
18
|
+
"FalMinimaxSpeech26TurboGenerator",
|
|
19
|
+
]
|