sushilideaclan01 commited on
Commit
3c7508e
·
1 Parent(s): dd2695a

feat: add cancel button to generation progress and fix stuck loading state

Browse files
frontend/components/generation/BatchProgress.tsx CHANGED
@@ -4,16 +4,18 @@ import React, { useState, useEffect } from "react";
4
  import { Card, CardContent } from "@/components/ui/Card";
5
  import { ProgressBar } from "@/components/ui/ProgressBar";
6
  import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
7
- import {
8
- Sparkles,
9
- Image as ImageIcon,
10
- Database,
11
- CheckCircle2,
12
  AlertCircle,
13
  Wand2,
14
  Zap,
15
- Package
 
16
  } from "lucide-react";
 
17
 
18
  interface BatchProgressProps {
19
  progress: number; // 0-100
@@ -57,6 +59,7 @@ export const BatchProgressComponent: React.FC<BatchProgressProps> = ({
57
  const [currentMessageIndex, setCurrentMessageIndex] = useState(0);
58
  const [elapsedTime, setElapsedTime] = useState(0);
59
  const [estimatedTimeRemaining, setEstimatedTimeRemaining] = useState<number | null>(null);
 
60
 
61
  const clampedProgress = Math.min(100, Math.max(0, progress));
62
  const isComplete = clampedProgress >= 100;
@@ -71,7 +74,7 @@ export const BatchProgressComponent: React.FC<BatchProgressProps> = ({
71
  const interval = setInterval(() => {
72
  const elapsed = Math.floor((Date.now() - generationStartTime) / 1000);
73
  setElapsedTime(elapsed);
74
-
75
  // Estimate time remaining based on progress
76
  if (clampedProgress > 5 && clampedProgress < 100) {
77
  const rate = clampedProgress / elapsed; // % per second
@@ -119,120 +122,129 @@ export const BatchProgressComponent: React.FC<BatchProgressProps> = ({
119
  <div className="sticky top-20 z-30 mb-6 animate-scale-in">
120
  <Card variant="glass" className="overflow-hidden shadow-xl border-2 border-blue-200/50 backdrop-blur-xl">
121
  <CardContent className="pt-6">
122
- <div className="space-y-6">
123
- {/* Header with animated icon */}
124
- <div className="flex items-center justify-between">
125
- <div className="flex items-center space-x-4">
126
- {isComplete ? (
127
- <div className="relative">
128
- <div className="absolute inset-0 bg-green-500 rounded-full animate-ping opacity-75"></div>
129
- <CheckCircle2 className="h-8 w-8 text-green-500 relative z-10" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  </div>
131
- ) : (
132
- <div className="relative">
133
- <div className="absolute inset-0 bg-gradient-to-r from-blue-500 to-cyan-500 rounded-full animate-ping opacity-20"></div>
134
- <div className="relative bg-gradient-to-r from-blue-500 to-cyan-500 rounded-full p-2">
135
- <Package className="h-5 w-5 text-white animate-pulse" />
 
136
  </div>
 
137
  </div>
138
  )}
139
- <div>
140
- <h3 className="text-lg font-bold bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">
141
- {isComplete ? "Batch Generation Complete!" : "Generating Batch Ads"}
142
- </h3>
143
- <p className="text-sm text-gray-600 mt-0.5 transition-all duration-500">
144
- {isComplete ? "All ads are ready!" : getCurrentMessage()}
145
- </p>
146
- {elapsedTime > 30 && !isComplete && (
147
- <p className="text-xs text-gray-500 mt-1">
148
- {Math.floor(elapsedTime / 60)}m {elapsedTime % 60}s elapsed
149
- </p>
150
- )}
151
- </div>
152
  </div>
153
- {estimatedTimeRemaining !== null && !isComplete && (
154
- <div className="text-right">
155
- <div className="flex items-center space-x-1 text-sm font-semibold text-gray-700">
156
- <Zap className="h-4 w-4 text-yellow-500 animate-pulse" />
157
- <span>~{estimatedTimeRemaining}s</span>
158
- </div>
159
- <p className="text-xs text-gray-500">remaining</p>
160
- </div>
161
- )}
162
- </div>
163
 
164
- {/* Batch Stats */}
165
- {totalCount > 0 && (
166
- <div className="grid grid-cols-3 gap-4">
167
- <div className="bg-gradient-to-br from-blue-50 to-cyan-50 rounded-xl p-4 border border-blue-200">
168
- <div className="flex items-center space-x-2 mb-1">
169
- <Package className="h-4 w-4 text-blue-600" />
170
- <p className="text-xs font-semibold text-gray-600">Total Ads</p>
 
 
 
 
171
  </div>
172
- <p className="text-2xl font-bold bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">
173
- {totalCount}
174
- </p>
175
- </div>
176
- <div className="bg-gradient-to-br from-cyan-50 to-pink-50 rounded-xl p-4 border border-cyan-200">
177
- <div className="flex items-center space-x-2 mb-1">
178
- <ImageIcon className="h-4 w-4 text-cyan-600" />
179
- <p className="text-xs font-semibold text-gray-600">Variations</p>
180
  </div>
181
- <p className="text-2xl font-bold bg-gradient-to-r from-cyan-600 to-pink-600 bg-clip-text text-transparent">
182
- {totalVariations}
183
- </p>
184
- </div>
185
- <div className="bg-gradient-to-br from-pink-50 to-purple-50 rounded-xl p-4 border border-pink-200">
186
- <div className="flex items-center space-x-2 mb-1">
187
- <Sparkles className="h-4 w-4 text-pink-600" />
188
- <p className="text-xs font-semibold text-gray-600">Current</p>
189
  </div>
190
- <p className="text-2xl font-bold bg-gradient-to-r from-pink-600 to-purple-600 bg-clip-text text-transparent">
191
- {currentAdNumber}/{totalCount}
192
- </p>
193
  </div>
194
- </div>
195
- )}
196
 
197
- {/* Progress Bar */}
198
- <div className="space-y-2">
199
- <div className="flex justify-between items-center">
200
- <span className="text-sm font-semibold text-gray-700">Overall Progress</span>
201
- <span className="text-sm font-bold bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">
202
- {Math.round(clampedProgress)}%
203
- </span>
 
 
 
 
 
 
 
 
 
 
 
204
  </div>
205
- <ProgressBar
206
- progress={clampedProgress}
207
- showPercentage={false}
208
- />
209
- {totalCount > 0 && currentIndex >= 0 && (
210
- <p className="text-xs text-gray-500 text-center mt-2">
211
- {currentAdNumber} of {totalCount} ads completed
212
- {imagesPerAd > 1 && ` • ${currentVariation} of ${totalVariations} variations`}
213
- </p>
214
- )}
215
- </div>
216
 
217
- {/* Success State */}
218
- {isComplete && (
219
- <div className="mt-4 p-4 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-200 rounded-xl animate-scale-in">
220
- <div className="flex items-center space-x-3">
221
- <CheckCircle2 className="h-6 w-6 text-green-600 flex-shrink-0" />
222
- <div>
223
- <p className="text-sm font-semibold text-green-900">
224
- Batch generation completed successfully!
225
- </p>
226
- <p className="text-xs text-green-700 mt-0.5">
227
- {totalCount > 0 ? `${totalCount} ads with ${totalVariations} total variations are ready!` : "All ads are ready to use"}
228
- </p>
 
229
  </div>
230
  </div>
231
- </div>
232
- )}
233
- </div>
234
- </CardContent>
235
- </Card>
236
  </div>
237
  );
238
  };
 
4
  import { Card, CardContent } from "@/components/ui/Card";
5
  import { ProgressBar } from "@/components/ui/ProgressBar";
6
  import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
7
+ import {
8
+ Sparkles,
9
+ Image as ImageIcon,
10
+ Database,
11
+ CheckCircle2,
12
  AlertCircle,
13
  Wand2,
14
  Zap,
15
+ Package,
16
+ X
17
  } from "lucide-react";
18
+ import { useGenerationStore } from "@/store/generationStore";
19
 
20
  interface BatchProgressProps {
21
  progress: number; // 0-100
 
59
  const [currentMessageIndex, setCurrentMessageIndex] = useState(0);
60
  const [elapsedTime, setElapsedTime] = useState(0);
61
  const [estimatedTimeRemaining, setEstimatedTimeRemaining] = useState<number | null>(null);
62
+ const reset = useGenerationStore((state) => state.reset);
63
 
64
  const clampedProgress = Math.min(100, Math.max(0, progress));
65
  const isComplete = clampedProgress >= 100;
 
74
  const interval = setInterval(() => {
75
  const elapsed = Math.floor((Date.now() - generationStartTime) / 1000);
76
  setElapsedTime(elapsed);
77
+
78
  // Estimate time remaining based on progress
79
  if (clampedProgress > 5 && clampedProgress < 100) {
80
  const rate = clampedProgress / elapsed; // % per second
 
122
  <div className="sticky top-20 z-30 mb-6 animate-scale-in">
123
  <Card variant="glass" className="overflow-hidden shadow-xl border-2 border-blue-200/50 backdrop-blur-xl">
124
  <CardContent className="pt-6">
125
+ <div className="space-y-6">
126
+ {/* Header with animated icon */}
127
+ <div className="flex items-center justify-between">
128
+ <div className="flex items-center space-x-4">
129
+ {isComplete ? (
130
+ <div className="relative">
131
+ <div className="absolute inset-0 bg-green-500 rounded-full animate-ping opacity-75"></div>
132
+ <CheckCircle2 className="h-8 w-8 text-green-500 relative z-10" />
133
+ </div>
134
+ ) : (
135
+ <div className="relative">
136
+ <div className="absolute inset-0 bg-gradient-to-r from-blue-500 to-cyan-500 rounded-full animate-ping opacity-20"></div>
137
+ <div className="relative bg-gradient-to-r from-blue-500 to-cyan-500 rounded-full p-2">
138
+ <Package className="h-5 w-5 text-white animate-pulse" />
139
+ </div>
140
+ </div>
141
+ )}
142
+ <div>
143
+ <h3 className="text-lg font-bold bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">
144
+ {isComplete ? "Batch Generation Complete!" : "Generating Batch Ads"}
145
+ </h3>
146
+ <p className="text-sm text-gray-600 mt-0.5 transition-all duration-500">
147
+ {isComplete ? "All ads are ready!" : getCurrentMessage()}
148
+ </p>
149
+ {elapsedTime > 30 && !isComplete && (
150
+ <p className="text-xs text-gray-500 mt-1">
151
+ {Math.floor(elapsedTime / 60)}m {elapsedTime % 60}s elapsed
152
+ </p>
153
+ )}
154
  </div>
155
+ </div>
156
+ {estimatedTimeRemaining !== null && !isComplete && (
157
+ <div className="text-right">
158
+ <div className="flex items-center space-x-1 text-sm font-semibold text-gray-700">
159
+ <Zap className="h-4 w-4 text-yellow-500 animate-pulse" />
160
+ <span>~{estimatedTimeRemaining}s</span>
161
  </div>
162
+ <p className="text-xs text-gray-500">remaining</p>
163
  </div>
164
  )}
165
+ {!isComplete && (
166
+ <button
167
+ onClick={() => reset()}
168
+ className="p-2 hover:bg-red-50 text-gray-400 hover:text-red-500 rounded-full transition-colors group"
169
+ title="Cancel Generation"
170
+ >
171
+ <X className="h-5 w-5 group-hover:rotate-90 transition-transform duration-300" />
172
+ </button>
173
+ )}
 
 
 
 
174
  </div>
 
 
 
 
 
 
 
 
 
 
175
 
176
+ {/* Batch Stats */}
177
+ {totalCount > 0 && (
178
+ <div className="grid grid-cols-3 gap-4">
179
+ <div className="bg-gradient-to-br from-blue-50 to-cyan-50 rounded-xl p-4 border border-blue-200">
180
+ <div className="flex items-center space-x-2 mb-1">
181
+ <Package className="h-4 w-4 text-blue-600" />
182
+ <p className="text-xs font-semibold text-gray-600">Total Ads</p>
183
+ </div>
184
+ <p className="text-2xl font-bold bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">
185
+ {totalCount}
186
+ </p>
187
  </div>
188
+ <div className="bg-gradient-to-br from-cyan-50 to-pink-50 rounded-xl p-4 border border-cyan-200">
189
+ <div className="flex items-center space-x-2 mb-1">
190
+ <ImageIcon className="h-4 w-4 text-cyan-600" />
191
+ <p className="text-xs font-semibold text-gray-600">Variations</p>
192
+ </div>
193
+ <p className="text-2xl font-bold bg-gradient-to-r from-cyan-600 to-pink-600 bg-clip-text text-transparent">
194
+ {totalVariations}
195
+ </p>
196
  </div>
197
+ <div className="bg-gradient-to-br from-pink-50 to-purple-50 rounded-xl p-4 border border-pink-200">
198
+ <div className="flex items-center space-x-2 mb-1">
199
+ <Sparkles className="h-4 w-4 text-pink-600" />
200
+ <p className="text-xs font-semibold text-gray-600">Current</p>
201
+ </div>
202
+ <p className="text-2xl font-bold bg-gradient-to-r from-pink-600 to-purple-600 bg-clip-text text-transparent">
203
+ {currentAdNumber}/{totalCount}
204
+ </p>
205
  </div>
 
 
 
206
  </div>
207
+ )}
 
208
 
209
+ {/* Progress Bar */}
210
+ <div className="space-y-2">
211
+ <div className="flex justify-between items-center">
212
+ <span className="text-sm font-semibold text-gray-700">Overall Progress</span>
213
+ <span className="text-sm font-bold bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">
214
+ {Math.round(clampedProgress)}%
215
+ </span>
216
+ </div>
217
+ <ProgressBar
218
+ progress={clampedProgress}
219
+ showPercentage={false}
220
+ />
221
+ {totalCount > 0 && currentIndex >= 0 && (
222
+ <p className="text-xs text-gray-500 text-center mt-2">
223
+ {currentAdNumber} of {totalCount} ads completed
224
+ {imagesPerAd > 1 && ` • ${currentVariation} of ${totalVariations} variations`}
225
+ </p>
226
+ )}
227
  </div>
 
 
 
 
 
 
 
 
 
 
 
228
 
229
+ {/* Success State */}
230
+ {isComplete && (
231
+ <div className="mt-4 p-4 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-200 rounded-xl animate-scale-in">
232
+ <div className="flex items-center space-x-3">
233
+ <CheckCircle2 className="h-6 w-6 text-green-600 flex-shrink-0" />
234
+ <div>
235
+ <p className="text-sm font-semibold text-green-900">
236
+ Batch generation completed successfully!
237
+ </p>
238
+ <p className="text-xs text-green-700 mt-0.5">
239
+ {totalCount > 0 ? `${totalCount} ads with ${totalVariations} total variations are ready!` : "All ads are ready to use"}
240
+ </p>
241
+ </div>
242
  </div>
243
  </div>
244
+ )}
245
+ </div>
246
+ </CardContent>
247
+ </Card>
 
248
  </div>
249
  );
250
  };
frontend/components/generation/GenerationProgress.tsx CHANGED
@@ -4,16 +4,18 @@ import React, { useState, useEffect } from "react";
4
  import { Card, CardContent } from "@/components/ui/Card";
5
  import { ProgressBar } from "@/components/ui/ProgressBar";
6
  import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
7
- import {
8
- Sparkles,
9
- Image as ImageIcon,
10
- Database,
11
- CheckCircle2,
12
  AlertCircle,
13
  Wand2,
14
- Zap
 
15
  } from "lucide-react";
16
  import type { GenerationProgress } from "@/types";
 
17
 
18
  interface GenerationProgressProps {
19
  progress: GenerationProgress;
@@ -21,30 +23,36 @@ interface GenerationProgressProps {
21
  }
22
 
23
  const STEPS = [
24
- { key: "copy", label: "Crafting Copy", icon: Sparkles, color: "from-blue-500 to-cyan-500", messages: [
25
- "Brainstorming compelling headlines...",
26
- "Writing persuasive ad copy...",
27
- "Polishing the perfect message...",
28
- "Adding psychological triggers...",
29
- ]},
30
- { key: "image", label: "Generating Images", icon: ImageIcon, color: "from-cyan-500 to-pink-500", messages: [
31
- "Creating stunning visuals...",
32
- "Bringing your vision to life...",
33
- "Rendering high-quality images...",
34
- "Adding creative flair...",
35
- "Generation may take a while...",
36
- "Almost there!",
37
- "Working on the perfect image...",
38
- "This is worth the wait...",
39
- "Crafting something amazing...",
40
- "Just a few more moments...",
41
- ]},
42
- { key: "saving", label: "Saving", icon: Database, color: "from-pink-500 to-purple-500", messages: [
43
- "Storing your creative...",
44
- "Securing your masterpiece...",
45
- "Almost done...",
46
- "Finalizing everything...",
47
- ]},
 
 
 
 
 
 
48
  ] as const;
49
 
50
  // Engaging messages for when stuck at high progress
@@ -65,6 +73,7 @@ export const GenerationProgressComponent: React.FC<GenerationProgressProps> = ({
65
  }) => {
66
  const [currentMessageIndex, setCurrentMessageIndex] = useState(0);
67
  const [elapsedTime, setElapsedTime] = useState(0);
 
68
 
69
  const stepProgress = {
70
  idle: 0,
@@ -118,12 +127,12 @@ export const GenerationProgressComponent: React.FC<GenerationProgressProps> = ({
118
  // Get message for current step
119
  const getStepMessage = () => {
120
  if (progress.message) return progress.message;
121
-
122
  // If stuck at high progress, show engaging messages
123
  if (isStuckAtHighProgress) {
124
  return ENGAGING_MESSAGES[currentMessageIndex];
125
  }
126
-
127
  const step = STEPS.find(s => s.key === progress.step);
128
  if (step && step.messages.length > 0) {
129
  return step.messages[currentMessageIndex % step.messages.length];
@@ -135,154 +144,161 @@ export const GenerationProgressComponent: React.FC<GenerationProgressProps> = ({
135
  <div className="sticky top-20 z-30 mb-6 animate-scale-in">
136
  <Card variant="glass" className="overflow-hidden shadow-xl border-2 border-blue-200/50 backdrop-blur-xl">
137
  <CardContent className="pt-6">
138
- <div className="space-y-6">
139
- {/* Header with animated icon */}
140
- <div className="flex items-center justify-between">
141
- <div className="flex items-center space-x-4">
142
- {isComplete ? (
143
- <div className="relative">
144
- <div className="absolute inset-0 bg-green-500 rounded-full animate-ping opacity-75"></div>
145
- <CheckCircle2 className="h-8 w-8 text-green-500 relative z-10" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  </div>
147
- ) : isError ? (
148
- <AlertCircle className="h-8 w-8 text-red-500 animate-pulse" />
149
- ) : (
150
- <div className="relative">
151
- <div className="absolute inset-0 bg-gradient-to-r from-blue-500 to-cyan-500 rounded-full animate-ping opacity-20"></div>
152
- <div className="relative bg-gradient-to-r from-blue-500 to-cyan-500 rounded-full p-2">
153
- <Wand2 className="h-5 w-5 text-white animate-spin-slow" />
154
  </div>
 
155
  </div>
156
  )}
157
- <div>
158
- <h3 className="text-lg font-bold bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">
159
- {isComplete ? "Generation Complete!" : isError ? "Generation Failed" : "Creating Your Ad"}
160
- </h3>
161
- <p className="text-sm text-gray-600 mt-0.5 transition-all duration-500">
162
- {isComplete ? "Your ad is ready!" : isError ? "Something went wrong" : getStepMessage()}
163
- </p>
164
- {elapsedTime > 30 && !isComplete && !isError && (
165
- <p className="text-xs text-gray-500 mt-1">
166
- {Math.floor(elapsedTime / 60)}m {elapsedTime % 60}s elapsed
167
- </p>
168
- )}
169
- </div>
170
  </div>
171
- {progress.estimatedTimeRemaining && !isComplete && !isError && (
172
- <div className="text-right">
173
- <div className="flex items-center space-x-1 text-sm font-semibold text-gray-700">
174
- <Zap className="h-4 w-4 text-yellow-500 animate-pulse" />
175
- <span>~{Math.ceil(progress.estimatedTimeRemaining)}s</span>
 
 
 
 
 
176
  </div>
177
- <p className="text-xs text-gray-500">remaining</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  </div>
179
  )}
180
- </div>
181
 
182
- {/* Step Indicators */}
183
- {!isComplete && !isError && (
184
- <div className="flex items-center justify-between relative">
185
- {/* Progress line */}
186
- <div className="absolute top-5 left-0 right-0 h-0.5 bg-gray-200 -z-10">
187
- <div
188
- className="h-full bg-gradient-to-r from-blue-500 via-cyan-500 to-pink-500 transition-all duration-500 ease-out"
189
- style={{ width: `${currentProgress}%` }}
190
- />
191
  </div>
192
-
193
- {STEPS.map((step, index) => {
194
- const StepIcon = step.icon;
195
- const isActive = progress.step === step.key;
196
- const isCompleted = currentStepIndex > index;
197
- const isUpcoming = currentStepIndex < index;
198
 
199
- return (
200
- <div key={step.key} className="flex flex-col items-center flex-1">
201
- <div className={`relative w-10 h-10 rounded-full flex items-center justify-center transition-all duration-300 ${
202
- isActive
203
- ? `bg-gradient-to-r ${step.color} shadow-lg scale-110 animate-pulse`
204
- : isCompleted
205
- ? "bg-gradient-to-r from-green-500 to-emerald-500 shadow-md"
206
- : "bg-gray-200"
207
- }`}>
208
- {isActive ? (
209
- <div className="text-white">
210
- <LoadingSpinner size="sm" />
211
- </div>
212
- ) : isCompleted ? (
213
- <CheckCircle2 className="h-5 w-5 text-white" />
214
- ) : (
215
- <StepIcon className={`h-5 w-5 ${isUpcoming ? "text-gray-400" : "text-white"}`} />
216
- )}
217
- {isActive && (
218
- <div className={`absolute inset-0 rounded-full bg-gradient-to-r ${step.color} animate-ping opacity-75`}></div>
219
- )}
220
- </div>
221
- <p className={`text-xs font-medium mt-2 text-center ${
222
- isActive
223
- ? "text-gray-900 font-bold"
224
- : isCompleted
225
- ? "text-green-600"
226
- : "text-gray-400"
227
- }`}>
228
- {step.label}
229
  </p>
230
  </div>
231
- );
232
- })}
233
- </div>
234
- )}
235
-
236
- {/* Progress Bar */}
237
- <div className="space-y-2">
238
- <div className="flex justify-between items-center">
239
- <span className="text-sm font-semibold text-gray-700">Overall Progress</span>
240
- <span className="text-sm font-bold bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">
241
- {Math.round(currentProgress)}%
242
- </span>
243
- </div>
244
- <ProgressBar
245
- progress={currentProgress}
246
- showPercentage={false}
247
- />
248
- </div>
249
-
250
- {/* Success State */}
251
- {isComplete && (
252
- <div className="mt-4 p-4 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-200 rounded-xl animate-scale-in">
253
- <div className="flex items-center space-x-3">
254
- <CheckCircle2 className="h-6 w-6 text-green-600 flex-shrink-0" />
255
- <div>
256
- <p className="text-sm font-semibold text-green-900">
257
- Ad generated successfully!
258
- </p>
259
- <p className="text-xs text-green-700 mt-0.5">
260
- Your creative is ready to use
261
- </p>
262
  </div>
263
  </div>
264
- </div>
265
- )}
266
 
267
- {/* Error State */}
268
- {isError && (
269
- <div className="mt-4 p-4 bg-gradient-to-r from-red-50 to-pink-50 border-2 border-red-200 rounded-xl animate-scale-in">
270
- <div className="flex items-center space-x-3">
271
- <AlertCircle className="h-6 w-6 text-red-600 flex-shrink-0" />
272
- <div>
273
- <p className="text-sm font-semibold text-red-900">
274
- Generation failed
275
- </p>
276
- <p className="text-xs text-red-700 mt-0.5">
277
- {progress.message || "An error occurred. Please try again."}
278
- </p>
 
279
  </div>
280
  </div>
281
- </div>
282
- )}
283
- </div>
284
- </CardContent>
285
- </Card>
286
  </div>
287
  );
288
  };
 
4
  import { Card, CardContent } from "@/components/ui/Card";
5
  import { ProgressBar } from "@/components/ui/ProgressBar";
6
  import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
7
+ import {
8
+ Sparkles,
9
+ Image as ImageIcon,
10
+ Database,
11
+ CheckCircle2,
12
  AlertCircle,
13
  Wand2,
14
+ Zap,
15
+ X
16
  } from "lucide-react";
17
  import type { GenerationProgress } from "@/types";
18
+ import { useGenerationStore } from "@/store/generationStore";
19
 
20
  interface GenerationProgressProps {
21
  progress: GenerationProgress;
 
23
  }
24
 
25
  const STEPS = [
26
+ {
27
+ key: "copy", label: "Crafting Copy", icon: Sparkles, color: "from-blue-500 to-cyan-500", messages: [
28
+ "Brainstorming compelling headlines...",
29
+ "Writing persuasive ad copy...",
30
+ "Polishing the perfect message...",
31
+ "Adding psychological triggers...",
32
+ ]
33
+ },
34
+ {
35
+ key: "image", label: "Generating Images", icon: ImageIcon, color: "from-cyan-500 to-pink-500", messages: [
36
+ "Creating stunning visuals...",
37
+ "Bringing your vision to life...",
38
+ "Rendering high-quality images...",
39
+ "Adding creative flair...",
40
+ "Generation may take a while...",
41
+ "Almost there!",
42
+ "Working on the perfect image...",
43
+ "This is worth the wait...",
44
+ "Crafting something amazing...",
45
+ "Just a few more moments...",
46
+ ]
47
+ },
48
+ {
49
+ key: "saving", label: "Saving", icon: Database, color: "from-pink-500 to-purple-500", messages: [
50
+ "Storing your creative...",
51
+ "Securing your masterpiece...",
52
+ "Almost done...",
53
+ "Finalizing everything...",
54
+ ]
55
+ },
56
  ] as const;
57
 
58
  // Engaging messages for when stuck at high progress
 
73
  }) => {
74
  const [currentMessageIndex, setCurrentMessageIndex] = useState(0);
75
  const [elapsedTime, setElapsedTime] = useState(0);
76
+ const reset = useGenerationStore((state) => state.reset);
77
 
78
  const stepProgress = {
79
  idle: 0,
 
127
  // Get message for current step
128
  const getStepMessage = () => {
129
  if (progress.message) return progress.message;
130
+
131
  // If stuck at high progress, show engaging messages
132
  if (isStuckAtHighProgress) {
133
  return ENGAGING_MESSAGES[currentMessageIndex];
134
  }
135
+
136
  const step = STEPS.find(s => s.key === progress.step);
137
  if (step && step.messages.length > 0) {
138
  return step.messages[currentMessageIndex % step.messages.length];
 
144
  <div className="sticky top-20 z-30 mb-6 animate-scale-in">
145
  <Card variant="glass" className="overflow-hidden shadow-xl border-2 border-blue-200/50 backdrop-blur-xl">
146
  <CardContent className="pt-6">
147
+ <div className="space-y-6">
148
+ {/* Header with animated icon */}
149
+ <div className="flex items-center justify-between">
150
+ <div className="flex items-center space-x-4">
151
+ {isComplete ? (
152
+ <div className="relative">
153
+ <div className="absolute inset-0 bg-green-500 rounded-full animate-ping opacity-75"></div>
154
+ <CheckCircle2 className="h-8 w-8 text-green-500 relative z-10" />
155
+ </div>
156
+ ) : isError ? (
157
+ <AlertCircle className="h-8 w-8 text-red-500 animate-pulse" />
158
+ ) : (
159
+ <div className="relative">
160
+ <div className="absolute inset-0 bg-gradient-to-r from-blue-500 to-cyan-500 rounded-full animate-ping opacity-20"></div>
161
+ <div className="relative bg-gradient-to-r from-blue-500 to-cyan-500 rounded-full p-2">
162
+ <Wand2 className="h-5 w-5 text-white animate-spin-slow" />
163
+ </div>
164
+ </div>
165
+ )}
166
+ <div>
167
+ <h3 className="text-lg font-bold bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">
168
+ {isComplete ? "Generation Complete!" : isError ? "Generation Failed" : "Creating Your Ad"}
169
+ </h3>
170
+ <p className="text-sm text-gray-600 mt-0.5 transition-all duration-500">
171
+ {isComplete ? "Your ad is ready!" : isError ? "Something went wrong" : getStepMessage()}
172
+ </p>
173
+ {elapsedTime > 30 && !isComplete && !isError && (
174
+ <p className="text-xs text-gray-500 mt-1">
175
+ {Math.floor(elapsedTime / 60)}m {elapsedTime % 60}s elapsed
176
+ </p>
177
+ )}
178
  </div>
179
+ </div>
180
+ {progress.estimatedTimeRemaining && !isComplete && !isError && (
181
+ <div className="text-right">
182
+ <div className="flex items-center space-x-1 text-sm font-semibold text-gray-700">
183
+ <Zap className="h-4 w-4 text-yellow-500 animate-pulse" />
184
+ <span>~{Math.ceil(progress.estimatedTimeRemaining)}s</span>
 
185
  </div>
186
+ <p className="text-xs text-gray-500">remaining</p>
187
  </div>
188
  )}
189
+ {!isComplete && !isError && (
190
+ <button
191
+ onClick={() => reset()}
192
+ className="p-2 hover:bg-red-50 text-gray-400 hover:text-red-500 rounded-full transition-colors group"
193
+ title="Cancel Generation"
194
+ >
195
+ <X className="h-5 w-5 group-hover:rotate-90 transition-transform duration-300" />
196
+ </button>
197
+ )}
 
 
 
 
198
  </div>
199
+
200
+ {/* Step Indicators */}
201
+ {!isComplete && !isError && (
202
+ <div className="flex items-center justify-between relative">
203
+ {/* Progress line */}
204
+ <div className="absolute top-5 left-0 right-0 h-0.5 bg-gray-200 -z-10">
205
+ <div
206
+ className="h-full bg-gradient-to-r from-blue-500 via-cyan-500 to-pink-500 transition-all duration-500 ease-out"
207
+ style={{ width: `${currentProgress}%` }}
208
+ />
209
  </div>
210
+
211
+ {STEPS.map((step, index) => {
212
+ const StepIcon = step.icon;
213
+ const isActive = progress.step === step.key;
214
+ const isCompleted = currentStepIndex > index;
215
+ const isUpcoming = currentStepIndex < index;
216
+
217
+ return (
218
+ <div key={step.key} className="flex flex-col items-center flex-1">
219
+ <div className={`relative w-10 h-10 rounded-full flex items-center justify-center transition-all duration-300 ${isActive
220
+ ? `bg-gradient-to-r ${step.color} shadow-lg scale-110 animate-pulse`
221
+ : isCompleted
222
+ ? "bg-gradient-to-r from-green-500 to-emerald-500 shadow-md"
223
+ : "bg-gray-200"
224
+ }`}>
225
+ {isActive ? (
226
+ <div className="text-white">
227
+ <LoadingSpinner size="sm" />
228
+ </div>
229
+ ) : isCompleted ? (
230
+ <CheckCircle2 className="h-5 w-5 text-white" />
231
+ ) : (
232
+ <StepIcon className={`h-5 w-5 ${isUpcoming ? "text-gray-400" : "text-white"}`} />
233
+ )}
234
+ {isActive && (
235
+ <div className={`absolute inset-0 rounded-full bg-gradient-to-r ${step.color} animate-ping opacity-75`}></div>
236
+ )}
237
+ </div>
238
+ <p className={`text-xs font-medium mt-2 text-center ${isActive
239
+ ? "text-gray-900 font-bold"
240
+ : isCompleted
241
+ ? "text-green-600"
242
+ : "text-gray-400"
243
+ }`}>
244
+ {step.label}
245
+ </p>
246
+ </div>
247
+ );
248
+ })}
249
  </div>
250
  )}
 
251
 
252
+ {/* Progress Bar */}
253
+ <div className="space-y-2">
254
+ <div className="flex justify-between items-center">
255
+ <span className="text-sm font-semibold text-gray-700">Overall Progress</span>
256
+ <span className="text-sm font-bold bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">
257
+ {Math.round(currentProgress)}%
258
+ </span>
 
 
259
  </div>
260
+ <ProgressBar
261
+ progress={currentProgress}
262
+ showPercentage={false}
263
+ />
264
+ </div>
 
265
 
266
+ {/* Success State */}
267
+ {isComplete && (
268
+ <div className="mt-4 p-4 bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-200 rounded-xl animate-scale-in">
269
+ <div className="flex items-center space-x-3">
270
+ <CheckCircle2 className="h-6 w-6 text-green-600 flex-shrink-0" />
271
+ <div>
272
+ <p className="text-sm font-semibold text-green-900">
273
+ Ad generated successfully!
274
+ </p>
275
+ <p className="text-xs text-green-700 mt-0.5">
276
+ Your creative is ready to use
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  </p>
278
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  </div>
280
  </div>
281
+ )}
 
282
 
283
+ {/* Error State */}
284
+ {isError && (
285
+ <div className="mt-4 p-4 bg-gradient-to-r from-red-50 to-pink-50 border-2 border-red-200 rounded-xl animate-scale-in">
286
+ <div className="flex items-center space-x-3">
287
+ <AlertCircle className="h-6 w-6 text-red-600 flex-shrink-0" />
288
+ <div>
289
+ <p className="text-sm font-semibold text-red-900">
290
+ Generation failed
291
+ </p>
292
+ <p className="text-xs text-red-700 mt-0.5">
293
+ {progress.message || "An error occurred. Please try again."}
294
+ </p>
295
+ </div>
296
  </div>
297
  </div>
298
+ )}
299
+ </div>
300
+ </CardContent>
301
+ </Card>
 
302
  </div>
303
  );
304
  };