diff --git a/components/Gallery.tsx b/components/Gallery.tsx index c4d8f78..5a96189 100644 --- a/components/Gallery.tsx +++ b/components/Gallery.tsx @@ -37,6 +37,59 @@ export function Gallery() { setEditModalOpen(true); }; + const [isGeneratingMetaVideo, setIsGeneratingMetaVideo] = React.useState(false); + + // Handle Meta AI video generation (image-to-video) + const handleGenerateMetaVideo = async (img: { data: string; prompt: string }) => { + if (!settings.metaCookies) { + alert("Please set your Meta AI Cookies in Settings first!"); + return; + } + + setIsGeneratingMetaVideo(true); + + try { + console.log("[Gallery] Starting Meta AI image-to-video..."); + + const res = await fetch('/api/meta/video', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + prompt: img.prompt || "Animate this image with natural movement", + cookies: settings.metaCookies, + imageBase64: img.data + }) + }); + + const data = await res.json(); + console.log("[Gallery] Meta video response:", data); + + if (data.success && data.videos?.length > 0) { + for (const video of data.videos) { + addVideo({ + id: crypto.randomUUID(), + url: video.url, + prompt: video.prompt || img.prompt, + thumbnail: img.data, + createdAt: Date.now() + }); + } + alert('🎬 Video generation complete! Scroll up to see your video.'); + } else { + throw new Error(data.error || 'No videos generated'); + } + } catch (error: any) { + console.error("[Gallery] Meta video error:", error); + let errorMessage = error.message || 'Video generation failed'; + if (errorMessage.includes('401') || errorMessage.includes('cookies')) { + errorMessage = '🔐 Your Meta AI cookies have expired. Please go to Settings and update them.'; + } + alert(errorMessage); + } finally { + setIsGeneratingMetaVideo(false); + } + }; + const handleGenerateVideo = async (prompt: string) => { if (!videoSource) return; @@ -359,93 +412,175 @@ export function Gallery() { - {/* Lightbox Modal */} + {/* Lightbox Modal - Split Panel Design */} {selectedIndex !== null && selectedImage && ( setSelectedIndex(null)} > {/* Close Button */} {/* Navigation Buttons */} {selectedIndex > 0 && ( )} {selectedIndex < gallery.length - 1 && ( )} - {/* Image Container */} + {/* Split Panel Container */} e.stopPropagation()} > + {/* Left: Image */} +
+ {selectedImage.prompt} +
- {selectedImage.prompt} + {/* Right: Controls Panel */} +
+ {/* Provider Badge */} + {selectedImage.provider && ( +
+ {selectedImage.provider} +
+ )} -
-

- {selectedImage.prompt} -

-
+ {/* Prompt Section */} +
+

Prompt

+

+ {selectedImage.prompt || "No prompt available"} +

+
+ + {/* Divider */} +
+ + {/* Actions */} +
+

Actions

+ + {/* Download */} - - Download Current + + Download Image + + {/* Generate Video - Show for all providers */} + + + {/* Remix/Edit - Only for Whisk */} {(!selectedImage.provider || selectedImage.provider === 'whisk') && ( )} + + {/* Use Prompt */} + + {/* Delete */} + +
+ + {/* Image Info */} +
+ {selectedImage.aspectRatio && ( +

Aspect Ratio: {selectedImage.aspectRatio}

+ )} +

Image {selectedIndex + 1} of {gallery.length}

