Spaces:
Running
Running
File size: 8,309 Bytes
370c714 1b7ad81 370c714 3fd02c0 370c714 3fd02c0 370c714 3fd02c0 370c714 3fd02c0 1b7ad81 370c714 809dc87 370c714 3fd02c0 370c714 809dc87 370c714 3fd02c0 370c714 809dc87 3fd02c0 370c714 3fd02c0 370c714 809dc87 370c714 3fd02c0 370c714 3fd02c0 370c714 3fd02c0 370c714 809dc87 370c714 3fd02c0 809dc87 3fd02c0 809dc87 3fd02c0 809dc87 3fd02c0 809dc87 3fd02c0 370c714 3fd02c0 1b7ad81 3fd02c0 809dc87 1b7ad81 809dc87 1b7ad81 809dc87 1b7ad81 370c714 3fd02c0 370c714 3fd02c0 370c714 3fd02c0 370c714 809dc87 370c714 3fd02c0 809dc87 370c714 3fd02c0 |
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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
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; |