@weirdfingers/baseboards 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +887 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
- package/templates/README.md +120 -0
- package/templates/api/.env.example +62 -0
- package/templates/api/Dockerfile +32 -0
- package/templates/api/README.md +132 -0
- package/templates/api/alembic/env.py +106 -0
- package/templates/api/alembic/script.py.mako +28 -0
- package/templates/api/alembic/versions/20250101_000000_initial_schema.py +448 -0
- package/templates/api/alembic/versions/20251022_174729_remove_provider_name_from_generations.py +71 -0
- package/templates/api/alembic/versions/20251023_165852_switch_to_declarative_base_and_mapping.py +411 -0
- package/templates/api/alembic/versions/2025925_62735_add_seed_data_for_default_tenant.py +85 -0
- package/templates/api/alembic.ini +36 -0
- package/templates/api/config/generators.yaml +25 -0
- package/templates/api/config/storage_config.yaml +26 -0
- package/templates/api/docs/ADDING_GENERATORS.md +409 -0
- package/templates/api/docs/GENERATORS_API.md +502 -0
- package/templates/api/docs/MIGRATIONS.md +472 -0
- package/templates/api/docs/storage_providers.md +337 -0
- package/templates/api/pyproject.toml +165 -0
- package/templates/api/src/boards/__init__.py +10 -0
- package/templates/api/src/boards/api/app.py +171 -0
- package/templates/api/src/boards/api/auth.py +75 -0
- package/templates/api/src/boards/api/endpoints/__init__.py +3 -0
- package/templates/api/src/boards/api/endpoints/jobs.py +76 -0
- package/templates/api/src/boards/api/endpoints/setup.py +505 -0
- package/templates/api/src/boards/api/endpoints/sse.py +129 -0
- package/templates/api/src/boards/api/endpoints/storage.py +74 -0
- package/templates/api/src/boards/api/endpoints/tenant_registration.py +296 -0
- package/templates/api/src/boards/api/endpoints/webhooks.py +13 -0
- package/templates/api/src/boards/auth/__init__.py +15 -0
- package/templates/api/src/boards/auth/adapters/__init__.py +20 -0
- package/templates/api/src/boards/auth/adapters/auth0.py +220 -0
- package/templates/api/src/boards/auth/adapters/base.py +73 -0
- package/templates/api/src/boards/auth/adapters/clerk.py +172 -0
- package/templates/api/src/boards/auth/adapters/jwt.py +122 -0
- package/templates/api/src/boards/auth/adapters/none.py +102 -0
- package/templates/api/src/boards/auth/adapters/oidc.py +284 -0
- package/templates/api/src/boards/auth/adapters/supabase.py +110 -0
- package/templates/api/src/boards/auth/context.py +35 -0
- package/templates/api/src/boards/auth/factory.py +115 -0
- package/templates/api/src/boards/auth/middleware.py +221 -0
- package/templates/api/src/boards/auth/provisioning.py +129 -0
- package/templates/api/src/boards/auth/tenant_extraction.py +278 -0
- package/templates/api/src/boards/cli.py +354 -0
- package/templates/api/src/boards/config.py +116 -0
- package/templates/api/src/boards/database/__init__.py +7 -0
- package/templates/api/src/boards/database/cli.py +110 -0
- package/templates/api/src/boards/database/connection.py +252 -0
- package/templates/api/src/boards/database/models.py +19 -0
- package/templates/api/src/boards/database/seed_data.py +182 -0
- package/templates/api/src/boards/dbmodels/__init__.py +455 -0
- package/templates/api/src/boards/generators/__init__.py +57 -0
- package/templates/api/src/boards/generators/artifacts.py +53 -0
- package/templates/api/src/boards/generators/base.py +140 -0
- package/templates/api/src/boards/generators/implementations/__init__.py +12 -0
- package/templates/api/src/boards/generators/implementations/audio/__init__.py +3 -0
- package/templates/api/src/boards/generators/implementations/audio/whisper.py +66 -0
- package/templates/api/src/boards/generators/implementations/image/__init__.py +3 -0
- package/templates/api/src/boards/generators/implementations/image/dalle3.py +93 -0
- package/templates/api/src/boards/generators/implementations/image/flux_pro.py +85 -0
- package/templates/api/src/boards/generators/implementations/video/__init__.py +3 -0
- package/templates/api/src/boards/generators/implementations/video/lipsync.py +70 -0
- package/templates/api/src/boards/generators/loader.py +253 -0
- package/templates/api/src/boards/generators/registry.py +114 -0
- package/templates/api/src/boards/generators/resolution.py +515 -0
- package/templates/api/src/boards/generators/testmods/class_gen.py +34 -0
- package/templates/api/src/boards/generators/testmods/import_side_effect.py +35 -0
- package/templates/api/src/boards/graphql/__init__.py +7 -0
- package/templates/api/src/boards/graphql/access_control.py +136 -0
- package/templates/api/src/boards/graphql/mutations/root.py +136 -0
- package/templates/api/src/boards/graphql/queries/root.py +116 -0
- package/templates/api/src/boards/graphql/resolvers/__init__.py +8 -0
- package/templates/api/src/boards/graphql/resolvers/auth.py +12 -0
- package/templates/api/src/boards/graphql/resolvers/board.py +1055 -0
- package/templates/api/src/boards/graphql/resolvers/generation.py +889 -0
- package/templates/api/src/boards/graphql/resolvers/generator.py +50 -0
- package/templates/api/src/boards/graphql/resolvers/user.py +25 -0
- package/templates/api/src/boards/graphql/schema.py +81 -0
- package/templates/api/src/boards/graphql/types/board.py +102 -0
- package/templates/api/src/boards/graphql/types/generation.py +130 -0
- package/templates/api/src/boards/graphql/types/generator.py +17 -0
- package/templates/api/src/boards/graphql/types/user.py +47 -0
- package/templates/api/src/boards/jobs/repository.py +104 -0
- package/templates/api/src/boards/logging.py +195 -0
- package/templates/api/src/boards/middleware.py +339 -0
- package/templates/api/src/boards/progress/__init__.py +4 -0
- package/templates/api/src/boards/progress/models.py +25 -0
- package/templates/api/src/boards/progress/publisher.py +64 -0
- package/templates/api/src/boards/py.typed +0 -0
- package/templates/api/src/boards/redis_pool.py +118 -0
- package/templates/api/src/boards/storage/__init__.py +52 -0
- package/templates/api/src/boards/storage/base.py +363 -0
- package/templates/api/src/boards/storage/config.py +187 -0
- package/templates/api/src/boards/storage/factory.py +278 -0
- package/templates/api/src/boards/storage/implementations/__init__.py +27 -0
- package/templates/api/src/boards/storage/implementations/gcs.py +340 -0
- package/templates/api/src/boards/storage/implementations/local.py +201 -0
- package/templates/api/src/boards/storage/implementations/s3.py +294 -0
- package/templates/api/src/boards/storage/implementations/supabase.py +218 -0
- package/templates/api/src/boards/tenant_isolation.py +446 -0
- package/templates/api/src/boards/validation.py +262 -0
- package/templates/api/src/boards/workers/__init__.py +1 -0
- package/templates/api/src/boards/workers/actors.py +201 -0
- package/templates/api/src/boards/workers/cli.py +125 -0
- package/templates/api/src/boards/workers/context.py +188 -0
- package/templates/api/src/boards/workers/middleware.py +58 -0
- package/templates/api/src/py.typed +0 -0
- package/templates/compose.dev.yaml +39 -0
- package/templates/compose.yaml +109 -0
- package/templates/docker/env.example +23 -0
- package/templates/web/.env.example +28 -0
- package/templates/web/Dockerfile +51 -0
- package/templates/web/components.json +22 -0
- package/templates/web/imageLoader.js +18 -0
- package/templates/web/next-env.d.ts +5 -0
- package/templates/web/next.config.js +36 -0
- package/templates/web/package.json +37 -0
- package/templates/web/postcss.config.mjs +7 -0
- package/templates/web/public/favicon.ico +0 -0
- package/templates/web/src/app/boards/[boardId]/page.tsx +232 -0
- package/templates/web/src/app/globals.css +120 -0
- package/templates/web/src/app/layout.tsx +21 -0
- package/templates/web/src/app/page.tsx +35 -0
- package/templates/web/src/app/providers.tsx +18 -0
- package/templates/web/src/components/boards/ArtifactInputSlots.tsx +142 -0
- package/templates/web/src/components/boards/ArtifactPreview.tsx +125 -0
- package/templates/web/src/components/boards/GenerationGrid.tsx +45 -0
- package/templates/web/src/components/boards/GenerationInput.tsx +251 -0
- package/templates/web/src/components/boards/GeneratorSelector.tsx +89 -0
- package/templates/web/src/components/header.tsx +30 -0
- package/templates/web/src/components/ui/button.tsx +58 -0
- package/templates/web/src/components/ui/card.tsx +92 -0
- package/templates/web/src/components/ui/navigation-menu.tsx +168 -0
- package/templates/web/src/lib/utils.ts +6 -0
- package/templates/web/tsconfig.json +47 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base generator classes and interfaces for the Boards generators system.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Protocol, runtime_checkable
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from ..progress.models import ProgressUpdate
|
|
11
|
+
from .artifacts import (
|
|
12
|
+
AudioArtifact,
|
|
13
|
+
DigitalArtifact,
|
|
14
|
+
ImageArtifact,
|
|
15
|
+
TextArtifact,
|
|
16
|
+
VideoArtifact,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GeneratorResult(BaseModel):
|
|
21
|
+
"""All generators return a list of urls to the artifacts they produce."""
|
|
22
|
+
|
|
23
|
+
outputs: list[DigitalArtifact]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseGenerator(ABC):
|
|
27
|
+
"""
|
|
28
|
+
Abstract base class for all generators in the Boards system.
|
|
29
|
+
|
|
30
|
+
Generators define input/output schemas using Pydantic models and implement
|
|
31
|
+
the generation logic using provider SDKs directly.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# Class attributes that subclasses must define
|
|
35
|
+
name: str
|
|
36
|
+
artifact_type: str # 'image', 'video', 'audio', 'text', 'lora'
|
|
37
|
+
description: str
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def get_input_schema(self) -> type[BaseModel]:
|
|
41
|
+
"""
|
|
42
|
+
Return the Pydantic model class that defines the input schema for this generator.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Type[BaseModel]: Pydantic model class for input validation
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
async def generate(
|
|
51
|
+
self, inputs: BaseModel, context: "GeneratorExecutionContext"
|
|
52
|
+
) -> GeneratorResult:
|
|
53
|
+
"""
|
|
54
|
+
Execute the generation process using the provided inputs.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
inputs: Validated input data matching the input schema
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
BaseModel: Generated output matching the output schema
|
|
61
|
+
"""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
async def estimate_cost(self, inputs: BaseModel) -> float:
|
|
66
|
+
"""
|
|
67
|
+
Estimate the cost of running this generation in USD.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
inputs: Input data for cost estimation
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
float: Estimated cost in USD
|
|
74
|
+
"""
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
def __repr__(self) -> str:
|
|
78
|
+
return f"<{self.__class__.__name__}(name='{self.name}', type='{self.artifact_type}')>"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@runtime_checkable
|
|
82
|
+
class GeneratorExecutionContext(Protocol):
|
|
83
|
+
"""Typed protocol for the execution context passed to generators.
|
|
84
|
+
|
|
85
|
+
This protocol defines the interface that generators can use to interact
|
|
86
|
+
with storage, database, and progress tracking systems.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
async def resolve_artifact(self, artifact: DigitalArtifact) -> str:
|
|
90
|
+
"""Resolve an artifact to a local file path for use with provider SDKs."""
|
|
91
|
+
...
|
|
92
|
+
|
|
93
|
+
async def store_image_result(
|
|
94
|
+
self,
|
|
95
|
+
storage_url: str,
|
|
96
|
+
format: str,
|
|
97
|
+
width: int,
|
|
98
|
+
height: int,
|
|
99
|
+
) -> ImageArtifact:
|
|
100
|
+
"""Store an image result to permanent storage."""
|
|
101
|
+
...
|
|
102
|
+
|
|
103
|
+
async def store_video_result(
|
|
104
|
+
self,
|
|
105
|
+
storage_url: str,
|
|
106
|
+
format: str,
|
|
107
|
+
width: int,
|
|
108
|
+
height: int,
|
|
109
|
+
duration: float | None = None,
|
|
110
|
+
fps: float | None = None,
|
|
111
|
+
) -> VideoArtifact:
|
|
112
|
+
"""Store a video result to permanent storage."""
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
async def store_audio_result(
|
|
116
|
+
self,
|
|
117
|
+
storage_url: str,
|
|
118
|
+
format: str,
|
|
119
|
+
duration: float | None = None,
|
|
120
|
+
sample_rate: int | None = None,
|
|
121
|
+
channels: int | None = None,
|
|
122
|
+
) -> AudioArtifact:
|
|
123
|
+
"""Store an audio result to permanent storage."""
|
|
124
|
+
...
|
|
125
|
+
|
|
126
|
+
async def store_text_result(
|
|
127
|
+
self,
|
|
128
|
+
content: str,
|
|
129
|
+
format: str,
|
|
130
|
+
) -> TextArtifact:
|
|
131
|
+
"""Store a text result to permanent storage."""
|
|
132
|
+
...
|
|
133
|
+
|
|
134
|
+
async def publish_progress(self, update: ProgressUpdate) -> None:
|
|
135
|
+
"""Publish a progress update for this generation."""
|
|
136
|
+
...
|
|
137
|
+
|
|
138
|
+
async def set_external_job_id(self, external_id: str) -> None:
|
|
139
|
+
"""Set the external job ID from the provider (e.g., Replicate prediction ID)."""
|
|
140
|
+
...
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Built-in generator implementations for the Boards system.
|
|
3
|
+
|
|
4
|
+
This package contains example generators that demonstrate how to integrate
|
|
5
|
+
various AI services using their native SDKs.
|
|
6
|
+
|
|
7
|
+
Import this module to automatically register all built-in generators:
|
|
8
|
+
import boards.generators.implementations
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# Import all generator modules to trigger registration
|
|
12
|
+
from . import audio, image, video
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Whisper audio transcription using OpenAI API.
|
|
3
|
+
|
|
4
|
+
Demonstrates audio processing generator that outputs text.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
from ...artifacts import AudioArtifact
|
|
10
|
+
from ...base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WhisperInput(BaseModel):
|
|
14
|
+
"""Input schema for Whisper transcription."""
|
|
15
|
+
|
|
16
|
+
audio_source: AudioArtifact = Field(description="Audio file to transcribe")
|
|
17
|
+
language: str = Field(default="en", description="Language code (e.g., 'en', 'es', 'fr')")
|
|
18
|
+
prompt: str = Field(default="", description="Optional prompt to guide transcription")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class WhisperGenerator(BaseGenerator):
|
|
22
|
+
"""Whisper speech-to-text transcription using OpenAI API."""
|
|
23
|
+
|
|
24
|
+
name = "whisper"
|
|
25
|
+
artifact_type = "text"
|
|
26
|
+
description = "OpenAI Whisper - speech-to-text transcription"
|
|
27
|
+
|
|
28
|
+
def get_input_schema(self) -> type[WhisperInput]:
|
|
29
|
+
return WhisperInput
|
|
30
|
+
|
|
31
|
+
async def generate(
|
|
32
|
+
self, inputs: WhisperInput, context: GeneratorExecutionContext
|
|
33
|
+
) -> GeneratorResult:
|
|
34
|
+
"""Transcribe audio using OpenAI Whisper."""
|
|
35
|
+
try:
|
|
36
|
+
from openai import AsyncOpenAI
|
|
37
|
+
except ImportError as e:
|
|
38
|
+
raise ValueError("Required dependencies not available") from e
|
|
39
|
+
|
|
40
|
+
client = AsyncOpenAI()
|
|
41
|
+
|
|
42
|
+
# Resolve audio artifact to file path via context
|
|
43
|
+
audio_file_path = await context.resolve_artifact(inputs.audio_source)
|
|
44
|
+
|
|
45
|
+
# Use OpenAI SDK for transcription
|
|
46
|
+
with open(audio_file_path, "rb") as audio_file:
|
|
47
|
+
transcript = await client.audio.transcriptions.create(
|
|
48
|
+
model="whisper-1",
|
|
49
|
+
file=audio_file,
|
|
50
|
+
language=inputs.language,
|
|
51
|
+
prompt=inputs.prompt,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Create text artifact
|
|
55
|
+
text_artifact = await context.store_text_result(
|
|
56
|
+
content=transcript.text,
|
|
57
|
+
format="plain",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return GeneratorResult(outputs=[text_artifact])
|
|
61
|
+
|
|
62
|
+
async def estimate_cost(self, inputs: WhisperInput) -> float:
|
|
63
|
+
"""Estimate cost for Whisper transcription."""
|
|
64
|
+
# Whisper pricing is $0.006 per minute
|
|
65
|
+
duration_minutes = (inputs.audio_source.duration or 60) / 60
|
|
66
|
+
return duration_minutes * 0.006
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DALL-E 3 generator using OpenAI API.
|
|
3
|
+
|
|
4
|
+
Demonstrates integration with OpenAI's SDK for image generation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
from ...base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DallE3Input(BaseModel):
|
|
15
|
+
"""Input schema for DALL-E 3 image generation."""
|
|
16
|
+
|
|
17
|
+
prompt: str = Field(description="Text prompt for image generation")
|
|
18
|
+
size: str = Field(
|
|
19
|
+
default="1024x1024",
|
|
20
|
+
description="Image size",
|
|
21
|
+
pattern="^(1024x1024|1024x1792|1792x1024)$",
|
|
22
|
+
)
|
|
23
|
+
quality: str = Field(default="standard", description="Image quality", pattern="^(standard|hd)$")
|
|
24
|
+
style: str = Field(default="vivid", description="Image style", pattern="^(vivid|natural)$")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DallE3Generator(BaseGenerator):
|
|
28
|
+
"""DALL-E 3 image generator using OpenAI API."""
|
|
29
|
+
|
|
30
|
+
name = "dall-e-3"
|
|
31
|
+
artifact_type = "image"
|
|
32
|
+
description = "OpenAI's DALL-E 3 - advanced text-to-image generation"
|
|
33
|
+
|
|
34
|
+
def get_input_schema(self) -> type[DallE3Input]:
|
|
35
|
+
return DallE3Input
|
|
36
|
+
|
|
37
|
+
async def generate(
|
|
38
|
+
self, inputs: DallE3Input, context: GeneratorExecutionContext
|
|
39
|
+
) -> GeneratorResult:
|
|
40
|
+
"""Generate image using OpenAI DALL-E 3."""
|
|
41
|
+
# Check for API key
|
|
42
|
+
if not os.getenv("OPENAI_API_KEY"):
|
|
43
|
+
raise ValueError("API configuration invalid")
|
|
44
|
+
|
|
45
|
+
# Import SDK directly
|
|
46
|
+
try:
|
|
47
|
+
from openai import AsyncOpenAI
|
|
48
|
+
except ImportError as e:
|
|
49
|
+
raise ValueError("Required dependencies not available") from e
|
|
50
|
+
|
|
51
|
+
client = AsyncOpenAI()
|
|
52
|
+
|
|
53
|
+
# Use OpenAI SDK directly
|
|
54
|
+
response = await client.images.generate(
|
|
55
|
+
model="dall-e-3",
|
|
56
|
+
prompt=inputs.prompt,
|
|
57
|
+
size=inputs.size, # pyright: ignore[reportArgumentType]
|
|
58
|
+
quality=inputs.quality, # pyright: ignore[reportArgumentType]
|
|
59
|
+
style=inputs.style, # pyright: ignore[reportArgumentType]
|
|
60
|
+
n=1,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Get the generated image URL
|
|
64
|
+
if not response.data or not response.data[0].url:
|
|
65
|
+
raise ValueError("No image generated")
|
|
66
|
+
image_url = response.data[0].url
|
|
67
|
+
|
|
68
|
+
# Parse dimensions from size
|
|
69
|
+
width, height = map(int, inputs.size.split("x"))
|
|
70
|
+
|
|
71
|
+
# Store via context (downloads from OpenAI and uploads to our storage)
|
|
72
|
+
image_artifact = await context.store_image_result(
|
|
73
|
+
storage_url=image_url,
|
|
74
|
+
format="png", # DALL-E 3 outputs PNG
|
|
75
|
+
width=width,
|
|
76
|
+
height=height,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return GeneratorResult(outputs=[image_artifact])
|
|
80
|
+
|
|
81
|
+
async def estimate_cost(self, inputs: DallE3Input) -> float:
|
|
82
|
+
"""Estimate cost for DALL-E 3 generation."""
|
|
83
|
+
# DALL-E 3 pricing varies by quality and size
|
|
84
|
+
if inputs.quality == "hd":
|
|
85
|
+
if inputs.size in ["1024x1792", "1792x1024"]:
|
|
86
|
+
return 0.080 # HD, non-square
|
|
87
|
+
else:
|
|
88
|
+
return 0.080 # HD, square
|
|
89
|
+
else: # standard quality
|
|
90
|
+
if inputs.size in ["1024x1792", "1792x1024"]:
|
|
91
|
+
return 0.040 # Standard, non-square
|
|
92
|
+
else:
|
|
93
|
+
return 0.040 # Standard, square
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FLUX.1.1 Pro generator using Replicate API.
|
|
3
|
+
|
|
4
|
+
This demonstrates the simple pattern for creating generators:
|
|
5
|
+
1. Define Pydantic input/output models
|
|
6
|
+
2. Implement generation logic using provider SDK directly
|
|
7
|
+
3. Register with the global registry
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from collections.abc import AsyncIterator
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel, Field
|
|
14
|
+
|
|
15
|
+
from ...base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FluxProInput(BaseModel):
|
|
19
|
+
"""Input schema for FLUX.1.1 Pro image generation."""
|
|
20
|
+
|
|
21
|
+
prompt: str = Field(description="Text prompt for image generation")
|
|
22
|
+
aspect_ratio: str = Field(
|
|
23
|
+
default="1:1",
|
|
24
|
+
description="Image aspect ratio",
|
|
25
|
+
pattern="^(1:1|16:9|21:9|2:3|3:2|4:5|5:4|9:16|9:21)$",
|
|
26
|
+
)
|
|
27
|
+
safety_tolerance: int = Field(default=2, ge=1, le=5, description="Safety tolerance level (1-5)")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FluxProGenerator(BaseGenerator):
|
|
31
|
+
"""FLUX.1.1 Pro image generator using Replicate."""
|
|
32
|
+
|
|
33
|
+
name = "flux-pro"
|
|
34
|
+
artifact_type = "image"
|
|
35
|
+
description = "FLUX.1.1 [pro] by Black Forest Labs - high-quality image generation"
|
|
36
|
+
|
|
37
|
+
def get_input_schema(self) -> type[FluxProInput]:
|
|
38
|
+
return FluxProInput
|
|
39
|
+
|
|
40
|
+
async def generate(
|
|
41
|
+
self, inputs: FluxProInput, context: GeneratorExecutionContext
|
|
42
|
+
) -> GeneratorResult:
|
|
43
|
+
"""Generate image using Replicate FLUX.1.1 Pro model."""
|
|
44
|
+
# Check for API key
|
|
45
|
+
if not os.getenv("REPLICATE_API_TOKEN"):
|
|
46
|
+
raise ValueError("API configuration invalid. Missing REPLICATE_API_TOKEN")
|
|
47
|
+
|
|
48
|
+
# Import SDK directly
|
|
49
|
+
try:
|
|
50
|
+
import replicate
|
|
51
|
+
from replicate.helpers import FileOutput
|
|
52
|
+
except ImportError as e:
|
|
53
|
+
raise ValueError("Required dependencies not available") from e
|
|
54
|
+
|
|
55
|
+
# Use Replicate SDK directly
|
|
56
|
+
prediction: FileOutput | AsyncIterator[FileOutput] = await replicate.async_run(
|
|
57
|
+
"black-forest-labs/flux-1.1-pro",
|
|
58
|
+
input={
|
|
59
|
+
"prompt": inputs.prompt,
|
|
60
|
+
"aspect_ratio": inputs.aspect_ratio,
|
|
61
|
+
"safety_tolerance": inputs.safety_tolerance,
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# If prediction is an async iterator, get the first item; else, use as is.
|
|
66
|
+
if isinstance(prediction, AsyncIterator):
|
|
67
|
+
file_output = await anext(prediction)
|
|
68
|
+
else:
|
|
69
|
+
file_output = prediction
|
|
70
|
+
|
|
71
|
+
output_url = file_output.url
|
|
72
|
+
|
|
73
|
+
image_artifact = await context.store_image_result(
|
|
74
|
+
storage_url=output_url,
|
|
75
|
+
format="png",
|
|
76
|
+
width=1024,
|
|
77
|
+
height=1024,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return GeneratorResult(outputs=[image_artifact])
|
|
81
|
+
|
|
82
|
+
async def estimate_cost(self, inputs: FluxProInput) -> float:
|
|
83
|
+
"""Estimate cost for FLUX.1.1 Pro generation."""
|
|
84
|
+
# FLUX.1.1 Pro typically costs around $0.055 per generation
|
|
85
|
+
return 0.055
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lipsync generator using Replicate API.
|
|
3
|
+
|
|
4
|
+
This demonstrates how generators can use multiple artifact inputs
|
|
5
|
+
with automatic artifact resolution.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from ...artifacts import AudioArtifact, VideoArtifact
|
|
11
|
+
from ...base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LipsyncInput(BaseModel):
|
|
15
|
+
"""Input schema for lipsync generation."""
|
|
16
|
+
|
|
17
|
+
audio_source: AudioArtifact = Field(description="Audio track for lip sync")
|
|
18
|
+
video_source: VideoArtifact = Field(description="Video to sync lips in")
|
|
19
|
+
prompt: str | None = Field(None, description="Optional prompt for generation")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LipsyncGenerator(BaseGenerator):
|
|
23
|
+
"""Lipsync generator that syncs lips in video to audio."""
|
|
24
|
+
|
|
25
|
+
name = "lipsync"
|
|
26
|
+
artifact_type = "video"
|
|
27
|
+
description = "Sync lips in video to match audio track"
|
|
28
|
+
|
|
29
|
+
def get_input_schema(self) -> type[LipsyncInput]:
|
|
30
|
+
return LipsyncInput
|
|
31
|
+
|
|
32
|
+
async def generate(
|
|
33
|
+
self, inputs: LipsyncInput, context: GeneratorExecutionContext
|
|
34
|
+
) -> GeneratorResult:
|
|
35
|
+
"""Generate lip-synced video."""
|
|
36
|
+
# Import SDK directly
|
|
37
|
+
try:
|
|
38
|
+
import replicate # type: ignore
|
|
39
|
+
except ImportError as e:
|
|
40
|
+
raise ValueError("Required dependencies not available") from e
|
|
41
|
+
|
|
42
|
+
# Resolve artifacts via context
|
|
43
|
+
audio_file = await context.resolve_artifact(inputs.audio_source)
|
|
44
|
+
video_file = await context.resolve_artifact(inputs.video_source)
|
|
45
|
+
|
|
46
|
+
# Use Replicate SDK directly with proper file handling
|
|
47
|
+
with open(audio_file, "rb") as audio_f, open(video_file, "rb") as video_f:
|
|
48
|
+
result = await replicate.async_run(
|
|
49
|
+
"cjwbw/wav2lip",
|
|
50
|
+
input={
|
|
51
|
+
"audio": audio_f,
|
|
52
|
+
"video": video_f,
|
|
53
|
+
},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Store output and create artifact via context
|
|
57
|
+
video_artifact = await context.store_video_result(
|
|
58
|
+
storage_url=str(result),
|
|
59
|
+
format="mp4",
|
|
60
|
+
width=inputs.video_source.width,
|
|
61
|
+
height=inputs.video_source.height,
|
|
62
|
+
duration=inputs.audio_source.duration,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return GeneratorResult(outputs=[video_artifact])
|
|
66
|
+
|
|
67
|
+
async def estimate_cost(self, inputs: LipsyncInput) -> float:
|
|
68
|
+
"""Estimate cost for lipsync generation."""
|
|
69
|
+
# Wav2Lip is typically free on Replicate, but let's add a small cost
|
|
70
|
+
return 0.01
|