@weirdfingers/baseboards 0.4.1 → 0.5.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/package.json +1 -1
- package/templates/api/config/generators.yaml +9 -0
- package/templates/api/src/boards/__init__.py +1 -1
- package/templates/api/src/boards/config.py +7 -7
- package/templates/api/src/boards/generators/implementations/fal/image/__init__.py +2 -0
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_pro.py +179 -0
- package/templates/api/src/boards/generators/implementations/fal/video/__init__.py +4 -0
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_image_to_video.py +183 -0
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_reference_to_video.py +172 -0
- package/templates/api/src/boards/jobs/repository.py +3 -3
- package/templates/api/src/boards/workers/context.py +7 -3
- package/templates/web/package.json +1 -1
- package/templates/web/src/app/boards/[boardId]/page.tsx +44 -64
- package/templates/web/src/components/boards/ArtifactInputSlots.tsx +67 -3
- package/templates/web/src/components/boards/ArtifactPreview.tsx +292 -20
- package/templates/web/src/components/boards/GenerationGrid.tsx +51 -11
- package/templates/web/src/components/boards/GenerationInput.tsx +26 -23
- package/templates/web/src/components/boards/GeneratorSelector.tsx +10 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useGeneratorSelection } from "@weirdfingers/boards";
|
|
1
2
|
import { ArtifactPreview } from "./ArtifactPreview";
|
|
2
3
|
|
|
3
4
|
interface Generation {
|
|
@@ -19,6 +20,8 @@ export function GenerationGrid({
|
|
|
19
20
|
generations,
|
|
20
21
|
onGenerationClick,
|
|
21
22
|
}: GenerationGridProps) {
|
|
23
|
+
const { canArtifactBeAdded, addArtifactToSlot } = useGeneratorSelection();
|
|
24
|
+
|
|
22
25
|
if (generations.length === 0) {
|
|
23
26
|
return (
|
|
24
27
|
<div className="flex items-center justify-center py-12 text-gray-500">
|
|
@@ -27,19 +30,56 @@ export function GenerationGrid({
|
|
|
27
30
|
);
|
|
28
31
|
}
|
|
29
32
|
|
|
33
|
+
const handleDownload = (generation: Generation) => {
|
|
34
|
+
if (generation.storageUrl) {
|
|
35
|
+
window.open(generation.storageUrl, "_blank");
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const handlePreview = (generation: Generation) => {
|
|
40
|
+
// For now, use the same handler as onClick
|
|
41
|
+
onGenerationClick?.(generation);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handleAddToSlot = (generation: Generation) => {
|
|
45
|
+
const success = addArtifactToSlot({
|
|
46
|
+
id: generation.id,
|
|
47
|
+
artifactType: generation.artifactType,
|
|
48
|
+
storageUrl: generation.storageUrl,
|
|
49
|
+
thumbnailUrl: generation.thumbnailUrl,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (success) {
|
|
53
|
+
// Scroll to the generation input to show the user where the artifact was added
|
|
54
|
+
const generationInput = document.getElementById('generation-input');
|
|
55
|
+
if (generationInput) {
|
|
56
|
+
generationInput.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
30
61
|
return (
|
|
31
62
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
32
|
-
{generations.map((generation) =>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
63
|
+
{generations.map((generation) => {
|
|
64
|
+
const canAdd = generation.status === "COMPLETED" && canArtifactBeAdded(generation.artifactType);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<ArtifactPreview
|
|
68
|
+
key={generation.id}
|
|
69
|
+
artifactId={generation.id}
|
|
70
|
+
artifactType={generation.artifactType}
|
|
71
|
+
storageUrl={generation.storageUrl}
|
|
72
|
+
thumbnailUrl={generation.thumbnailUrl}
|
|
73
|
+
status={generation.status}
|
|
74
|
+
errorMessage={generation.errorMessage}
|
|
75
|
+
onClick={() => onGenerationClick?.(generation)}
|
|
76
|
+
onAddToSlot={() => handleAddToSlot(generation)}
|
|
77
|
+
canAddToSlot={canAdd}
|
|
78
|
+
onDownload={() => handleDownload(generation)}
|
|
79
|
+
onPreview={() => handlePreview(generation)}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
})}
|
|
43
83
|
</div>
|
|
44
84
|
);
|
|
45
85
|
}
|
|
@@ -4,8 +4,7 @@ import { useState, useMemo, useEffect } from "react";
|
|
|
4
4
|
import { Settings, ArrowUp, X } from "lucide-react";
|
|
5
5
|
import Image from "next/image";
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
parseGeneratorSchema,
|
|
7
|
+
useGeneratorSelection,
|
|
9
8
|
} from "@weirdfingers/boards";
|
|
10
9
|
import { GeneratorSelector, GeneratorInfo } from "./GeneratorSelector";
|
|
11
10
|
import { ArtifactInputSlots } from "./ArtifactInputSlots";
|
|
@@ -35,44 +34,49 @@ export function GenerationInput({
|
|
|
35
34
|
onSubmit,
|
|
36
35
|
isGenerating = false,
|
|
37
36
|
}: GenerationInputProps) {
|
|
38
|
-
const
|
|
39
|
-
|
|
37
|
+
const {
|
|
38
|
+
selectedGenerator,
|
|
39
|
+
setSelectedGenerator,
|
|
40
|
+
parsedSchema,
|
|
41
|
+
selectedArtifacts,
|
|
42
|
+
setSelectedArtifacts
|
|
43
|
+
} = useGeneratorSelection();
|
|
44
|
+
|
|
40
45
|
const [prompt, setPrompt] = useState("");
|
|
41
|
-
const [selectedArtifacts, setSelectedArtifacts] = useState<
|
|
42
|
-
Map<string, Generation>
|
|
43
|
-
>(new Map());
|
|
44
46
|
const [attachedImage, setAttachedImage] = useState<Generation | null>(null);
|
|
45
47
|
const [showSettings, setShowSettings] = useState(false);
|
|
46
48
|
const [settings, setSettings] = useState<Record<string, unknown>>({});
|
|
47
49
|
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
if (!selectedGenerator) {
|
|
51
|
-
|
|
50
|
+
// Initialize selected generator if not set
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!selectedGenerator && generators.length > 0) {
|
|
53
|
+
setSelectedGenerator(generators[0]);
|
|
52
54
|
}
|
|
53
|
-
|
|
54
|
-
}, [selectedGenerator]);
|
|
55
|
+
}, [generators, selectedGenerator, setSelectedGenerator]);
|
|
55
56
|
|
|
56
57
|
const artifactSlots = useMemo(() => {
|
|
58
|
+
if (!parsedSchema) return [];
|
|
57
59
|
return parsedSchema.artifactSlots.map((slot) => ({
|
|
58
60
|
name: slot.fieldName,
|
|
59
61
|
type: slot.artifactType,
|
|
60
62
|
required: slot.required,
|
|
61
63
|
}));
|
|
62
|
-
}, [parsedSchema
|
|
64
|
+
}, [parsedSchema]);
|
|
63
65
|
|
|
64
66
|
const needsArtifactInputs = artifactSlots.length > 0;
|
|
65
67
|
|
|
66
68
|
// Initialize settings with defaults when generator changes
|
|
67
69
|
const defaultSettings = useMemo(() => {
|
|
68
70
|
const defaults: Record<string, unknown> = {};
|
|
69
|
-
parsedSchema
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
if (parsedSchema) {
|
|
72
|
+
parsedSchema.settingsFields.forEach((field) => {
|
|
73
|
+
if (field.default !== undefined) {
|
|
74
|
+
defaults[field.fieldName] = field.default;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
74
78
|
return defaults;
|
|
75
|
-
}, [parsedSchema
|
|
79
|
+
}, [parsedSchema]);
|
|
76
80
|
|
|
77
81
|
// Reset settings when generator changes or defaultSettings change
|
|
78
82
|
useEffect(() => {
|
|
@@ -89,9 +93,8 @@ export function GenerationInput({
|
|
|
89
93
|
settings,
|
|
90
94
|
});
|
|
91
95
|
|
|
92
|
-
// Reset form
|
|
96
|
+
// Reset form (but keep selected artifacts and generator)
|
|
93
97
|
setPrompt("");
|
|
94
|
-
setSelectedArtifacts(new Map());
|
|
95
98
|
setAttachedImage(null);
|
|
96
99
|
setSettings(defaultSettings);
|
|
97
100
|
};
|
|
@@ -229,7 +232,7 @@ export function GenerationInput({
|
|
|
229
232
|
{/* Settings panel (collapsed by default) */}
|
|
230
233
|
{showSettings && (
|
|
231
234
|
<div className="px-4 py-4 border-t border-gray-200 bg-gray-50">
|
|
232
|
-
{parsedSchema.settingsFields.length === 0 ? (
|
|
235
|
+
{!parsedSchema || parsedSchema.settingsFields.length === 0 ? (
|
|
233
236
|
<p className="text-sm text-gray-600">
|
|
234
237
|
No additional settings available for this generator
|
|
235
238
|
</p>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Zap, Check } from "lucide-react";
|
|
4
4
|
import type { JSONSchema7 } from "@weirdfingers/boards";
|
|
5
|
+
import { useGeneratorSelection } from "@weirdfingers/boards";
|
|
5
6
|
import {
|
|
6
7
|
DropdownMenu,
|
|
7
8
|
DropdownMenuContent,
|
|
@@ -27,11 +28,19 @@ export function GeneratorSelector({
|
|
|
27
28
|
selectedGenerator,
|
|
28
29
|
onSelect,
|
|
29
30
|
}: GeneratorSelectorProps) {
|
|
31
|
+
const { setSelectedGenerator } = useGeneratorSelection();
|
|
32
|
+
|
|
30
33
|
const getGeneratorIcon = (name: string) => {
|
|
31
34
|
// You can customize icons per generator here
|
|
32
35
|
return <Zap className="w-4 h-4" />;
|
|
33
36
|
};
|
|
34
37
|
|
|
38
|
+
const handleSelect = (generator: GeneratorInfo) => {
|
|
39
|
+
// Update both local state and context
|
|
40
|
+
setSelectedGenerator(generator);
|
|
41
|
+
onSelect(generator);
|
|
42
|
+
};
|
|
43
|
+
|
|
35
44
|
return (
|
|
36
45
|
<DropdownMenu>
|
|
37
46
|
<DropdownMenuTrigger asChild>
|
|
@@ -57,7 +66,7 @@ export function GeneratorSelector({
|
|
|
57
66
|
{generators.map((generator) => (
|
|
58
67
|
<DropdownMenuItem
|
|
59
68
|
key={generator.name}
|
|
60
|
-
onClick={() =>
|
|
69
|
+
onClick={() => handleSelect(generator)}
|
|
61
70
|
className="px-4 py-3 flex items-start gap-3 cursor-pointer"
|
|
62
71
|
>
|
|
63
72
|
<div className="flex-shrink-0 mt-0.5">
|