@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.
Files changed (52) hide show
  1. package/README.md +10 -0
  2. package/dist/index.js +15 -10
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/templates/api/.env.example +1 -1
  6. package/templates/api/ARTIFACT_RESOLUTION_GUIDE.md +148 -0
  7. package/templates/api/README.md +138 -6
  8. package/templates/api/config/generators.yaml +41 -7
  9. package/templates/api/docs/TESTING_LIVE_APIS.md +417 -0
  10. package/templates/api/pyproject.toml +49 -9
  11. package/templates/api/src/boards/__init__.py +1 -1
  12. package/templates/api/src/boards/generators/__init__.py +2 -2
  13. package/templates/api/src/boards/generators/artifact_resolution.py +380 -0
  14. package/templates/api/src/boards/generators/base.py +4 -0
  15. package/templates/api/src/boards/generators/implementations/__init__.py +4 -2
  16. package/templates/api/src/boards/generators/implementations/fal/__init__.py +25 -0
  17. package/templates/api/src/boards/generators/implementations/fal/audio/__init__.py +4 -0
  18. package/templates/api/src/boards/generators/implementations/fal/audio/minimax_music_v2.py +173 -0
  19. package/templates/api/src/boards/generators/implementations/fal/audio/minimax_speech_2_6_turbo.py +221 -0
  20. package/templates/api/src/boards/generators/implementations/fal/image/__init__.py +17 -0
  21. package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_kontext.py +216 -0
  22. package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_ultra.py +197 -0
  23. package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview.py +191 -0
  24. package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview_fast.py +179 -0
  25. package/templates/api/src/boards/generators/implementations/fal/image/nano_banana.py +183 -0
  26. package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_edit.py +212 -0
  27. package/templates/api/src/boards/generators/implementations/fal/utils.py +61 -0
  28. package/templates/api/src/boards/generators/implementations/fal/video/__init__.py +13 -0
  29. package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_text_to_video.py +168 -0
  30. package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2.py +167 -0
  31. package/templates/api/src/boards/generators/implementations/fal/video/veo31_first_last_frame_to_video.py +180 -0
  32. package/templates/api/src/boards/generators/implementations/openai/__init__.py +1 -0
  33. package/templates/api/src/boards/generators/implementations/openai/audio/__init__.py +1 -0
  34. package/templates/api/src/boards/generators/implementations/{audio → openai/audio}/whisper.py +9 -6
  35. package/templates/api/src/boards/generators/implementations/openai/image/__init__.py +1 -0
  36. package/templates/api/src/boards/generators/implementations/{image → openai/image}/dalle3.py +8 -5
  37. package/templates/api/src/boards/generators/implementations/replicate/__init__.py +1 -0
  38. package/templates/api/src/boards/generators/implementations/replicate/image/__init__.py +1 -0
  39. package/templates/api/src/boards/generators/implementations/{image → replicate/image}/flux_pro.py +8 -5
  40. package/templates/api/src/boards/generators/implementations/replicate/video/__init__.py +1 -0
  41. package/templates/api/src/boards/generators/implementations/{video → replicate/video}/lipsync.py +9 -6
  42. package/templates/api/src/boards/generators/resolution.py +72 -12
  43. package/templates/api/src/boards/jobs/repository.py +49 -0
  44. package/templates/api/src/boards/storage/factory.py +16 -6
  45. package/templates/api/src/boards/workers/actors.py +69 -5
  46. package/templates/api/src/boards/workers/context.py +173 -17
  47. package/templates/web/package.json +1 -1
  48. package/templates/web/src/components/boards/GenerationInput.tsx +154 -52
  49. package/templates/web/src/components/boards/GeneratorSelector.tsx +2 -1
  50. package/templates/api/src/boards/generators/implementations/audio/__init__.py +0 -3
  51. package/templates/api/src/boards/generators/implementations/image/__init__.py +0 -3
  52. 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 to determine if this generator needs artifact inputs
50
- const getArtifactSlots = (): ArtifactSlot[] => {
51
- if (!selectedGenerator) return [];
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
- return slots;
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
- <p className="text-sm text-gray-600">
245
- Generator-specific settings will appear here
246
- </p>
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: Record<string, unknown>;
11
+ inputSchema: JSONSchema7;
11
12
  }
12
13
 
13
14
  interface GeneratorSelectorProps {
@@ -1,3 +0,0 @@
1
- """Audio generation implementations."""
2
-
3
- from . import whisper
@@ -1,3 +0,0 @@
1
- """Image generation implementations."""
2
-
3
- from . import dalle3, flux_pro
@@ -1,3 +0,0 @@
1
- """Video generation implementations."""
2
-
3
- from . import lipsync