@weirdfingers/baseboards 0.2.0 → 0.3.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 +10 -0
- package/dist/index.js +15 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/api/.env.example +1 -1
- package/templates/api/ARTIFACT_RESOLUTION_GUIDE.md +148 -0
- package/templates/api/README.md +138 -6
- package/templates/api/config/generators.yaml +41 -7
- package/templates/api/docs/TESTING_LIVE_APIS.md +417 -0
- package/templates/api/pyproject.toml +49 -9
- package/templates/api/src/boards/__init__.py +1 -1
- package/templates/api/src/boards/generators/__init__.py +2 -2
- package/templates/api/src/boards/generators/artifact_resolution.py +380 -0
- package/templates/api/src/boards/generators/base.py +4 -0
- package/templates/api/src/boards/generators/implementations/__init__.py +4 -2
- package/templates/api/src/boards/generators/implementations/fal/__init__.py +25 -0
- package/templates/api/src/boards/generators/implementations/fal/audio/__init__.py +4 -0
- package/templates/api/src/boards/generators/implementations/fal/audio/minimax_music_v2.py +173 -0
- package/templates/api/src/boards/generators/implementations/fal/audio/minimax_speech_2_6_turbo.py +221 -0
- package/templates/api/src/boards/generators/implementations/fal/image/__init__.py +17 -0
- package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_kontext.py +216 -0
- package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_ultra.py +197 -0
- package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview.py +191 -0
- package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview_fast.py +179 -0
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana.py +183 -0
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_edit.py +212 -0
- package/templates/api/src/boards/generators/implementations/fal/utils.py +61 -0
- package/templates/api/src/boards/generators/implementations/fal/video/__init__.py +13 -0
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_text_to_video.py +168 -0
- package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2.py +167 -0
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_first_last_frame_to_video.py +180 -0
- package/templates/api/src/boards/generators/implementations/openai/__init__.py +1 -0
- package/templates/api/src/boards/generators/implementations/openai/audio/__init__.py +1 -0
- package/templates/api/src/boards/generators/implementations/{audio → openai/audio}/whisper.py +9 -6
- package/templates/api/src/boards/generators/implementations/openai/image/__init__.py +1 -0
- package/templates/api/src/boards/generators/implementations/{image → openai/image}/dalle3.py +8 -5
- package/templates/api/src/boards/generators/implementations/replicate/__init__.py +1 -0
- package/templates/api/src/boards/generators/implementations/replicate/image/__init__.py +1 -0
- package/templates/api/src/boards/generators/implementations/{image → replicate/image}/flux_pro.py +8 -5
- package/templates/api/src/boards/generators/implementations/replicate/video/__init__.py +1 -0
- package/templates/api/src/boards/generators/implementations/{video → replicate/video}/lipsync.py +9 -6
- package/templates/api/src/boards/generators/resolution.py +72 -12
- package/templates/api/src/boards/jobs/repository.py +49 -0
- package/templates/api/src/boards/storage/factory.py +16 -6
- package/templates/api/src/boards/workers/actors.py +69 -5
- package/templates/api/src/boards/workers/context.py +173 -17
- package/templates/web/package.json +1 -1
- package/templates/web/src/components/boards/GenerationInput.tsx +154 -52
- package/templates/web/src/components/boards/GeneratorSelector.tsx +2 -1
- package/templates/api/src/boards/generators/implementations/audio/__init__.py +0 -3
- package/templates/api/src/boards/generators/implementations/image/__init__.py +0 -3
- package/templates/api/src/boards/generators/implementations/video/__init__.py +0 -3
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useState } from "react";
|
|
3
|
+
import { useState, useMemo, useEffect } from "react";
|
|
4
4
|
import { Settings, ArrowUp, X } from "lucide-react";
|
|
5
5
|
import Image from "next/image";
|
|
6
|
+
import {
|
|
7
|
+
ParsedGeneratorSchema,
|
|
8
|
+
parseGeneratorSchema,
|
|
9
|
+
} from "@weirdfingers/boards";
|
|
6
10
|
import { GeneratorSelector, GeneratorInfo } from "./GeneratorSelector";
|
|
7
11
|
import { ArtifactInputSlots } from "./ArtifactInputSlots";
|
|
8
12
|
|
|
@@ -13,12 +17,6 @@ interface Generation {
|
|
|
13
17
|
thumbnailUrl?: string | null;
|
|
14
18
|
}
|
|
15
19
|
|
|
16
|
-
interface ArtifactSlot {
|
|
17
|
-
name: string;
|
|
18
|
-
type: string;
|
|
19
|
-
required: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
20
|
interface GenerationInputProps {
|
|
23
21
|
generators: GeneratorInfo[];
|
|
24
22
|
availableArtifacts: Generation[];
|
|
@@ -45,53 +43,42 @@ export function GenerationInput({
|
|
|
45
43
|
>(new Map());
|
|
46
44
|
const [attachedImage, setAttachedImage] = useState<Generation | null>(null);
|
|
47
45
|
const [showSettings, setShowSettings] = useState(false);
|
|
46
|
+
const [settings, setSettings] = useState<Record<string, unknown>>({});
|
|
48
47
|
|
|
49
|
-
// Parse input schema
|
|
50
|
-
const
|
|
51
|
-
if (!selectedGenerator)
|
|
52
|
-
|
|
53
|
-
const schema = selectedGenerator.inputSchema as {
|
|
54
|
-
properties?: Record<
|
|
55
|
-
string,
|
|
56
|
-
{
|
|
57
|
-
type?: string;
|
|
58
|
-
anyOf?: Array<{ $ref?: string }>;
|
|
59
|
-
description?: string;
|
|
60
|
-
}
|
|
61
|
-
>;
|
|
62
|
-
required?: string[];
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
if (!schema.properties) return [];
|
|
66
|
-
|
|
67
|
-
const slots: ArtifactSlot[] = [];
|
|
68
|
-
|
|
69
|
-
for (const [key, value] of Object.entries(schema.properties)) {
|
|
70
|
-
// Check if this property references an artifact type
|
|
71
|
-
const isArtifact =
|
|
72
|
-
value.anyOf?.some((ref) => ref.$ref?.includes("Artifact")) || false;
|
|
73
|
-
|
|
74
|
-
if (isArtifact) {
|
|
75
|
-
// Determine artifact type from the key or description
|
|
76
|
-
let type = "video";
|
|
77
|
-
if (key.toLowerCase().includes("audio")) type = "audio";
|
|
78
|
-
if (key.toLowerCase().includes("video")) type = "video";
|
|
79
|
-
if (key.toLowerCase().includes("image")) type = "image";
|
|
80
|
-
|
|
81
|
-
slots.push({
|
|
82
|
-
name: key,
|
|
83
|
-
type,
|
|
84
|
-
required: schema.required?.includes(key) || false,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
48
|
+
// Parse input schema using the toolkit's schema parser
|
|
49
|
+
const parsedSchema = useMemo((): ParsedGeneratorSchema => {
|
|
50
|
+
if (!selectedGenerator) {
|
|
51
|
+
return { artifactSlots: [], promptField: null, settingsFields: [] };
|
|
87
52
|
}
|
|
53
|
+
return parseGeneratorSchema(selectedGenerator.inputSchema);
|
|
54
|
+
}, [selectedGenerator]);
|
|
88
55
|
|
|
89
|
-
|
|
90
|
-
|
|
56
|
+
const artifactSlots = useMemo(() => {
|
|
57
|
+
return parsedSchema.artifactSlots.map((slot) => ({
|
|
58
|
+
name: slot.fieldName,
|
|
59
|
+
type: slot.artifactType,
|
|
60
|
+
required: slot.required,
|
|
61
|
+
}));
|
|
62
|
+
}, [parsedSchema.artifactSlots]);
|
|
91
63
|
|
|
92
|
-
const artifactSlots = getArtifactSlots();
|
|
93
64
|
const needsArtifactInputs = artifactSlots.length > 0;
|
|
94
65
|
|
|
66
|
+
// Initialize settings with defaults when generator changes
|
|
67
|
+
const defaultSettings = useMemo(() => {
|
|
68
|
+
const defaults: Record<string, unknown> = {};
|
|
69
|
+
parsedSchema.settingsFields.forEach((field) => {
|
|
70
|
+
if (field.default !== undefined) {
|
|
71
|
+
defaults[field.fieldName] = field.default;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return defaults;
|
|
75
|
+
}, [parsedSchema.settingsFields]);
|
|
76
|
+
|
|
77
|
+
// Reset settings when generator changes or defaultSettings change
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
setSettings(defaultSettings);
|
|
80
|
+
}, [selectedGenerator, defaultSettings]);
|
|
81
|
+
|
|
95
82
|
const handleSubmit = () => {
|
|
96
83
|
if (!selectedGenerator || !prompt.trim()) return;
|
|
97
84
|
|
|
@@ -99,13 +86,14 @@ export function GenerationInput({
|
|
|
99
86
|
generatorName: selectedGenerator.name,
|
|
100
87
|
prompt: prompt.trim(),
|
|
101
88
|
artifacts: selectedArtifacts,
|
|
102
|
-
settings
|
|
89
|
+
settings,
|
|
103
90
|
});
|
|
104
91
|
|
|
105
92
|
// Reset form
|
|
106
93
|
setPrompt("");
|
|
107
94
|
setSelectedArtifacts(new Map());
|
|
108
95
|
setAttachedImage(null);
|
|
96
|
+
setSettings(defaultSettings);
|
|
109
97
|
};
|
|
110
98
|
|
|
111
99
|
const handleSelectArtifact = (
|
|
@@ -241,9 +229,123 @@ export function GenerationInput({
|
|
|
241
229
|
{/* Settings panel (collapsed by default) */}
|
|
242
230
|
{showSettings && (
|
|
243
231
|
<div className="px-4 py-4 border-t border-gray-200 bg-gray-50">
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
232
|
+
{parsedSchema.settingsFields.length === 0 ? (
|
|
233
|
+
<p className="text-sm text-gray-600">
|
|
234
|
+
No additional settings available for this generator
|
|
235
|
+
</p>
|
|
236
|
+
) : (
|
|
237
|
+
<div>
|
|
238
|
+
<h3 className="text-sm font-medium text-gray-900 mb-4">
|
|
239
|
+
Generator Settings
|
|
240
|
+
</h3>
|
|
241
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
242
|
+
{parsedSchema.settingsFields.map((field) => (
|
|
243
|
+
<div key={field.fieldName} className="space-y-1.5">
|
|
244
|
+
<label
|
|
245
|
+
htmlFor={field.fieldName}
|
|
246
|
+
className="block text-sm font-medium text-gray-700"
|
|
247
|
+
>
|
|
248
|
+
{field.title}
|
|
249
|
+
</label>
|
|
250
|
+
{field.description && (
|
|
251
|
+
<p className="text-xs text-gray-500">{field.description}</p>
|
|
252
|
+
)}
|
|
253
|
+
|
|
254
|
+
{/* Slider control */}
|
|
255
|
+
{field.type === "slider" && (
|
|
256
|
+
<div className="space-y-1">
|
|
257
|
+
<input
|
|
258
|
+
id={field.fieldName}
|
|
259
|
+
type="range"
|
|
260
|
+
min={field.min}
|
|
261
|
+
max={field.max}
|
|
262
|
+
step={field.step || (field.isInteger ? 1 : 0.01)}
|
|
263
|
+
value={
|
|
264
|
+
(settings[field.fieldName] as number) ?? field.default
|
|
265
|
+
}
|
|
266
|
+
onChange={(e) =>
|
|
267
|
+
setSettings({
|
|
268
|
+
...settings,
|
|
269
|
+
[field.fieldName]: field.isInteger
|
|
270
|
+
? parseInt(e.target.value)
|
|
271
|
+
: parseFloat(e.target.value),
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-orange-500"
|
|
275
|
+
/>
|
|
276
|
+
<div className="flex justify-between text-xs text-gray-600">
|
|
277
|
+
<span>{field.min}</span>
|
|
278
|
+
<span className="font-medium">
|
|
279
|
+
{String(settings[field.fieldName] ?? field.default ?? field.min)}
|
|
280
|
+
</span>
|
|
281
|
+
<span>{field.max}</span>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
)}
|
|
285
|
+
|
|
286
|
+
{/* Dropdown control */}
|
|
287
|
+
{field.type === "dropdown" && (
|
|
288
|
+
<select
|
|
289
|
+
id={field.fieldName}
|
|
290
|
+
value={(settings[field.fieldName] as string) ?? field.default}
|
|
291
|
+
onChange={(e) =>
|
|
292
|
+
setSettings({
|
|
293
|
+
...settings,
|
|
294
|
+
[field.fieldName]: e.target.value,
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
className="block w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-orange-500"
|
|
298
|
+
>
|
|
299
|
+
{field.options.map((option) => (
|
|
300
|
+
<option key={option} value={option}>
|
|
301
|
+
{option}
|
|
302
|
+
</option>
|
|
303
|
+
))}
|
|
304
|
+
</select>
|
|
305
|
+
)}
|
|
306
|
+
|
|
307
|
+
{/* Number input control */}
|
|
308
|
+
{field.type === "number" && (
|
|
309
|
+
<input
|
|
310
|
+
id={field.fieldName}
|
|
311
|
+
type="number"
|
|
312
|
+
min={field.min}
|
|
313
|
+
max={field.max}
|
|
314
|
+
step={field.isInteger ? 1 : "any"}
|
|
315
|
+
value={(settings[field.fieldName] as number) ?? field.default}
|
|
316
|
+
onChange={(e) =>
|
|
317
|
+
setSettings({
|
|
318
|
+
...settings,
|
|
319
|
+
[field.fieldName]: field.isInteger
|
|
320
|
+
? parseInt(e.target.value)
|
|
321
|
+
: parseFloat(e.target.value),
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
className="block w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-orange-500"
|
|
325
|
+
/>
|
|
326
|
+
)}
|
|
327
|
+
|
|
328
|
+
{/* Text input control */}
|
|
329
|
+
{field.type === "text" && (
|
|
330
|
+
<input
|
|
331
|
+
id={field.fieldName}
|
|
332
|
+
type="text"
|
|
333
|
+
value={(settings[field.fieldName] as string) ?? field.default ?? ""}
|
|
334
|
+
onChange={(e) =>
|
|
335
|
+
setSettings({
|
|
336
|
+
...settings,
|
|
337
|
+
[field.fieldName]: e.target.value,
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
pattern={field.pattern}
|
|
341
|
+
className="block w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-orange-500"
|
|
342
|
+
/>
|
|
343
|
+
)}
|
|
344
|
+
</div>
|
|
345
|
+
))}
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
)}
|
|
247
349
|
</div>
|
|
248
350
|
)}
|
|
249
351
|
</div>
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { Zap, Check } from "lucide-react";
|
|
5
|
+
import type { JSONSchema7 } from "@weirdfingers/boards";
|
|
5
6
|
|
|
6
7
|
export interface GeneratorInfo {
|
|
7
8
|
name: string;
|
|
8
9
|
description: string;
|
|
9
10
|
artifactType: string;
|
|
10
|
-
inputSchema:
|
|
11
|
+
inputSchema: JSONSchema7;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
interface GeneratorSelectorProps {
|