Spaces:
Running
Running
File size: 5,302 Bytes
dce7eca | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | "use client";
import { useState } from "react";
import { Upload, X, Image as ImageIcon, Sparkles } from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";
interface UploadSectionProps {
onGenerate: (prompt: string, images: File[]) => void;
isGenerating: boolean;
}
export default function UploadSection({ onGenerate, isGenerating }: UploadSectionProps) {
const [prompt, setPrompt] = useState("");
const [files, setFiles] = useState<File[]>([]);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
setFiles((prev) => [...prev, ...Array.from(e.target.files!)]);
}
};
const removeFile = (index: number) => {
setFiles((prev) => prev.filter((_, i) => i !== index));
};
return (
<div className="flex flex-col gap-6">
<div className="glass-card">
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
<Upload className="w-5 h-5 text-primary" />
Upload Reference Photos
</h3>
<div
className="border-2 border-dashed border-white/10 rounded-xl p-8 flex flex-col items-center justify-center gap-4 hover:border-primary/50 hover:bg-white/5 transition-all cursor-pointer relative"
onClick={() => document.getElementById("fileInput")?.click()}
>
<input
id="fileInput"
type="file"
multiple
accept="image/*"
className="hidden"
onChange={handleFileChange}
/>
<div className="w-12 h-12 bg-white/5 rounded-full flex items-center justify-center">
<Upload className="w-6 h-6 text-white/40" />
</div>
<div className="text-center">
<p className="font-medium">Click to browse or drag and drop</p>
<p className="text-sm text-white/30">(Supports multiple JPG/PNG photos)</p>
</div>
</div>
<div className="grid grid-cols-4 gap-2 mt-4">
<AnimatePresence>
{files.map((file, i) => (
<motion.div
key={i}
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0, opacity: 0 }}
className="relative aspect-square rounded-lg overflow-hidden group border border-white/10"
>
<img
src={URL.createObjectURL(file)}
alt="Preview"
className="w-full h-full object-cover"
/>
<button
onClick={(e) => { e.stopPropagation(); removeFile(i); }}
className="absolute top-1 right-1 p-1 bg-black/60 rounded-full opacity-0 group-hover:opacity-100 transition-opacity"
>
<X className="w-3 h-3" />
</button>
</motion.div>
))}
</AnimatePresence>
</div>
</div>
<div className="glass-card">
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
<Sparkles className="w-5 h-5 text-primary" />
Generation Details
</h3>
<label className="text-xs uppercase tracking-wider text-white/40 mb-2 block">
Style / Description (Optional)
</label>
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="e.g., A modern living room with warm sunset lighting and panoramic window views..."
className="w-full h-32 input-field resize-none mb-4"
/>
<button
onClick={() => onGenerate(prompt, files)}
disabled={isGenerating || (!prompt && files.length === 0)}
className="w-full btn-primary py-4 flex items-center justify-center gap-2"
>
{isGenerating ? (
<div className="flex items-center gap-2">
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
<span>Generating...</span>
</div>
) : (
<>
<Sparkles className="w-5 h-5" />
<span>Create 360° Panorama</span>
</>
)}
</button>
</div>
</div>
);
}
|