@weirdfingers/baseboards 0.9.5 → 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.
- package/dist/index.js +561 -469
- package/dist/index.js.map +1 -1
- package/package.json +2 -5
- package/templates/README.md +0 -122
- package/templates/api/.env.example +0 -65
- package/templates/api/ARTIFACT_RESOLUTION_GUIDE.md +0 -148
- package/templates/api/Dockerfile +0 -32
- package/templates/api/README.md +0 -264
- package/templates/api/alembic/env.py +0 -114
- package/templates/api/alembic/script.py.mako +0 -28
- package/templates/api/alembic/versions/20250101_000000_initial_schema.py +0 -506
- package/templates/api/alembic/versions/20251022_174729_remove_provider_name_from_generations.py +0 -75
- package/templates/api/alembic/versions/20251023_165852_switch_to_declarative_base_and_mapping.py +0 -467
- package/templates/api/alembic/versions/20251202_000000_add_artifact_lineage.py +0 -134
- package/templates/api/alembic/versions/2025925_62735_add_seed_data_for_default_tenant.py +0 -88
- package/templates/api/alembic.ini +0 -36
- package/templates/api/config/generators.yaml +0 -237
- package/templates/api/config/storage_config.yaml +0 -26
- package/templates/api/docs/ADDING_GENERATORS.md +0 -409
- package/templates/api/docs/GENERATORS_API.md +0 -502
- package/templates/api/docs/MIGRATIONS.md +0 -472
- package/templates/api/docs/TESTING_LIVE_APIS.md +0 -417
- package/templates/api/docs/storage_providers.md +0 -337
- package/templates/api/pyproject.toml +0 -205
- package/templates/api/src/boards/__init__.py +0 -10
- package/templates/api/src/boards/api/app.py +0 -172
- package/templates/api/src/boards/api/auth.py +0 -75
- package/templates/api/src/boards/api/endpoints/__init__.py +0 -3
- package/templates/api/src/boards/api/endpoints/jobs.py +0 -76
- package/templates/api/src/boards/api/endpoints/setup.py +0 -505
- package/templates/api/src/boards/api/endpoints/sse.py +0 -129
- package/templates/api/src/boards/api/endpoints/storage.py +0 -155
- package/templates/api/src/boards/api/endpoints/tenant_registration.py +0 -296
- package/templates/api/src/boards/api/endpoints/uploads.py +0 -149
- package/templates/api/src/boards/api/endpoints/webhooks.py +0 -13
- package/templates/api/src/boards/auth/__init__.py +0 -15
- package/templates/api/src/boards/auth/adapters/__init__.py +0 -27
- package/templates/api/src/boards/auth/adapters/auth0.py +0 -220
- package/templates/api/src/boards/auth/adapters/base.py +0 -73
- package/templates/api/src/boards/auth/adapters/clerk.py +0 -172
- package/templates/api/src/boards/auth/adapters/jwt.py +0 -122
- package/templates/api/src/boards/auth/adapters/none.py +0 -102
- package/templates/api/src/boards/auth/adapters/oidc.py +0 -284
- package/templates/api/src/boards/auth/adapters/supabase.py +0 -110
- package/templates/api/src/boards/auth/context.py +0 -35
- package/templates/api/src/boards/auth/factory.py +0 -129
- package/templates/api/src/boards/auth/middleware.py +0 -221
- package/templates/api/src/boards/auth/provisioning.py +0 -129
- package/templates/api/src/boards/auth/tenant_extraction.py +0 -278
- package/templates/api/src/boards/cli.py +0 -354
- package/templates/api/src/boards/config.py +0 -131
- package/templates/api/src/boards/database/__init__.py +0 -7
- package/templates/api/src/boards/database/cli.py +0 -110
- package/templates/api/src/boards/database/connection.py +0 -292
- package/templates/api/src/boards/database/models.py +0 -19
- package/templates/api/src/boards/database/seed_data.py +0 -182
- package/templates/api/src/boards/dbmodels/__init__.py +0 -441
- package/templates/api/src/boards/generators/__init__.py +0 -57
- package/templates/api/src/boards/generators/artifact_resolution.py +0 -405
- package/templates/api/src/boards/generators/artifacts.py +0 -53
- package/templates/api/src/boards/generators/base.py +0 -144
- package/templates/api/src/boards/generators/implementations/__init__.py +0 -14
- package/templates/api/src/boards/generators/implementations/fal/__init__.py +0 -25
- package/templates/api/src/boards/generators/implementations/fal/audio/__init__.py +0 -23
- package/templates/api/src/boards/generators/implementations/fal/audio/beatoven_music_generation.py +0 -171
- package/templates/api/src/boards/generators/implementations/fal/audio/beatoven_sound_effect_generation.py +0 -167
- package/templates/api/src/boards/generators/implementations/fal/audio/chatterbox_text_to_speech.py +0 -176
- package/templates/api/src/boards/generators/implementations/fal/audio/chatterbox_tts_turbo.py +0 -195
- package/templates/api/src/boards/generators/implementations/fal/audio/elevenlabs_sound_effects_v2.py +0 -194
- package/templates/api/src/boards/generators/implementations/fal/audio/elevenlabs_tts_eleven_v3.py +0 -209
- package/templates/api/src/boards/generators/implementations/fal/audio/fal_elevenlabs_tts_turbo_v2_5.py +0 -206
- package/templates/api/src/boards/generators/implementations/fal/audio/fal_minimax_speech_26_hd.py +0 -237
- package/templates/api/src/boards/generators/implementations/fal/audio/minimax_music_v2.py +0 -173
- package/templates/api/src/boards/generators/implementations/fal/audio/minimax_speech_2_6_turbo.py +0 -221
- package/templates/api/src/boards/generators/implementations/fal/image/__init__.py +0 -63
- package/templates/api/src/boards/generators/implementations/fal/image/bytedance_seedream_v45_edit.py +0 -219
- package/templates/api/src/boards/generators/implementations/fal/image/clarity_upscaler.py +0 -220
- package/templates/api/src/boards/generators/implementations/fal/image/crystal_upscaler.py +0 -173
- package/templates/api/src/boards/generators/implementations/fal/image/fal_ideogram_character.py +0 -227
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2.py +0 -203
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2_edit.py +0 -230
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2_pro.py +0 -204
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2_pro_edit.py +0 -221
- package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_kontext.py +0 -216
- package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_ultra.py +0 -197
- package/templates/api/src/boards/generators/implementations/fal/image/gemini_25_flash_image.py +0 -177
- package/templates/api/src/boards/generators/implementations/fal/image/gemini_25_flash_image_edit.py +0 -208
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_15_edit.py +0 -216
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_5.py +0 -177
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_edit_image.py +0 -182
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_mini.py +0 -167
- package/templates/api/src/boards/generators/implementations/fal/image/ideogram_character_edit.py +0 -299
- package/templates/api/src/boards/generators/implementations/fal/image/ideogram_v2.py +0 -190
- package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview.py +0 -191
- package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview_fast.py +0 -179
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana.py +0 -183
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_edit.py +0 -212
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_pro.py +0 -179
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_pro_edit.py +0 -226
- package/templates/api/src/boards/generators/implementations/fal/image/qwen_image.py +0 -249
- package/templates/api/src/boards/generators/implementations/fal/image/qwen_image_edit.py +0 -244
- package/templates/api/src/boards/generators/implementations/fal/image/reve_edit.py +0 -178
- package/templates/api/src/boards/generators/implementations/fal/image/reve_text_to_image.py +0 -155
- package/templates/api/src/boards/generators/implementations/fal/image/seedream_v45_text_to_image.py +0 -180
- package/templates/api/src/boards/generators/implementations/fal/utils.py +0 -61
- package/templates/api/src/boards/generators/implementations/fal/video/__init__.py +0 -77
- package/templates/api/src/boards/generators/implementations/fal/video/bytedance_seedance_v1_pro_text_to_video.py +0 -209
- package/templates/api/src/boards/generators/implementations/fal/video/creatify_lipsync.py +0 -161
- package/templates/api/src/boards/generators/implementations/fal/video/fal_bytedance_seedance_v1_pro_image_to_video.py +0 -222
- package/templates/api/src/boards/generators/implementations/fal/video/fal_minimax_hailuo_02_standard_text_to_video.py +0 -152
- package/templates/api/src/boards/generators/implementations/fal/video/fal_pixverse_lipsync.py +0 -197
- package/templates/api/src/boards/generators/implementations/fal/video/fal_sora_2_text_to_video.py +0 -173
- package/templates/api/src/boards/generators/implementations/fal/video/infinitalk.py +0 -221
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_ai_avatar_v2_pro.py +0 -168
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_ai_avatar_v2_standard.py +0 -159
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_image_to_video.py +0 -175
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_text_to_video.py +0 -168
- package/templates/api/src/boards/generators/implementations/fal/video/minimax_hailuo_2_3_pro_image_to_video.py +0 -153
- package/templates/api/src/boards/generators/implementations/fal/video/sora2_image_to_video.py +0 -172
- package/templates/api/src/boards/generators/implementations/fal/video/sora_2_image_to_video_pro.py +0 -175
- package/templates/api/src/boards/generators/implementations/fal/video/sora_2_text_to_video_pro.py +0 -163
- package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2.py +0 -167
- package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2_pro.py +0 -155
- package/templates/api/src/boards/generators/implementations/fal/video/veed_fabric_1_0.py +0 -180
- package/templates/api/src/boards/generators/implementations/fal/video/veed_lipsync.py +0 -174
- package/templates/api/src/boards/generators/implementations/fal/video/veo3.py +0 -194
- package/templates/api/src/boards/generators/implementations/fal/video/veo31.py +0 -190
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_fast.py +0 -190
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_fast_image_to_video.py +0 -191
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_first_last_frame_to_video.py +0 -187
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_image_to_video.py +0 -183
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_reference_to_video.py +0 -172
- package/templates/api/src/boards/generators/implementations/fal/video/wan_25_preview_image_to_video.py +0 -212
- package/templates/api/src/boards/generators/implementations/fal/video/wan_25_preview_text_to_video.py +0 -208
- package/templates/api/src/boards/generators/implementations/fal/video/wan_pro_image_to_video.py +0 -158
- package/templates/api/src/boards/generators/implementations/kie/__init__.py +0 -11
- package/templates/api/src/boards/generators/implementations/kie/base.py +0 -316
- package/templates/api/src/boards/generators/implementations/kie/image/__init__.py +0 -3
- package/templates/api/src/boards/generators/implementations/kie/image/nano_banana_edit.py +0 -190
- package/templates/api/src/boards/generators/implementations/kie/utils.py +0 -98
- package/templates/api/src/boards/generators/implementations/kie/video/__init__.py +0 -8
- package/templates/api/src/boards/generators/implementations/kie/video/veo3.py +0 -161
- package/templates/api/src/boards/generators/implementations/openai/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/openai/audio/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/openai/audio/whisper.py +0 -69
- package/templates/api/src/boards/generators/implementations/openai/image/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/openai/image/dalle3.py +0 -96
- package/templates/api/src/boards/generators/implementations/replicate/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/replicate/image/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/replicate/image/flux_pro.py +0 -88
- package/templates/api/src/boards/generators/implementations/replicate/video/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/replicate/video/lipsync.py +0 -73
- package/templates/api/src/boards/generators/loader.py +0 -253
- package/templates/api/src/boards/generators/registry.py +0 -114
- package/templates/api/src/boards/generators/resolution.py +0 -632
- package/templates/api/src/boards/generators/testmods/class_gen.py +0 -34
- package/templates/api/src/boards/generators/testmods/import_side_effect.py +0 -35
- package/templates/api/src/boards/graphql/__init__.py +0 -7
- package/templates/api/src/boards/graphql/access_control.py +0 -136
- package/templates/api/src/boards/graphql/mutations/root.py +0 -148
- package/templates/api/src/boards/graphql/queries/root.py +0 -116
- package/templates/api/src/boards/graphql/resolvers/__init__.py +0 -8
- package/templates/api/src/boards/graphql/resolvers/auth.py +0 -12
- package/templates/api/src/boards/graphql/resolvers/board.py +0 -1053
- package/templates/api/src/boards/graphql/resolvers/generation.py +0 -666
- package/templates/api/src/boards/graphql/resolvers/generator.py +0 -50
- package/templates/api/src/boards/graphql/resolvers/lineage.py +0 -381
- package/templates/api/src/boards/graphql/resolvers/upload.py +0 -463
- package/templates/api/src/boards/graphql/resolvers/user.py +0 -25
- package/templates/api/src/boards/graphql/schema.py +0 -81
- package/templates/api/src/boards/graphql/types/board.py +0 -102
- package/templates/api/src/boards/graphql/types/generation.py +0 -166
- package/templates/api/src/boards/graphql/types/generator.py +0 -17
- package/templates/api/src/boards/graphql/types/user.py +0 -47
- package/templates/api/src/boards/jobs/repository.py +0 -153
- package/templates/api/src/boards/logging.py +0 -195
- package/templates/api/src/boards/middleware.py +0 -339
- package/templates/api/src/boards/progress/__init__.py +0 -4
- package/templates/api/src/boards/progress/models.py +0 -25
- package/templates/api/src/boards/progress/publisher.py +0 -64
- package/templates/api/src/boards/py.typed +0 -0
- package/templates/api/src/boards/redis_pool.py +0 -118
- package/templates/api/src/boards/storage/__init__.py +0 -52
- package/templates/api/src/boards/storage/base.py +0 -363
- package/templates/api/src/boards/storage/config.py +0 -187
- package/templates/api/src/boards/storage/factory.py +0 -288
- package/templates/api/src/boards/storage/implementations/__init__.py +0 -27
- package/templates/api/src/boards/storage/implementations/gcs.py +0 -340
- package/templates/api/src/boards/storage/implementations/local.py +0 -201
- package/templates/api/src/boards/storage/implementations/s3.py +0 -294
- package/templates/api/src/boards/storage/implementations/supabase.py +0 -218
- package/templates/api/src/boards/tenant_isolation.py +0 -446
- package/templates/api/src/boards/validation.py +0 -262
- package/templates/api/src/boards/workers/__init__.py +0 -1
- package/templates/api/src/boards/workers/actors.py +0 -274
- package/templates/api/src/boards/workers/cli.py +0 -125
- package/templates/api/src/boards/workers/context.py +0 -348
- package/templates/api/src/boards/workers/middleware.py +0 -58
- package/templates/api/src/py.typed +0 -0
- package/templates/compose.web.yaml +0 -35
- package/templates/compose.yaml +0 -116
- package/templates/docker/env.example +0 -23
- package/templates/web/.env.example +0 -28
- package/templates/web/Dockerfile +0 -51
- package/templates/web/components.json +0 -22
- package/templates/web/imageLoader.js +0 -18
- package/templates/web/next-env.d.ts +0 -5
- package/templates/web/next.config.js +0 -36
- package/templates/web/package.json +0 -41
- package/templates/web/postcss.config.mjs +0 -7
- package/templates/web/public/favicon.ico +0 -0
- package/templates/web/src/app/boards/[boardId]/page.tsx +0 -353
- package/templates/web/src/app/globals.css +0 -123
- package/templates/web/src/app/layout.tsx +0 -31
- package/templates/web/src/app/lineage/[generationId]/page.tsx +0 -235
- package/templates/web/src/app/page.tsx +0 -35
- package/templates/web/src/app/providers.tsx +0 -18
- package/templates/web/src/components/boards/ArtifactInputSlots.tsx +0 -206
- package/templates/web/src/components/boards/ArtifactPreview.tsx +0 -466
- package/templates/web/src/components/boards/GenerationGrid.tsx +0 -282
- package/templates/web/src/components/boards/GenerationInput.tsx +0 -370
- package/templates/web/src/components/boards/GeneratorSelector.tsx +0 -272
- package/templates/web/src/components/boards/UploadArtifact.tsx +0 -563
- package/templates/web/src/components/header.tsx +0 -32
- package/templates/web/src/components/theme-provider.tsx +0 -10
- package/templates/web/src/components/theme-toggle.tsx +0 -75
- package/templates/web/src/components/ui/alert-dialog.tsx +0 -157
- package/templates/web/src/components/ui/button.tsx +0 -58
- package/templates/web/src/components/ui/card.tsx +0 -92
- package/templates/web/src/components/ui/dropdown-menu.tsx +0 -200
- package/templates/web/src/components/ui/navigation-menu.tsx +0 -168
- package/templates/web/src/components/ui/toast.tsx +0 -128
- package/templates/web/src/components/ui/toaster.tsx +0 -35
- package/templates/web/src/components/ui/use-toast.ts +0 -187
- package/templates/web/src/hooks/useGeneratorMRU.ts +0 -57
- package/templates/web/src/lib/utils.ts +0 -6
- package/templates/web/tsconfig.json +0 -41
|
@@ -1,632 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Artifact resolution utilities for converting Generation references to actual files.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import base64
|
|
6
|
-
import os
|
|
7
|
-
import tempfile
|
|
8
|
-
import uuid
|
|
9
|
-
from urllib.parse import urlparse
|
|
10
|
-
|
|
11
|
-
import aiofiles
|
|
12
|
-
import httpx
|
|
13
|
-
|
|
14
|
-
from ..logging import get_logger
|
|
15
|
-
from ..storage.base import StorageManager
|
|
16
|
-
from .artifacts import (
|
|
17
|
-
AudioArtifact,
|
|
18
|
-
ImageArtifact,
|
|
19
|
-
LoRArtifact,
|
|
20
|
-
TextArtifact,
|
|
21
|
-
VideoArtifact,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
logger = get_logger(__name__)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def _rewrite_storage_url(storage_url: str) -> str:
|
|
28
|
-
"""
|
|
29
|
-
Rewrite storage URL for Docker internal networking.
|
|
30
|
-
|
|
31
|
-
Similar to the Next.js imageLoader, this rewrites public API URLs
|
|
32
|
-
to internal Docker network URLs when running in containers.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
storage_url: The original storage URL
|
|
36
|
-
|
|
37
|
-
Returns:
|
|
38
|
-
str: Rewritten URL if internal_api_url is configured, otherwise original URL
|
|
39
|
-
"""
|
|
40
|
-
from ..config import settings
|
|
41
|
-
|
|
42
|
-
logger.debug(
|
|
43
|
-
"Checking URL rewriting configuration",
|
|
44
|
-
internal_api_url=settings.internal_api_url,
|
|
45
|
-
storage_url=storage_url[:100] if storage_url else None,
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
if not settings.internal_api_url:
|
|
49
|
-
logger.debug("No internal_api_url configured, skipping URL rewrite")
|
|
50
|
-
return storage_url
|
|
51
|
-
|
|
52
|
-
# Common patterns to replace (localhost and 127.0.0.1 with various ports)
|
|
53
|
-
# In Docker, the public URL is typically http://localhost:8800 or http://localhost:8088
|
|
54
|
-
# We need to replace it with the internal URL (http://api:8800)
|
|
55
|
-
replacements = [
|
|
56
|
-
("http://localhost:8800", settings.internal_api_url),
|
|
57
|
-
("http://127.0.0.1:8800", settings.internal_api_url),
|
|
58
|
-
("http://localhost:8088", settings.internal_api_url),
|
|
59
|
-
("http://127.0.0.1:8088", settings.internal_api_url),
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
rewritten_url = storage_url
|
|
63
|
-
for public_pattern, internal_url in replacements:
|
|
64
|
-
if public_pattern in storage_url:
|
|
65
|
-
rewritten_url = storage_url.replace(public_pattern, internal_url)
|
|
66
|
-
logger.info(
|
|
67
|
-
"Rewrote storage URL for internal Docker networking",
|
|
68
|
-
original_url=storage_url,
|
|
69
|
-
rewritten_url=rewritten_url,
|
|
70
|
-
)
|
|
71
|
-
break
|
|
72
|
-
|
|
73
|
-
return rewritten_url
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
async def resolve_artifact(
|
|
77
|
-
artifact: AudioArtifact | VideoArtifact | ImageArtifact | LoRArtifact,
|
|
78
|
-
) -> str:
|
|
79
|
-
"""
|
|
80
|
-
Resolve an artifact to a local file path that can be used by provider SDKs.
|
|
81
|
-
|
|
82
|
-
This function downloads the artifact from storage if needed and returns
|
|
83
|
-
a local file path that generators can pass to provider SDKs.
|
|
84
|
-
|
|
85
|
-
Args:
|
|
86
|
-
artifact: Artifact instance to resolve
|
|
87
|
-
|
|
88
|
-
Returns:
|
|
89
|
-
str: Local file path to the artifact content
|
|
90
|
-
|
|
91
|
-
Raises:
|
|
92
|
-
ValueError: If the artifact type is not supported for file resolution
|
|
93
|
-
httpx.HTTPError: If downloading the artifact fails
|
|
94
|
-
"""
|
|
95
|
-
if isinstance(artifact, TextArtifact):
|
|
96
|
-
# Text artifacts don't need file resolution - they contain content directly
|
|
97
|
-
raise ValueError(
|
|
98
|
-
"TextArtifact cannot be resolved to a file path - use artifact.content directly"
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
# Validate that storage_url is actually a URL (not a local file path)
|
|
102
|
-
# This prevents potential security issues with paths like /etc/passwd
|
|
103
|
-
parsed = urlparse(artifact.storage_url)
|
|
104
|
-
|
|
105
|
-
# Check if it's a valid URL with a scheme (http, https, s3, etc.)
|
|
106
|
-
if parsed.scheme in ("http", "https", "s3", "gs"):
|
|
107
|
-
# It's a remote URL, download it
|
|
108
|
-
return await download_artifact_to_temp(artifact)
|
|
109
|
-
|
|
110
|
-
# If no scheme, it might be a local file path
|
|
111
|
-
# Only allow this if the file actually exists (for backward compatibility)
|
|
112
|
-
if os.path.exists(artifact.storage_url):
|
|
113
|
-
logger.debug(
|
|
114
|
-
"Using local file path for artifact",
|
|
115
|
-
storage_url=artifact.storage_url,
|
|
116
|
-
)
|
|
117
|
-
return artifact.storage_url
|
|
118
|
-
|
|
119
|
-
# Download the file to a temporary location
|
|
120
|
-
return await download_artifact_to_temp(artifact)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
async def download_artifact_to_temp(
|
|
124
|
-
artifact: AudioArtifact | VideoArtifact | ImageArtifact | LoRArtifact,
|
|
125
|
-
) -> str:
|
|
126
|
-
"""
|
|
127
|
-
Download an artifact from its storage URL to a temporary file.
|
|
128
|
-
|
|
129
|
-
Args:
|
|
130
|
-
artifact: Artifact to download
|
|
131
|
-
|
|
132
|
-
Returns:
|
|
133
|
-
str: Path to the temporary file containing the artifact content
|
|
134
|
-
|
|
135
|
-
Raises:
|
|
136
|
-
httpx.HTTPError: If downloading fails
|
|
137
|
-
"""
|
|
138
|
-
# Determine file extension based on artifact type and format
|
|
139
|
-
extension = _get_file_extension(artifact)
|
|
140
|
-
|
|
141
|
-
# Create temporary file with appropriate extension (use random prefix for security)
|
|
142
|
-
random_id = uuid.uuid4().hex[:8]
|
|
143
|
-
temp_fd, temp_path = tempfile.mkstemp(suffix=extension, prefix=f"boards_artifact_{random_id}_")
|
|
144
|
-
|
|
145
|
-
# Set restrictive file permissions (owner read/write only: 0o600)
|
|
146
|
-
os.chmod(temp_path, 0o600)
|
|
147
|
-
|
|
148
|
-
try:
|
|
149
|
-
# Rewrite URL for Docker internal networking
|
|
150
|
-
download_url = _rewrite_storage_url(artifact.storage_url)
|
|
151
|
-
|
|
152
|
-
# Stream the download to avoid loading large files into memory
|
|
153
|
-
async with httpx.AsyncClient(timeout=300.0) as client:
|
|
154
|
-
logger.info(
|
|
155
|
-
"Attempting to download artifact",
|
|
156
|
-
original_url=artifact.storage_url,
|
|
157
|
-
download_url=download_url,
|
|
158
|
-
)
|
|
159
|
-
async with client.stream("GET", download_url) as response:
|
|
160
|
-
response.raise_for_status()
|
|
161
|
-
|
|
162
|
-
# Close the file descriptor returned by mkstemp and use aiofiles
|
|
163
|
-
os.close(temp_fd)
|
|
164
|
-
|
|
165
|
-
# Stream content to file using async I/O
|
|
166
|
-
total_bytes = 0
|
|
167
|
-
async with aiofiles.open(temp_path, "wb") as temp_file:
|
|
168
|
-
async for chunk in response.aiter_bytes(chunk_size=8192):
|
|
169
|
-
await temp_file.write(chunk)
|
|
170
|
-
total_bytes += len(chunk)
|
|
171
|
-
|
|
172
|
-
# Validate that we downloaded something
|
|
173
|
-
if total_bytes == 0:
|
|
174
|
-
raise ValueError("Downloaded file is empty")
|
|
175
|
-
|
|
176
|
-
logger.debug(
|
|
177
|
-
"Successfully downloaded artifact to temp file",
|
|
178
|
-
temp_path=temp_path,
|
|
179
|
-
size_bytes=total_bytes,
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
return temp_path
|
|
183
|
-
|
|
184
|
-
except Exception:
|
|
185
|
-
# Clean up the temporary file if download failed
|
|
186
|
-
try:
|
|
187
|
-
os.unlink(temp_path)
|
|
188
|
-
except FileNotFoundError:
|
|
189
|
-
pass
|
|
190
|
-
raise
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def _get_file_extension(
|
|
194
|
-
artifact: AudioArtifact | VideoArtifact | ImageArtifact | LoRArtifact,
|
|
195
|
-
) -> str:
|
|
196
|
-
"""
|
|
197
|
-
Get the appropriate file extension for an artifact based on its format.
|
|
198
|
-
|
|
199
|
-
Args:
|
|
200
|
-
artifact: Artifact to get extension for
|
|
201
|
-
|
|
202
|
-
Returns:
|
|
203
|
-
str: File extension including the dot (e.g., '.mp4', '.png')
|
|
204
|
-
"""
|
|
205
|
-
format_ext = artifact.format.lower()
|
|
206
|
-
|
|
207
|
-
# Add dot if not present
|
|
208
|
-
if not format_ext.startswith("."):
|
|
209
|
-
format_ext = f".{format_ext}"
|
|
210
|
-
|
|
211
|
-
return format_ext
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
def _decode_data_url(data_url: str) -> bytes:
|
|
215
|
-
"""
|
|
216
|
-
Decode a data URL to bytes.
|
|
217
|
-
|
|
218
|
-
Supports data URLs in the format: data:[<mediatype>][;base64],<data>
|
|
219
|
-
|
|
220
|
-
Args:
|
|
221
|
-
data_url: Data URL string (e.g., "data:image/png;base64,iVBORw0KGgo...")
|
|
222
|
-
|
|
223
|
-
Returns:
|
|
224
|
-
bytes: Decoded content
|
|
225
|
-
|
|
226
|
-
Raises:
|
|
227
|
-
ValueError: If data URL is malformed or empty
|
|
228
|
-
"""
|
|
229
|
-
if not data_url.startswith("data:"):
|
|
230
|
-
raise ValueError("Invalid data URL: must start with 'data:'")
|
|
231
|
-
|
|
232
|
-
# Split off the "data:" prefix
|
|
233
|
-
try:
|
|
234
|
-
# Format: data:[<mediatype>][;base64],<data>
|
|
235
|
-
header, data = data_url[5:].split(",", 1)
|
|
236
|
-
except ValueError as e:
|
|
237
|
-
raise ValueError("Invalid data URL format: missing comma separator") from e
|
|
238
|
-
|
|
239
|
-
if not data:
|
|
240
|
-
raise ValueError("Data URL contains no data after comma")
|
|
241
|
-
|
|
242
|
-
# Check if base64 encoded
|
|
243
|
-
is_base64 = ";base64" in header
|
|
244
|
-
|
|
245
|
-
if is_base64:
|
|
246
|
-
try:
|
|
247
|
-
decoded = base64.b64decode(data)
|
|
248
|
-
except Exception as e:
|
|
249
|
-
raise ValueError(f"Failed to decode base64 data: {e}") from e
|
|
250
|
-
else:
|
|
251
|
-
# URL-encoded data (rare for binary content)
|
|
252
|
-
from urllib.parse import unquote
|
|
253
|
-
|
|
254
|
-
decoded = unquote(data).encode("utf-8")
|
|
255
|
-
|
|
256
|
-
if len(decoded) == 0:
|
|
257
|
-
raise ValueError("Decoded data URL is empty")
|
|
258
|
-
|
|
259
|
-
logger.info(
|
|
260
|
-
"Successfully decoded data URL",
|
|
261
|
-
size_bytes=len(decoded),
|
|
262
|
-
is_base64=is_base64,
|
|
263
|
-
)
|
|
264
|
-
return decoded
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
async def download_from_url(url: str) -> bytes:
|
|
268
|
-
"""
|
|
269
|
-
Download content from a URL (typically a provider's temporary URL).
|
|
270
|
-
|
|
271
|
-
This is used to download generated content from providers like Replicate, OpenAI, etc.
|
|
272
|
-
before uploading to our permanent storage.
|
|
273
|
-
|
|
274
|
-
Supports both HTTP(S) URLs and data URLs (data:mime/type;base64,...)
|
|
275
|
-
|
|
276
|
-
Note: For very large files, consider using streaming downloads directly to storage
|
|
277
|
-
instead of loading into memory.
|
|
278
|
-
|
|
279
|
-
Args:
|
|
280
|
-
url: URL to download from (HTTP(S) or data URL)
|
|
281
|
-
|
|
282
|
-
Returns:
|
|
283
|
-
bytes: Downloaded content
|
|
284
|
-
|
|
285
|
-
Raises:
|
|
286
|
-
httpx.HTTPError: If download fails
|
|
287
|
-
ValueError: If downloaded content is empty or data URL is malformed
|
|
288
|
-
"""
|
|
289
|
-
logger.debug("Downloading content from URL", url=url[:50])
|
|
290
|
-
|
|
291
|
-
# Check if this is a data URL
|
|
292
|
-
if url.startswith("data:"):
|
|
293
|
-
return _decode_data_url(url)
|
|
294
|
-
|
|
295
|
-
# Stream download to avoid loading entire file into memory at once
|
|
296
|
-
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
297
|
-
async with client.stream("GET", url) as response:
|
|
298
|
-
response.raise_for_status()
|
|
299
|
-
|
|
300
|
-
# Collect chunks
|
|
301
|
-
chunks = []
|
|
302
|
-
total_bytes = 0
|
|
303
|
-
async for chunk in response.aiter_bytes(chunk_size=8192):
|
|
304
|
-
chunks.append(chunk)
|
|
305
|
-
total_bytes += len(chunk)
|
|
306
|
-
|
|
307
|
-
# Validate content
|
|
308
|
-
if total_bytes == 0:
|
|
309
|
-
raise ValueError(f"Downloaded file from {url} is empty")
|
|
310
|
-
|
|
311
|
-
logger.info(
|
|
312
|
-
"Successfully downloaded content",
|
|
313
|
-
url=url,
|
|
314
|
-
size_bytes=total_bytes,
|
|
315
|
-
)
|
|
316
|
-
return b"".join(chunks)
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
def _get_content_type_from_format(artifact_type: str, format: str) -> str:
|
|
320
|
-
"""
|
|
321
|
-
Get MIME content type from artifact type and format.
|
|
322
|
-
|
|
323
|
-
Args:
|
|
324
|
-
artifact_type: Type of artifact ('image', 'video', 'audio')
|
|
325
|
-
format: Format string (e.g., 'png', 'mp4', 'mp3')
|
|
326
|
-
|
|
327
|
-
Returns:
|
|
328
|
-
str: MIME content type
|
|
329
|
-
"""
|
|
330
|
-
format_lower = format.lower()
|
|
331
|
-
|
|
332
|
-
# Map common formats to content types
|
|
333
|
-
content_type_map = {
|
|
334
|
-
"image": {
|
|
335
|
-
"png": "image/png",
|
|
336
|
-
"jpg": "image/jpeg",
|
|
337
|
-
"jpeg": "image/jpeg",
|
|
338
|
-
"webp": "image/webp",
|
|
339
|
-
"gif": "image/gif",
|
|
340
|
-
},
|
|
341
|
-
"video": {
|
|
342
|
-
"mp4": "video/mp4",
|
|
343
|
-
"webm": "video/webm",
|
|
344
|
-
"mov": "video/quicktime",
|
|
345
|
-
},
|
|
346
|
-
"audio": {
|
|
347
|
-
"mp3": "audio/mpeg",
|
|
348
|
-
"wav": "audio/wav",
|
|
349
|
-
"ogg": "audio/ogg",
|
|
350
|
-
},
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
type_map = content_type_map.get(artifact_type, {})
|
|
354
|
-
return type_map.get(format_lower, "application/octet-stream")
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
async def store_image_result(
|
|
358
|
-
storage_manager: StorageManager,
|
|
359
|
-
generation_id: str,
|
|
360
|
-
tenant_id: str,
|
|
361
|
-
board_id: str,
|
|
362
|
-
storage_url: str,
|
|
363
|
-
format: str,
|
|
364
|
-
width: int | None = None,
|
|
365
|
-
height: int | None = None,
|
|
366
|
-
) -> ImageArtifact:
|
|
367
|
-
"""
|
|
368
|
-
Store an image result by downloading from provider URL and uploading to storage.
|
|
369
|
-
|
|
370
|
-
Args:
|
|
371
|
-
storage_manager: Storage manager instance
|
|
372
|
-
generation_id: ID of the generation
|
|
373
|
-
tenant_id: Tenant ID for storage isolation
|
|
374
|
-
board_id: Board ID for organization
|
|
375
|
-
storage_url: Provider's temporary URL to download from
|
|
376
|
-
format: Image format (png, jpg, etc.)
|
|
377
|
-
width: Image width in pixels (optional)
|
|
378
|
-
height: Image height in pixels (optional)
|
|
379
|
-
|
|
380
|
-
Returns:
|
|
381
|
-
ImageArtifact with permanent storage URL
|
|
382
|
-
|
|
383
|
-
Raises:
|
|
384
|
-
StorageException: If storage operation fails
|
|
385
|
-
httpx.HTTPError: If download fails
|
|
386
|
-
"""
|
|
387
|
-
logger.info(
|
|
388
|
-
"Storing image result",
|
|
389
|
-
generation_id=generation_id,
|
|
390
|
-
provider_url=storage_url[:50],
|
|
391
|
-
format=format,
|
|
392
|
-
)
|
|
393
|
-
|
|
394
|
-
# Download content from provider URL
|
|
395
|
-
content = await download_from_url(storage_url)
|
|
396
|
-
|
|
397
|
-
# Determine content type
|
|
398
|
-
content_type = _get_content_type_from_format("image", format)
|
|
399
|
-
|
|
400
|
-
# Upload to storage system
|
|
401
|
-
artifact_ref = await storage_manager.store_artifact(
|
|
402
|
-
artifact_id=generation_id,
|
|
403
|
-
content=content,
|
|
404
|
-
artifact_type="image",
|
|
405
|
-
content_type=content_type,
|
|
406
|
-
tenant_id=tenant_id,
|
|
407
|
-
board_id=board_id,
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
logger.info(
|
|
411
|
-
"Image stored successfully",
|
|
412
|
-
generation_id=generation_id,
|
|
413
|
-
storage_key=artifact_ref.storage_key,
|
|
414
|
-
storage_url=artifact_ref.storage_url[:50],
|
|
415
|
-
)
|
|
416
|
-
|
|
417
|
-
# Return artifact with our permanent storage URL
|
|
418
|
-
return ImageArtifact(
|
|
419
|
-
generation_id=generation_id,
|
|
420
|
-
storage_url=artifact_ref.storage_url,
|
|
421
|
-
width=width,
|
|
422
|
-
height=height,
|
|
423
|
-
format=format,
|
|
424
|
-
)
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
async def store_video_result(
|
|
428
|
-
storage_manager: StorageManager,
|
|
429
|
-
generation_id: str,
|
|
430
|
-
tenant_id: str,
|
|
431
|
-
board_id: str,
|
|
432
|
-
storage_url: str,
|
|
433
|
-
format: str,
|
|
434
|
-
width: int | None = None,
|
|
435
|
-
height: int | None = None,
|
|
436
|
-
duration: float | None = None,
|
|
437
|
-
fps: float | None = None,
|
|
438
|
-
) -> VideoArtifact:
|
|
439
|
-
"""
|
|
440
|
-
Store a video result by downloading from provider URL and uploading to storage.
|
|
441
|
-
|
|
442
|
-
Args:
|
|
443
|
-
storage_manager: Storage manager instance
|
|
444
|
-
generation_id: ID of the generation
|
|
445
|
-
tenant_id: Tenant ID for storage isolation
|
|
446
|
-
board_id: Board ID for organization
|
|
447
|
-
storage_url: Provider's temporary URL to download from
|
|
448
|
-
format: Video format (mp4, webm, etc.)
|
|
449
|
-
width: Video width in pixels (optional)
|
|
450
|
-
height: Video height in pixels (optional)
|
|
451
|
-
duration: Video duration in seconds (optional)
|
|
452
|
-
fps: Frames per second (optional)
|
|
453
|
-
|
|
454
|
-
Returns:
|
|
455
|
-
VideoArtifact with permanent storage URL
|
|
456
|
-
|
|
457
|
-
Raises:
|
|
458
|
-
StorageException: If storage operation fails
|
|
459
|
-
httpx.HTTPError: If download fails
|
|
460
|
-
"""
|
|
461
|
-
logger.info(
|
|
462
|
-
"Storing video result",
|
|
463
|
-
generation_id=generation_id,
|
|
464
|
-
provider_url=storage_url[:50],
|
|
465
|
-
format=format,
|
|
466
|
-
)
|
|
467
|
-
|
|
468
|
-
# Download content from provider URL
|
|
469
|
-
content = await download_from_url(storage_url)
|
|
470
|
-
|
|
471
|
-
# Determine content type
|
|
472
|
-
content_type = _get_content_type_from_format("video", format)
|
|
473
|
-
|
|
474
|
-
# Upload to storage system
|
|
475
|
-
artifact_ref = await storage_manager.store_artifact(
|
|
476
|
-
artifact_id=generation_id,
|
|
477
|
-
content=content,
|
|
478
|
-
artifact_type="video",
|
|
479
|
-
content_type=content_type,
|
|
480
|
-
tenant_id=tenant_id,
|
|
481
|
-
board_id=board_id,
|
|
482
|
-
)
|
|
483
|
-
|
|
484
|
-
logger.info(
|
|
485
|
-
"Video stored successfully",
|
|
486
|
-
generation_id=generation_id,
|
|
487
|
-
storage_key=artifact_ref.storage_key,
|
|
488
|
-
storage_url=artifact_ref.storage_url[:50],
|
|
489
|
-
)
|
|
490
|
-
|
|
491
|
-
# Return artifact with our permanent storage URL
|
|
492
|
-
return VideoArtifact(
|
|
493
|
-
generation_id=generation_id,
|
|
494
|
-
storage_url=artifact_ref.storage_url,
|
|
495
|
-
width=width,
|
|
496
|
-
height=height,
|
|
497
|
-
format=format,
|
|
498
|
-
duration=duration,
|
|
499
|
-
fps=fps,
|
|
500
|
-
)
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
async def store_audio_result(
|
|
504
|
-
storage_manager: StorageManager,
|
|
505
|
-
generation_id: str,
|
|
506
|
-
tenant_id: str,
|
|
507
|
-
board_id: str,
|
|
508
|
-
storage_url: str,
|
|
509
|
-
format: str,
|
|
510
|
-
duration: float | None = None,
|
|
511
|
-
sample_rate: int | None = None,
|
|
512
|
-
channels: int | None = None,
|
|
513
|
-
) -> AudioArtifact:
|
|
514
|
-
"""
|
|
515
|
-
Store an audio result by downloading from provider URL and uploading to storage.
|
|
516
|
-
|
|
517
|
-
Args:
|
|
518
|
-
storage_manager: Storage manager instance
|
|
519
|
-
generation_id: ID of the generation
|
|
520
|
-
tenant_id: Tenant ID for storage isolation
|
|
521
|
-
board_id: Board ID for organization
|
|
522
|
-
storage_url: Provider's temporary URL to download from
|
|
523
|
-
format: Audio format (mp3, wav, etc.)
|
|
524
|
-
duration: Audio duration in seconds (optional)
|
|
525
|
-
sample_rate: Sample rate in Hz (optional)
|
|
526
|
-
channels: Number of audio channels (optional)
|
|
527
|
-
|
|
528
|
-
Returns:
|
|
529
|
-
AudioArtifact with permanent storage URL
|
|
530
|
-
|
|
531
|
-
Raises:
|
|
532
|
-
StorageException: If storage operation fails
|
|
533
|
-
httpx.HTTPError: If download fails
|
|
534
|
-
"""
|
|
535
|
-
logger.info(
|
|
536
|
-
"Storing audio result",
|
|
537
|
-
generation_id=generation_id,
|
|
538
|
-
provider_url=storage_url[:50],
|
|
539
|
-
format=format,
|
|
540
|
-
)
|
|
541
|
-
|
|
542
|
-
# Download content from provider URL
|
|
543
|
-
content = await download_from_url(storage_url)
|
|
544
|
-
|
|
545
|
-
# Determine content type
|
|
546
|
-
content_type = _get_content_type_from_format("audio", format)
|
|
547
|
-
|
|
548
|
-
# Upload to storage system
|
|
549
|
-
artifact_ref = await storage_manager.store_artifact(
|
|
550
|
-
artifact_id=generation_id,
|
|
551
|
-
content=content,
|
|
552
|
-
artifact_type="audio",
|
|
553
|
-
content_type=content_type,
|
|
554
|
-
tenant_id=tenant_id,
|
|
555
|
-
board_id=board_id,
|
|
556
|
-
)
|
|
557
|
-
|
|
558
|
-
logger.info(
|
|
559
|
-
"Audio stored successfully",
|
|
560
|
-
generation_id=generation_id,
|
|
561
|
-
storage_key=artifact_ref.storage_key,
|
|
562
|
-
storage_url=artifact_ref.storage_url[:50],
|
|
563
|
-
)
|
|
564
|
-
|
|
565
|
-
# Return artifact with our permanent storage URL
|
|
566
|
-
return AudioArtifact(
|
|
567
|
-
generation_id=generation_id,
|
|
568
|
-
storage_url=artifact_ref.storage_url,
|
|
569
|
-
format=format,
|
|
570
|
-
duration=duration,
|
|
571
|
-
sample_rate=sample_rate,
|
|
572
|
-
channels=channels,
|
|
573
|
-
)
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
async def store_text_result(
|
|
577
|
-
storage_manager: StorageManager,
|
|
578
|
-
generation_id: str,
|
|
579
|
-
tenant_id: str,
|
|
580
|
-
board_id: str,
|
|
581
|
-
content: str,
|
|
582
|
-
format: str,
|
|
583
|
-
) -> TextArtifact:
|
|
584
|
-
"""
|
|
585
|
-
Store a text result by uploading to storage.
|
|
586
|
-
|
|
587
|
-
Args:
|
|
588
|
-
storage_manager: Storage manager instance
|
|
589
|
-
generation_id: ID of the generation
|
|
590
|
-
tenant_id: Tenant ID for storage isolation
|
|
591
|
-
board_id: Board ID for organization
|
|
592
|
-
content: Text content to store
|
|
593
|
-
format: Text format (plain, markdown, html, etc.)
|
|
594
|
-
|
|
595
|
-
Returns:
|
|
596
|
-
TextArtifact with permanent storage URL
|
|
597
|
-
|
|
598
|
-
Raises:
|
|
599
|
-
StorageException: If storage operation fails
|
|
600
|
-
httpx.HTTPError: If upload fails
|
|
601
|
-
"""
|
|
602
|
-
logger.info(
|
|
603
|
-
"Storing text result",
|
|
604
|
-
generation_id=generation_id,
|
|
605
|
-
content=content[:50],
|
|
606
|
-
format=format,
|
|
607
|
-
)
|
|
608
|
-
|
|
609
|
-
# Upload to storage system
|
|
610
|
-
artifact_ref = await storage_manager.store_artifact(
|
|
611
|
-
artifact_id=generation_id,
|
|
612
|
-
content=content.encode("utf-8"),
|
|
613
|
-
artifact_type="text",
|
|
614
|
-
content_type="text/plain",
|
|
615
|
-
tenant_id=tenant_id,
|
|
616
|
-
board_id=board_id,
|
|
617
|
-
)
|
|
618
|
-
|
|
619
|
-
logger.info(
|
|
620
|
-
"Text stored successfully",
|
|
621
|
-
generation_id=generation_id,
|
|
622
|
-
storage_key=artifact_ref.storage_key,
|
|
623
|
-
storage_url=artifact_ref.storage_url[:50],
|
|
624
|
-
)
|
|
625
|
-
|
|
626
|
-
# Return artifact with our permanent storage URL
|
|
627
|
-
return TextArtifact(
|
|
628
|
-
generation_id=generation_id,
|
|
629
|
-
storage_url=artifact_ref.storage_url,
|
|
630
|
-
content=content[:50],
|
|
631
|
-
format=format,
|
|
632
|
-
)
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
"""Test helper generator class for loader unit tests (class-based)."""
|
|
2
|
-
|
|
3
|
-
from pydantic import BaseModel
|
|
4
|
-
|
|
5
|
-
from ..base import BaseGenerator
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class _Input(BaseModel):
|
|
9
|
-
text: str = "hello"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class _Output(BaseModel):
|
|
13
|
-
text: str
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class ClassGen(BaseGenerator):
|
|
17
|
-
name = "class-gen"
|
|
18
|
-
artifact_type = "text"
|
|
19
|
-
description = "Test class-based generator"
|
|
20
|
-
|
|
21
|
-
def __init__(self, suffix: str | None = None):
|
|
22
|
-
self.suffix = suffix or "!"
|
|
23
|
-
|
|
24
|
-
def get_input_schema(self) -> type[_Input]:
|
|
25
|
-
return _Input
|
|
26
|
-
|
|
27
|
-
def get_output_schema(self) -> type[_Output]:
|
|
28
|
-
return _Output
|
|
29
|
-
|
|
30
|
-
async def generate(self, inputs: _Input, context) -> _Output: # type: ignore[override]
|
|
31
|
-
return _Output(text=f"{inputs.text}{self.suffix}")
|
|
32
|
-
|
|
33
|
-
async def estimate_cost(self, inputs: _Input) -> float: # type: ignore[override]
|
|
34
|
-
return 0.0
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"""Test helper module that registers on import (import-based)."""
|
|
2
|
-
|
|
3
|
-
from pydantic import BaseModel
|
|
4
|
-
|
|
5
|
-
from ..base import BaseGenerator
|
|
6
|
-
from ..registry import registry
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class _Input(BaseModel):
|
|
10
|
-
pass
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class _Output(BaseModel):
|
|
14
|
-
pass
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class ImportGen(BaseGenerator):
|
|
18
|
-
name = "import-gen"
|
|
19
|
-
artifact_type = "text"
|
|
20
|
-
description = "Test import-based generator"
|
|
21
|
-
|
|
22
|
-
def get_input_schema(self) -> type[_Input]:
|
|
23
|
-
return _Input
|
|
24
|
-
|
|
25
|
-
def get_output_schema(self) -> type[_Output]:
|
|
26
|
-
return _Output
|
|
27
|
-
|
|
28
|
-
async def generate(self, inputs: _Input, context) -> _Output: # type: ignore[override]
|
|
29
|
-
return _Output()
|
|
30
|
-
|
|
31
|
-
async def estimate_cost(self, inputs: _Input) -> float: # type: ignore[override]
|
|
32
|
-
return 0.0
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
registry.register(ImportGen())
|