Spaces:
Sleeping
Sleeping
| import { motion } from 'framer-motion'; | |
| import { getClipDownloadUrl } from '../api/client'; | |
| export default function DownloadGrid({ sessionId, clips }) { | |
| const completedClips = clips.filter(c => c.status === 'done'); | |
| const failedClips = clips.filter(c => c.status === 'error'); | |
| const downloadAll = () => { | |
| // Download each clip | |
| completedClips.forEach((clip, i) => { | |
| setTimeout(() => { | |
| const link = document.createElement('a'); | |
| link.href = getClipDownloadUrl(clip.clip_id); | |
| link.download = `viral_clip_${i + 1}.mp4`; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| }, i * 1000); // Stagger downloads | |
| }); | |
| }; | |
| return ( | |
| <div className="max-w-6xl mx-auto"> | |
| <div className="text-center mb-10"> | |
| <motion.div | |
| initial={{ scale: 0 }} | |
| animate={{ scale: 1 }} | |
| className="text-6xl mb-4" | |
| > | |
| 🎉 | |
| </motion.div> | |
| <h2 className="text-3xl font-bold mb-2">Your Viral Clips Are Ready!</h2> | |
| <p className="text-white/50"> | |
| {completedClips.length} clip{completedClips.length !== 1 ? 's' : ''} generated successfully | |
| {failedClips.length > 0 && ` • ${failedClips.length} failed`} | |
| </p> | |
| </div> | |
| {/* Download All Button */} | |
| {completedClips.length > 1 && ( | |
| <div className="text-center mb-8"> | |
| <button onClick={downloadAll} className="btn-primary text-lg px-8 py-4"> | |
| 📦 Download All Clips ({completedClips.length}) | |
| </button> | |
| </div> | |
| )} | |
| {/* Clip Cards Grid */} | |
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> | |
| {clips.map((clip, i) => ( | |
| <motion.div | |
| key={clip.clip_id} | |
| className="card overflow-hidden" | |
| initial={{ opacity: 0, y: 20 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| transition={{ delay: i * 0.1 }} | |
| > | |
| {/* Thumbnail / Preview */} | |
| <div className="aspect-[9/16] bg-gradient-to-br from-dark-900 to-dark-800 rounded-xl mb-4 | |
| flex items-center justify-center relative overflow-hidden"> | |
| <div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" /> | |
| <span className="text-4xl relative z-10"> | |
| {clip.status === 'done' ? '🎬' : clip.status === 'error' ? '❌' : '⏳'} | |
| </span> | |
| {/* Duration badge */} | |
| <div className="absolute bottom-3 right-3 px-2 py-1 bg-black/70 rounded text-xs font-mono z-10"> | |
| {clip.duration?.toFixed(0)}s | |
| </div> | |
| {/* Virality score badge */} | |
| <div className="absolute top-3 left-3 px-2 py-1 bg-green-500/20 text-green-400 | |
| rounded text-xs font-bold z-10 border border-green-500/30"> | |
| 🔥 {clip.virality_score?.toFixed(0)} | |
| </div> | |
| </div> | |
| {/* Card Info */} | |
| <h3 className="font-semibold text-sm mb-2 line-clamp-2"> | |
| {clip.title || `Viral Clip ${i + 1}`} | |
| </h3> | |
| <div className="flex items-center gap-2 text-xs text-white/40 mb-4"> | |
| <span>⏱️ {clip.duration?.toFixed(1)}s</span> | |
| <span>•</span> | |
| <span>1080×1920</span> | |
| <span>•</span> | |
| <span>60fps</span> | |
| </div> | |
| {/* Actions */} | |
| {clip.status === 'done' ? ( | |
| <div className="flex gap-2"> | |
| <a | |
| href={getClipDownloadUrl(clip.clip_id)} | |
| download={`viral_clip_${i + 1}.mp4`} | |
| className="flex-1 btn-primary text-center text-sm py-2" | |
| > | |
| ⬇️ Download MP4 | |
| </a> | |
| </div> | |
| ) : clip.status === 'error' ? ( | |
| <div className="text-center py-2 text-red-400/60 text-sm"> | |
| Generation failed | |
| </div> | |
| ) : ( | |
| <div className="text-center py-2 text-white/30 text-sm animate-pulse"> | |
| Processing... | |
| </div> | |
| )} | |
| </motion.div> | |
| ))} | |
| </div> | |
| {/* Start Over */} | |
| <div className="text-center mt-12"> | |
| <button | |
| onClick={() => window.location.reload()} | |
| className="btn-secondary" | |
| > | |
| 🔄 Create More Clips | |
| </button> | |
| </div> | |
| </div> | |
| ); | |
| } | |