Spaces:
Running
Running
| import React, { useEffect, useState } from 'react'; | |
| import { Product } from '../types'; | |
| import { RefreshCw, AlertCircle, Sparkles, ScanFace, Timer, Play, ChevronDown, ChevronUp, Bug } from 'lucide-react'; | |
| interface ResultViewProps { | |
| originalImage: string; | |
| generatedImage: string | null; | |
| product: Product; | |
| loading: boolean; | |
| error: string | null; | |
| retryAfter: number | null; | |
| onRetake: () => void; | |
| onRetry: () => void; | |
| } | |
| const ResultView: React.FC<ResultViewProps> = ({ | |
| originalImage, | |
| generatedImage, | |
| product, | |
| loading, | |
| error, | |
| retryAfter, | |
| onRetake, | |
| onRetry | |
| }) => { | |
| const [progressStep, setProgressStep] = useState(0); | |
| const [timeLeft, setTimeLeft] = useState<number | null>(null); | |
| const [showDetails, setShowDetails] = useState(false); | |
| useEffect(() => { | |
| if (loading) { | |
| setProgressStep(0); | |
| const interval = setInterval(() => { | |
| setProgressStep(prev => (prev < 3 ? prev + 1 : prev)); | |
| }, 2000); | |
| return () => clearInterval(interval); | |
| } | |
| }, [loading]); | |
| useEffect(() => { | |
| if (retryAfter !== null) { | |
| setTimeLeft(retryAfter); | |
| const timer = setInterval(() => { | |
| setTimeLeft(prev => (prev !== null && prev > 0 ? prev - 1 : 0)); | |
| }, 1000); | |
| return () => clearInterval(timer); | |
| } else { | |
| setTimeLeft(null); | |
| } | |
| }, [retryAfter]); | |
| const loadingTexts = [ | |
| "Encoding micro-pixels...", | |
| "Analyzing garment...", | |
| "Synthesizing fit...", | |
| "Almost there..." | |
| ]; | |
| return ( | |
| <div className="min-h-screen bg-gray-50 flex flex-col"> | |
| <div className="flex-1 max-w-4xl mx-auto w-full p-4 flex flex-col md:flex-row gap-6 items-start justify-center pt-8"> | |
| {/* Sidebar Preview */} | |
| <div className="w-full md:w-1/3 bg-white p-3 rounded-2xl shadow-sm border border-gray-100 flex flex-col gap-2"> | |
| <h3 className="text-xs font-bold text-gray-400 uppercase tracking-widest px-1">Source</h3> | |
| <div className="aspect-[3/4] rounded-xl overflow-hidden bg-gray-100 relative"> | |
| <img src={originalImage} alt="Original" className="w-full h-full object-cover" /> | |
| <div className="absolute bottom-2 right-2 bg-white/90 backdrop-blur rounded-lg p-1 shadow-sm w-12 h-12 border border-gray-200"> | |
| <img src={product.imageUrl} className="w-full h-full object-contain mix-blend-multiply" alt="item" /> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Result Area */} | |
| <div className="w-full md:w-2/3 bg-white p-3 rounded-2xl shadow-lg border border-gray-100 flex flex-col gap-2 relative min-h-[400px]"> | |
| <h3 className="text-sm font-semibold text-brand-600 uppercase tracking-wide px-1 flex items-center gap-2"> | |
| <Sparkles className="w-4 h-4 text-brand-500" /> | |
| AI Studio | |
| </h3> | |
| <div className="aspect-[3/4] md:aspect-square rounded-xl overflow-hidden bg-slate-900 relative flex items-center justify-center"> | |
| {loading && ( | |
| <div className="absolute inset-0 flex flex-col items-center justify-center bg-slate-900/90 backdrop-blur-md z-10 text-white p-6 text-center"> | |
| <div className="relative mb-6"> | |
| <div className="w-16 h-16 border-4 border-brand-500/20 rounded-full animate-pulse"></div> | |
| <div className="absolute inset-0 w-16 h-16 border-4 border-brand-500 border-t-transparent rounded-full animate-spin"></div> | |
| <ScanFace className="absolute inset-0 w-8 h-8 m-auto text-brand-500 animate-pulse" /> | |
| </div> | |
| <h4 className="text-xl font-bold mb-2">Generating...</h4> | |
| <p className="text-brand-300 font-medium animate-pulse">{loadingTexts[progressStep]}</p> | |
| </div> | |
| )} | |
| {timeLeft !== null && ( | |
| <div className="absolute inset-0 flex flex-col items-center justify-center p-8 text-center bg-slate-900 text-white z-20"> | |
| <div className="bg-amber-500/20 p-5 rounded-full mb-6"> | |
| <Timer className="w-12 h-12 text-amber-400 animate-pulse" /> | |
| </div> | |
| <h4 className="text-2xl font-bold mb-3 text-amber-400">AI Cooling Down</h4> | |
| <p className="text-slate-400 text-sm mb-8 max-w-xs"> | |
| To prevent errors, please wait 2 minutes between attempts on the free tier. | |
| </p> | |
| <div className="text-6xl font-mono font-bold text-white mb-10"> | |
| {timeLeft}s | |
| </div> | |
| <div className="flex gap-4"> | |
| <button onClick={onRetake} className="px-6 py-3 bg-white/10 hover:bg-white/20 rounded-xl font-bold transition-all">Back</button> | |
| <button | |
| onClick={onRetry} | |
| disabled={timeLeft > 0} | |
| className={`px-8 py-3 rounded-xl font-bold flex items-center gap-2 transition-all ${timeLeft === 0 ? 'bg-brand-500 text-white' : 'bg-slate-700 text-slate-500 cursor-not-allowed'}`} | |
| > | |
| <Play className="w-4 h-4" /> | |
| Try Now | |
| </button> | |
| </div> | |
| </div> | |
| )} | |
| {error && timeLeft === null && ( | |
| <div className="absolute inset-0 flex flex-col items-center justify-center p-8 text-center bg-slate-800"> | |
| <AlertCircle className="w-10 h-10 text-red-500 mb-4" /> | |
| <h4 className="text-white font-bold text-lg mb-2">Service Delay</h4> | |
| <p className="text-gray-400 text-sm mb-6 px-4">The AI encountered a limit. Wait a moment then retry.</p> | |
| <button | |
| onClick={() => setShowDetails(!showDetails)} | |
| className="flex items-center gap-2 text-[10px] text-gray-500 uppercase tracking-widest mb-4 hover:text-gray-300 transition-colors" | |
| > | |
| <Bug className="w-3 h-3" /> | |
| Diagnostics | |
| {showDetails ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />} | |
| </button> | |
| {showDetails && ( | |
| <div className="bg-black/50 p-3 rounded-lg border border-white/10 text-left mb-6 max-h-32 overflow-y-auto w-full"> | |
| <code className="text-[10px] text-red-400 break-all">{error}</code> | |
| </div> | |
| )} | |
| <button onClick={onRetake} className="px-8 py-3 bg-white text-black rounded-xl font-bold hover:bg-gray-100 transition-colors">Go Back</button> | |
| </div> | |
| )} | |
| {!loading && !error && !timeLeft && generatedImage && ( | |
| <img src={generatedImage} alt="Result" className="w-full h-full object-contain animate-in fade-in zoom-in-95 duration-700" /> | |
| )} | |
| </div> | |
| {!loading && !error && !timeLeft && generatedImage && ( | |
| <div className="grid grid-cols-2 gap-3 mt-2"> | |
| <button onClick={onRetake} className="flex items-center justify-center gap-2 py-4 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded-xl font-bold transition-all"> | |
| <RefreshCw className="w-4 h-4" /> | |
| Retake | |
| </button> | |
| <button className="flex items-center justify-center gap-2 py-4 bg-brand-600 hover:bg-brand-700 text-white rounded-xl font-bold transition-all shadow-lg shadow-brand-200"> | |
| Save | |
| </button> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default ResultView; |