feat: enable Subject upload for Meta AI video generation
- Subject button now enabled for Meta AI (Scene/Style still disabled) - Video button appears when Subject reference is uploaded - Uses Subject image for image-to-video generation - Added handleGenerateVideo() in PromptHero - Tooltip: 'Upload image to animate into video'
This commit is contained in:
parent
7aaa4c8166
commit
c2ee01b7b7
1 changed files with 117 additions and 4 deletions
|
|
@ -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 } from "lucide-react";
|
||||
import { Sparkles, Maximize2, X, Hash, AlertTriangle, Upload, Zap, Brain, Settings, Settings2, Video } from "lucide-react";
|
||||
|
||||
const IMAGE_COUNTS = [1, 2, 4];
|
||||
|
||||
|
|
@ -19,6 +19,8 @@ export function PromptHero() {
|
|||
} = useStore();
|
||||
|
||||
const [isGenerating, setLocalIsGenerating] = useState(false);
|
||||
const [isGeneratingVideo, setIsGeneratingVideo] = useState(false);
|
||||
const { addVideo } = useStore();
|
||||
|
||||
const [uploadingRefs, setUploadingRefs] = useState<Record<string, boolean>>({});
|
||||
const [errorNotification, setErrorNotification] = useState<{ message: string; type: 'error' | 'warning' } | null>(null);
|
||||
|
|
@ -205,6 +207,80 @@ export function PromptHero() {
|
|||
}
|
||||
};
|
||||
|
||||
// Handle video generation (Meta AI only) - uses Subject reference image
|
||||
const handleGenerateVideo = async () => {
|
||||
const subjectRefs = references.subject || [];
|
||||
if (subjectRefs.length === 0) {
|
||||
setErrorNotification({ message: '📷 Please upload a Subject image first', type: 'warning' });
|
||||
setTimeout(() => setErrorNotification(null), 4000);
|
||||
return;
|
||||
}
|
||||
|
||||
const finalPrompt = prompt.trim() || "Animate this image with natural, cinematic movement";
|
||||
|
||||
if (!settings.metaCookies) {
|
||||
setShowCookieExpired(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsGeneratingVideo(true);
|
||||
setIsGenerating(true);
|
||||
|
||||
try {
|
||||
const imageBase64 = subjectRefs[0].thumbnail;
|
||||
console.log('[PromptHero] Starting Meta AI image-to-video...');
|
||||
|
||||
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 data = await res.json();
|
||||
|
||||
if (data.error) throw new Error(data.error);
|
||||
|
||||
if (data.success && data.videos?.length > 0) {
|
||||
for (const video of data.videos) {
|
||||
addVideo({
|
||||
id: crypto.randomUUID(),
|
||||
url: video.url,
|
||||
prompt: video.prompt || finalPrompt,
|
||||
thumbnail: imageBase64,
|
||||
createdAt: Date.now()
|
||||
});
|
||||
}
|
||||
setErrorNotification({
|
||||
message: `🎬 Video generated! Check the gallery.`,
|
||||
type: 'warning'
|
||||
});
|
||||
setTimeout(() => setErrorNotification(null), 5000);
|
||||
} else {
|
||||
throw new Error('No videos generated');
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('[Video Gen]', e);
|
||||
const errorMessage = e.message || 'Video generation failed';
|
||||
|
||||
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();
|
||||
|
|
@ -488,18 +564,26 @@ export function PromptHero() {
|
|||
<div className="flex flex-col md:flex-row items-center justify-between gap-3 pt-1">
|
||||
|
||||
{/* Left Controls: References */}
|
||||
{/* For Meta AI: References are disabled (generate images first, video from gallery) */}
|
||||
<div className={cn("flex flex-wrap gap-2", settings.provider === 'meta' && "opacity-30 pointer-events-none grayscale")}>
|
||||
{/* For Meta AI: Only Subject is enabled (for video generation), Scene/Style disabled */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(['subject', 'scene', 'style'] as ReferenceCategory[]).map((cat) => {
|
||||
const refs = references[cat] || [];
|
||||
const hasRefs = refs.length > 0;
|
||||
const isUploading = uploadingRefs[cat];
|
||||
// For Meta AI: only Subject is enabled (for image-to-video), Scene/Style disabled
|
||||
const isDisabledForMeta = settings.provider === 'meta' && cat !== 'subject';
|
||||
return (
|
||||
<div key={cat} className="relative group">
|
||||
<div key={cat} className={cn(
|
||||
"relative group",
|
||||
isDisabledForMeta && "opacity-30 pointer-events-none grayscale"
|
||||
)}>
|
||||
<button
|
||||
onClick={() => toggleReference(cat)}
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={(e) => handleDrop(e, cat)}
|
||||
title={settings.provider === 'meta' && cat === 'subject'
|
||||
? "Upload image to animate into video"
|
||||
: undefined}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 rounded-md px-3 py-1.5 text-[10px] font-medium transition-all border relative overflow-hidden",
|
||||
hasRefs
|
||||
|
|
@ -645,6 +729,35 @@ export function PromptHero() {
|
|||
)}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Generate Video Button - Only for Meta AI when Subject is uploaded */}
|
||||
{settings.provider === 'meta' && (references.subject?.length ?? 0) > 0 && (
|
||||
<button
|
||||
onClick={handleGenerateVideo}
|
||||
disabled={isGenerating}
|
||||
className={cn(
|
||||
"relative overflow-hidden px-4 py-1.5 rounded-lg font-bold text-sm text-white shadow-lg transition-all active:scale-95 group border border-white/10",
|
||||
isGenerating
|
||||
? "bg-gray-700 cursor-not-allowed"
|
||||
: "bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-500 hover:to-cyan-500 hover:shadow-cyan-500/25"
|
||||
)}
|
||||
title="Animate the subject image into video"
|
||||
>
|
||||
<div className="relative z-10 flex items-center gap-1.5">
|
||||
{isGeneratingVideo ? (
|
||||
<>
|
||||
<div className="h-3 w-3 animate-spin rounded-full border-2 border-white border-t-transparent" />
|
||||
<span className="animate-pulse">Creating...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Video className="h-3 w-3 group-hover:scale-110 transition-transform" />
|
||||
<span>Video</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue