Skip to content

Commit ec8b42b

Browse files
D-K-Pmatt-aitken
authored andcommitted
Added dl functionality
1 parent 865cd16 commit ec8b42b

5 files changed

Lines changed: 275 additions & 31 deletions

File tree

product-image-generator/app/ProductImageGenerator.tsx

Lines changed: 195 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
"use client";
22

3-
import { Home, ImageIcon, Settings, User } from "lucide-react";
3+
import {
4+
Download,
5+
Home,
6+
ImageIcon,
7+
Settings,
8+
User,
9+
WandSparklesIcon,
10+
} from "lucide-react";
411
import { useState } from "react";
512
import CustomPromptCard from "./components/CustomPromptCard";
613
import GeneratedCard from "./components/GeneratedCard";
@@ -20,6 +27,11 @@ export default function ProductImageGenerator({
2027
const [productAnalysis, setProductAnalysis] =
2128
useState<ProductAnalysis | null>(null);
2229

30+
// Track all generated images
31+
const [generatedImages, setGeneratedImages] = useState<{
32+
[key: string]: { runId: string; prompt: string; imageUrl?: string };
33+
}>({});
34+
2335
// Track custom generations for bottom row
2436
const [customGenerations, setCustomGenerations] = useState<{
2537
runIds: (string | null)[];
@@ -39,15 +51,151 @@ export default function ProductImageGenerator({
3951
}
4052
};
4153

54+
const handleGenerationComplete = (
55+
runId: string,
56+
prompt: string,
57+
imageUrl?: string,
58+
key?: string
59+
) => {
60+
console.log("Generation completed:", { runId, prompt, imageUrl, key });
61+
setGeneratedImages((prev) => {
62+
const updated = {
63+
...prev,
64+
[key || runId]: { runId, prompt, imageUrl },
65+
};
66+
console.log("Updated generated images:", updated);
67+
return updated;
68+
});
69+
};
70+
4271
const handleCustomGenerationComplete = (
4372
runId: string,
4473
prompt: string,
45-
index: number
74+
index: number,
75+
imageUrl?: string
4676
) => {
4777
setCustomGenerations((prev) => ({
4878
runIds: prev.runIds.map((id, i) => (i === index ? runId : id)),
4979
prompts: prev.prompts.map((p, i) => (i === index ? prompt : p)),
5080
}));
81+
handleGenerationComplete(runId, prompt, imageUrl, `custom-${index}`);
82+
};
83+
84+
const handlePresetGenerationComplete = (
85+
runId: string,
86+
promptId: string,
87+
promptTitle: string,
88+
imageUrl?: string
89+
) => {
90+
handleGenerationComplete(runId, promptTitle, imageUrl, promptId);
91+
};
92+
93+
const handleDownloadAll = async () => {
94+
console.log("Download button clicked!");
95+
console.log("Generated images:", generatedImages);
96+
console.log("Total generated images:", totalGeneratedImages);
97+
98+
if (totalGeneratedImages === 0) {
99+
console.log("No images to download");
100+
return;
101+
}
102+
103+
try {
104+
// For a single image, download directly
105+
if (totalGeneratedImages === 1) {
106+
const imageData = Object.values(generatedImages)[0];
107+
console.log("Single image data:", imageData);
108+
109+
if (imageData.imageUrl) {
110+
console.log("Downloading single image:", imageData.imageUrl);
111+
112+
// Try to fetch the image first to handle CORS
113+
try {
114+
const response = await fetch(imageData.imageUrl);
115+
const blob = await response.blob();
116+
const url = window.URL.createObjectURL(blob);
117+
118+
const link = document.createElement("a");
119+
link.href = url;
120+
link.download = `${imageData.prompt
121+
.replace(/\s+/g, "-")
122+
.toLowerCase()}-${Date.now()}.png`;
123+
document.body.appendChild(link);
124+
link.click();
125+
document.body.removeChild(link);
126+
127+
// Clean up the blob URL
128+
window.URL.revokeObjectURL(url);
129+
} catch (fetchError) {
130+
console.log("Fetch failed, trying direct download:", fetchError);
131+
// Fallback to direct download
132+
const link = document.createElement("a");
133+
link.href = imageData.imageUrl;
134+
link.download = `${imageData.prompt
135+
.replace(/\s+/g, "-")
136+
.toLowerCase()}-${Date.now()}.png`;
137+
document.body.appendChild(link);
138+
link.click();
139+
document.body.removeChild(link);
140+
}
141+
} else {
142+
console.log("No image URL found for single image");
143+
}
144+
return;
145+
}
146+
147+
// For multiple images, download each individually
148+
console.log("Downloading multiple images...");
149+
Object.entries(generatedImages).forEach(([key, imageData], index) => {
150+
console.log(`Image ${index + 1}:`, key, imageData);
151+
152+
if (imageData.imageUrl) {
153+
setTimeout(async () => {
154+
try {
155+
console.log(
156+
`Downloading image ${index + 1}:`,
157+
imageData.imageUrl
158+
);
159+
160+
// Try to fetch the image first to handle CORS
161+
const response = await fetch(imageData.imageUrl!);
162+
const blob = await response.blob();
163+
const url = window.URL.createObjectURL(blob);
164+
165+
const link = document.createElement("a");
166+
link.href = url;
167+
link.download = `${imageData.prompt
168+
.replace(/\s+/g, "-")
169+
.toLowerCase()}-${Date.now()}-${index + 1}.png`;
170+
document.body.appendChild(link);
171+
link.click();
172+
document.body.removeChild(link);
173+
174+
// Clean up the blob URL
175+
window.URL.revokeObjectURL(url);
176+
} catch (fetchError) {
177+
console.log(
178+
`Fetch failed for image ${index + 1}, trying direct download:`,
179+
fetchError
180+
);
181+
// Fallback to direct download
182+
const link = document.createElement("a");
183+
link.href = imageData.imageUrl!;
184+
link.download = `${imageData.prompt
185+
.replace(/\s+/g, "-")
186+
.toLowerCase()}-${Date.now()}-${index + 1}.png`;
187+
document.body.appendChild(link);
188+
link.click();
189+
document.body.removeChild(link);
190+
}
191+
}, index * 1000); // Stagger downloads by 1 second
192+
} else {
193+
console.log(`No image URL found for image ${index + 1}`);
194+
}
195+
});
196+
} catch (error) {
197+
console.error("Failed to download images:", error);
198+
}
51199
};
52200

53201
const promptTitles = {
@@ -56,6 +204,10 @@ export default function ProductImageGenerator({
56204
"hero-shot": "Hero Shot",
57205
};
58206

207+
// Calculate total generated images
208+
const totalGeneratedImages = Object.keys(generatedImages).length;
209+
const hasGeneratedImages = totalGeneratedImages > 0;
210+
59211
// Determine which custom cards have completed generations
60212
const completedCustomCards = customGenerations.runIds.filter(
61213
(runId) => runId !== null
@@ -71,14 +223,14 @@ export default function ProductImageGenerator({
71223
uploadedImageUrl && productAnalysis ? true : false;
72224

73225
return (
74-
<div className="min-h-screen bg-gray-100/10">
226+
<div className="min-h-screen bg-gray-100/20">
75227
{/* Fixed Header */}
76228
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
77229
<div className="container mx-auto px-4">
78230
<div className="flex h-14 items-center justify-between">
79231
<div className="flex items-center space-x-4">
80232
<div className="flex items-center space-x-2">
81-
<ImageIcon className="h-5 w-5 text-purple-500" />
233+
<WandSparklesIcon className="h-5 w-5 text-purple-500" />
82234
<h1 className="text-xl font-bold text-foreground">ImageFlow</h1>
83235
</div>
84236
</div>
@@ -105,14 +257,35 @@ export default function ProductImageGenerator({
105257
<main className="container mx-auto px-4 py-16">
106258
<div className="max-w-7xl">
107259
{/* Page Title */}
108-
<div className="mb-8">
109-
<h2 className="text-3xl font-bold text-foreground mb-2">
110-
Product Image Generator
111-
</h2>
112-
<p className="text-muted-foreground">
113-
Upload a product image and generate professional marketing shots
114-
for your online store.
115-
</p>
260+
<div className="mb-8 flex justify-between items-end">
261+
<div>
262+
<h2 className="text-3xl font-bold text-foreground mb-2">
263+
Product Image Generator
264+
</h2>
265+
<p className="text-muted-foreground">
266+
Upload a product image and generate professional marketing shots
267+
for your online store.
268+
</p>
269+
</div>
270+
<div>
271+
<Button
272+
variant={hasGeneratedImages ? "default" : "outline"}
273+
disabled={!hasGeneratedImages}
274+
onClick={handleDownloadAll}
275+
className={
276+
!hasGeneratedImages
277+
? "opacity-50 cursor-not-allowed"
278+
: "cursor-pointer"
279+
}
280+
>
281+
<Download className="h-4 w-4 mr-1" />
282+
{hasGeneratedImages
283+
? `Download ${totalGeneratedImages} image${
284+
totalGeneratedImages === 1 ? "" : "s"
285+
} `
286+
: "Download images"}
287+
</Button>
288+
</div>
116289
</div>
117290

118291
{/* Top Row - Upload + 3 Generated Images */}
@@ -127,18 +300,21 @@ export default function ProductImageGenerator({
127300
productAnalysis={productAnalysis}
128301
promptId="isolated-table"
129302
promptTitle={promptTitles["isolated-table"]}
303+
onGenerationComplete={handlePresetGenerationComplete}
130304
/>
131305
<GeneratedCard
132306
baseImageUrl={uploadedImageUrl}
133307
productAnalysis={productAnalysis}
134308
promptId="lifestyle-scene"
135309
promptTitle={promptTitles["lifestyle-scene"]}
310+
onGenerationComplete={handlePresetGenerationComplete}
136311
/>
137312
<GeneratedCard
138313
baseImageUrl={uploadedImageUrl}
139314
productAnalysis={productAnalysis}
140315
promptId="hero-shot"
141316
promptTitle={promptTitles["hero-shot"]}
317+
onGenerationComplete={handlePresetGenerationComplete}
142318
/>
143319
</div>
144320

@@ -151,8 +327,13 @@ export default function ProductImageGenerator({
151327
key={`custom-prompt-${index}`}
152328
baseImageUrl={uploadedImageUrl}
153329
productAnalysis={productAnalysis}
154-
onGenerationComplete={(runId, prompt) =>
155-
handleCustomGenerationComplete(runId, prompt, index)
330+
onGenerationComplete={(runId, prompt, imageUrl) =>
331+
handleCustomGenerationComplete(
332+
runId,
333+
prompt,
334+
index,
335+
imageUrl
336+
)
156337
}
157338
/>
158339
);

0 commit comments

Comments
 (0)