diff --git a/components/PromptHero.tsx b/components/PromptHero.tsx index bcad359..1b448e4 100644 --- a/components/PromptHero.tsx +++ b/components/PromptHero.tsx @@ -3,7 +3,7 @@ import React, { useRef, useState, useEffect } from "react"; import { useStore, ReferenceCategory } from "@/lib/store"; import { cn } from "@/lib/utils"; -import { Sparkles, Maximize2, X, Hash, AlertTriangle, Upload, Zap, Brain, Settings, Settings2, Video } from "lucide-react"; +import { Sparkles, Maximize2, X, Hash, AlertTriangle, Upload, Zap, Brain, Settings, Settings2 } from "lucide-react"; const IMAGE_COUNTS = [1, 2, 4]; @@ -19,7 +19,7 @@ export function PromptHero() { } = useStore(); const [isGenerating, setLocalIsGenerating] = useState(false); - const [isGeneratingVideo, setIsGeneratingVideo] = useState(false); + const [uploadingRefs, setUploadingRefs] = useState>({}); const [errorNotification, setErrorNotification] = useState<{ message: string; type: 'error' | 'warning' } | null>(null); const textareaRef = useRef(null); @@ -205,85 +205,6 @@ export function PromptHero() { } }; - // Handle video generation (Meta AI only) - // If a subject reference is set, it will use image-to-video - // Otherwise, it will use text-to-video - const handleGenerateVideo = async () => { - let finalPrompt = prompt.trim(); - if (!finalPrompt || isGeneratingVideo || settings.provider !== 'meta') return; - - setIsGeneratingVideo(true); - setIsGenerating(true); - - try { - // Check if we have a subject reference for image-to-video - const subjectRefs = references.subject || []; - const imageBase64 = subjectRefs.length > 0 ? subjectRefs[0].thumbnail : undefined; - const mode = imageBase64 ? 'image-to-video' : 'text-to-video'; - - console.log(`[PromptHero] Starting Meta AI ${mode}...`); - - const res = await fetch('/api/meta/video', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - prompt: finalPrompt, - cookies: settings.metaCookies, - imageBase64: imageBase64 - }) - }); - - const responseText = await res.text(); - let data; - try { - data = JSON.parse(responseText); - } catch (e) { - console.error("API Error (Non-JSON response):", responseText.substring(0, 500)); - throw new Error(`Server Error: ${res.status} ${res.statusText}`); - } - - if (data.error) throw new Error(data.error); - - if (data.videos && data.videos.length > 0) { - // Add videos to store - for (const video of data.videos) { - useStore.getState().addVideo({ - id: crypto.randomUUID(), - url: video.url, - prompt: video.prompt || finalPrompt, - thumbnail: imageBase64, // Store the source image as thumbnail - createdAt: Date.now() - }); - } - // Show success notification - setErrorNotification({ - message: `🎬 Success! Generated ${data.videos.length} video(s) via ${mode}. Check the gallery.`, - type: 'warning' // Using warning for visibility (amber color) - }); - setTimeout(() => setErrorNotification(null), 5000); - } else { - throw new Error('No videos were generated'); - } - - } catch (e: any) { - console.error('[Video Gen]', e); - const errorMessage = e.message || ''; - - if (errorMessage.includes('401') || errorMessage.includes('cookies')) { - setShowCookieExpired(true); - } - - setErrorNotification({ - message: `🎬 Video Error: ${errorMessage}`, - type: 'error' - }); - setTimeout(() => setErrorNotification(null), 8000); - } finally { - setIsGeneratingVideo(false); - setIsGenerating(false); - } - }; - const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); @@ -567,19 +488,14 @@ export function PromptHero() {
{/* Left Controls: References */} - {/* For Meta AI: Only subject is enabled (for image-to-video), scene/style are disabled */} -
+ {/* For Meta AI: References are disabled (generate images first, video from gallery) */} +
{(['subject', 'scene', 'style'] as ReferenceCategory[]).map((cat) => { const refs = references[cat] || []; const hasRefs = refs.length > 0; const isUploading = uploadingRefs[cat]; - // For Meta AI: only enable subject (for image-to-video), disable scene/style - const isDisabledForMeta = settings.provider === 'meta' && cat !== 'subject'; return ( -
+
- - {/* Generate Video Button - Only for Meta AI */} - {settings.provider === 'meta' && ( - - )}
diff --git a/data/prompts.json b/data/prompts.json index 555be4b..435fb40 100644 --- a/data/prompts.json +++ b/data/prompts.json @@ -1,6 +1,6 @@ { - "last_updated": "2026-01-06T04:34:41.435Z", - "lastSync": 1767674081435, + "last_updated": "2026-01-06T07:32:15.350Z", + "lastSync": 1767684735350, "categories": { "style": [ "Illustration",