diff --git a/src/components/nodes/GenerateVideoNode.tsx b/src/components/nodes/GenerateVideoNode.tsx index f80b1bb1..7b1abcd0 100644 --- a/src/components/nodes/GenerateVideoNode.tsx +++ b/src/components/nodes/GenerateVideoNode.tsx @@ -19,10 +19,12 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps state.updateNodeData); const providerSettings = useWorkflowStore((state) => state.providerSettings); + const generationsPath = useWorkflowStore((state) => state.generationsPath); const [externalModels, setExternalModels] = useState([]); const [isLoadingModels, setIsLoadingModels] = useState(false); const [modelsFetchError, setModelsFetchError] = useState(null); const [isBrowseDialogOpen, setIsBrowseDialogOpen] = useState(false); + const [isLoadingCarouselVideo, setIsLoadingCarouselVideo] = useState(false); // Get the current selected provider (default to fal since Gemini doesn't do video) const currentProvider: ProviderType = nodeData.selectedModel?.provider || "fal"; @@ -160,6 +162,77 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps { + if (!generationsPath) { + console.error("Generations path not configured"); + return null; + } + + try { + const response = await fetch("/api/load-generation", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + directoryPath: generationsPath, + imageId: videoId, + }), + }); + + if (!response.ok) { + console.error("Failed to load video:", await response.text()); + return null; + } + + const result = await response.json(); + return result.success ? (result.video || result.image) : null; + } catch (error) { + console.error("Error loading video:", error); + return null; + } + }, [generationsPath]); + + // Carousel navigation handlers + const handleCarouselPrevious = useCallback(async () => { + const history = nodeData.videoHistory || []; + if (history.length === 0 || isLoadingCarouselVideo) return; + + const currentIndex = nodeData.selectedVideoHistoryIndex || 0; + const newIndex = currentIndex === 0 ? history.length - 1 : currentIndex - 1; + const videoItem = history[newIndex]; + + setIsLoadingCarouselVideo(true); + const video = await loadVideoById(videoItem.id); + setIsLoadingCarouselVideo(false); + + if (video) { + updateNodeData(id, { + outputVideo: video, + selectedVideoHistoryIndex: newIndex, + }); + } + }, [id, nodeData.videoHistory, nodeData.selectedVideoHistoryIndex, isLoadingCarouselVideo, loadVideoById, updateNodeData]); + + const handleCarouselNext = useCallback(async () => { + const history = nodeData.videoHistory || []; + if (history.length === 0 || isLoadingCarouselVideo) return; + + const currentIndex = nodeData.selectedVideoHistoryIndex || 0; + const newIndex = (currentIndex + 1) % history.length; + const videoItem = history[newIndex]; + + setIsLoadingCarouselVideo(true); + const video = await loadVideoById(videoItem.id); + setIsLoadingCarouselVideo(false); + + if (video) { + updateNodeData(id, { + outputVideo: video, + selectedVideoHistoryIndex: newIndex, + }); + } + }, [id, nodeData.videoHistory, nodeData.selectedVideoHistoryIndex, isLoadingCarouselVideo, loadVideoById, updateNodeData]); + // Handle model selection from browse dialog const handleBrowseModelSelect = useCallback((model: ProviderModel) => { const newSelectedModel: SelectedModel = { @@ -189,6 +262,8 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps ), []); + const hasCarouselVideos = (nodeData.videoHistory || []).length > 1; + // Track previous status to detect error transitions const prevStatusRef = useRef(nodeData.status); @@ -324,6 +399,7 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps {/* Preview area */} {nodeData.outputVideo ? ( + <>
)} + {/* Loading overlay for carousel navigation */} + {isLoadingCarouselVideo && ( +
+ + + + +
+ )}
+ + {/* Carousel controls - only show if there are multiple videos */} + {hasCarouselVideos && ( +
+ + + {(nodeData.selectedVideoHistoryIndex || 0) + 1} / {(nodeData.videoHistory || []).length} + + +
+ )} + ) : (
{nodeData.status === "loading" ? ( diff --git a/src/lib/quickstart/validation.ts b/src/lib/quickstart/validation.ts index b3459bea..4c24a2ae 100644 --- a/src/lib/quickstart/validation.ts +++ b/src/lib/quickstart/validation.ts @@ -227,6 +227,8 @@ function createDefaultNodeData(type: NodeType): WorkflowNodeData { selectedModel: undefined, status: "idle", error: null, + videoHistory: [], + selectedVideoHistoryIndex: 0, }; case "llmGenerate": return {