134 lines
4.9 KiB
TypeScript
134 lines
4.9 KiB
TypeScript
import React from 'react';
|
|
import { useLanguage } from '../src/contexts/LanguageContext';
|
|
import { cn } from '../src/lib/utils';
|
|
import { Card, CardContent } from '../src/components/ui/card';
|
|
import { Progress } from '../src/components/ui/progress';
|
|
import { Loader2, Sparkles, Music, Image, Video, CheckCircle } from 'lucide-react';
|
|
|
|
interface LoadingOverlayProps {
|
|
status: string;
|
|
message: string;
|
|
progress?: number;
|
|
}
|
|
|
|
const STEP_ICONS: Record<string, React.ReactNode> = {
|
|
'crawling': <Sparkles className="w-6 h-6" />,
|
|
'generating_text': <Sparkles className="w-6 h-6" />,
|
|
'generating_audio': <Music className="w-6 h-6" />,
|
|
'generating_poster': <Image className="w-6 h-6" />,
|
|
'generating_video': <Video className="w-6 h-6" />,
|
|
'completed': <CheckCircle className="w-6 h-6 text-green-500" />,
|
|
};
|
|
|
|
const LoadingOverlay: React.FC<LoadingOverlayProps> = ({ status, message, progress }) => {
|
|
const { t } = useLanguage();
|
|
|
|
let displayMessage = message;
|
|
|
|
switch (status) {
|
|
case 'crawling':
|
|
case 'generating_text':
|
|
displayMessage = t('loadingStep1');
|
|
break;
|
|
case 'generating_audio':
|
|
displayMessage = t('loadingStep2').replace('{style}', 'Auto');
|
|
break;
|
|
case 'generating_poster':
|
|
case 'generating_video':
|
|
displayMessage = t('loadingStep3');
|
|
break;
|
|
case 'completed':
|
|
displayMessage = t('loadingStep4');
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
const currentProgress = progress || (
|
|
status === 'crawling' ? 10 :
|
|
status === 'generating_text' ? 25 :
|
|
status === 'generating_audio' ? 50 :
|
|
status === 'generating_poster' ? 70 :
|
|
status === 'generating_video' ? 85 :
|
|
status === 'completed' ? 100 : 10
|
|
);
|
|
|
|
const currentIcon = STEP_ICONS[status] || <Sparkles className="w-6 h-6" />;
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/95 backdrop-blur-sm">
|
|
<Card className="w-full max-w-md mx-4 border-border/50 shadow-2xl">
|
|
<CardContent className="pt-8 pb-8 text-center">
|
|
|
|
{/* Animated Icon */}
|
|
<div className="relative w-24 h-24 mx-auto mb-8">
|
|
{/* Outer ring */}
|
|
<div className="absolute inset-0 rounded-full border-4 border-primary/20 animate-ping" style={{ animationDuration: '2s' }} />
|
|
|
|
{/* Spinning ring */}
|
|
<div className="absolute inset-0 rounded-full border-4 border-transparent border-t-primary border-r-accent animate-spin" style={{ animationDuration: '1.5s' }} />
|
|
|
|
{/* Center icon */}
|
|
<div className="absolute inset-3 rounded-full bg-gradient-to-br from-primary to-accent flex items-center justify-center shadow-lg">
|
|
<div className="text-primary-foreground animate-pulse">
|
|
{currentIcon}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h2 className="text-2xl font-bold text-foreground mb-2">
|
|
{t('loadingTitle')}
|
|
</h2>
|
|
<p className="text-muted-foreground text-sm mb-6">
|
|
{t('loadingSubtitle')}
|
|
</p>
|
|
|
|
{/* Progress Bar */}
|
|
<div className="mb-6">
|
|
<Progress value={currentProgress} className="h-2" />
|
|
<div className="flex items-center justify-between mt-2 text-xs text-muted-foreground">
|
|
<span>{t('textStyle') === '자막 스타일' ? '진행률' : 'Progress'}</span>
|
|
<span className="font-medium">{currentProgress}%</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Status Message */}
|
|
<div className="flex items-center justify-center gap-2 mb-4">
|
|
<Loader2 className="w-4 h-4 animate-spin text-primary" />
|
|
<p className="text-lg font-semibold gradient-text">
|
|
{displayMessage}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Steps indicator */}
|
|
<div className="flex items-center justify-center gap-2 mb-6">
|
|
{['generating_text', 'generating_audio', 'generating_poster', 'completed'].map((step, idx) => {
|
|
const isActive = status === step;
|
|
const isPast = ['generating_text', 'generating_audio', 'generating_poster', 'generating_video', 'completed'].indexOf(status) >
|
|
['generating_text', 'generating_audio', 'generating_poster', 'completed'].indexOf(step);
|
|
|
|
return (
|
|
<div
|
|
key={step}
|
|
className={cn(
|
|
"w-2 h-2 rounded-full transition-all",
|
|
isActive && "w-6 bg-primary",
|
|
isPast && "bg-primary/60",
|
|
!isActive && !isPast && "bg-muted-foreground/30"
|
|
)}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
<p className="text-xs text-muted-foreground">
|
|
{t('loadingWait')}
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LoadingOverlay;
|