Skip to content

Commit 3dec97b

Browse files
committed
WIP improvements
Code cleanup Custom prompt blank state Replace image Custom prompt working Improvements to the feedback New realtime version working
1 parent 115c811 commit 3dec97b

File tree

18 files changed

+676
-1462
lines changed

18 files changed

+676
-1462
lines changed

product-image-generator/app/ProductImageGenerator.tsx

Lines changed: 37 additions & 258 deletions
Original file line numberDiff line numberDiff line change
@@ -1,226 +1,25 @@
11
"use client";
22

3-
import {
4-
Download,
5-
Home,
6-
ImageIcon,
7-
Settings,
8-
User,
9-
WandSparklesIcon,
10-
} from "lucide-react";
11-
import { useState } from "react";
12-
import CustomPromptCard from "./components/CustomPromptCard";
13-
import GeneratedCard from "./components/GeneratedCard";
3+
import { Download, Home, Settings, User, WandSparklesIcon } from "lucide-react";
4+
import { useSearchParams } from "next/navigation";
5+
import { GeneratedCard } from "./components/GeneratedCard";
146
import { Button } from "./components/ui/button";
15-
import { Card } from "./components/ui/card";
16-
import UploadCard from "./components/UploadCard";
17-
import type { ProductAnalysis } from "./types/trigger";
18-
19-
interface ProductImageGeneratorProps {
20-
triggerToken: string;
21-
}
22-
23-
export default function ProductImageGenerator({
24-
triggerToken,
25-
}: ProductImageGeneratorProps) {
26-
const [uploadedImageUrl, setUploadedImageUrl] = useState<string | null>(null);
27-
const [productAnalysis, setProductAnalysis] =
28-
useState<ProductAnalysis | null>(null);
29-
30-
// Track all generated images
31-
const [generatedImages, setGeneratedImages] = useState<{
32-
[key: string]: { runId: string; prompt: string; imageUrl?: string };
33-
}>({});
34-
35-
// Track custom generations for bottom row
36-
const [customGenerations, setCustomGenerations] = useState<{
37-
runIds: (string | null)[];
38-
prompts: (string | null)[];
39-
}>({
40-
runIds: [null, null, null, null],
41-
prompts: [null, null, null, null],
42-
});
43-
44-
const handleUploadComplete = (
45-
imageUrl: string,
46-
productAnalysis?: ProductAnalysis
47-
) => {
48-
setUploadedImageUrl(imageUrl);
49-
if (productAnalysis) {
50-
setProductAnalysis(productAnalysis);
51-
}
52-
};
53-
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-
71-
const handleCustomGenerationComplete = (
72-
runId: string,
73-
prompt: string,
74-
index: number,
75-
imageUrl?: string
76-
) => {
77-
setCustomGenerations((prev) => ({
78-
runIds: prev.runIds.map((id, i) => (i === index ? runId : id)),
79-
prompts: prev.prompts.map((p, i) => (i === index ? prompt : p)),
80-
}));
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-
}
199-
};
200-
201-
const promptTitles = {
202-
"isolated-table": "Clean Product Shot",
203-
"lifestyle-scene": "Lifestyle Scene",
204-
"hero-shot": "Hero Shot",
205-
};
206-
207-
// Calculate total generated images
208-
const totalGeneratedImages = Object.keys(generatedImages).length;
209-
const hasGeneratedImages = totalGeneratedImages > 0;
210-
211-
// Determine which custom cards have completed generations
212-
const completedCustomCards = customGenerations.runIds.filter(
213-
(runId) => runId !== null
214-
).length;
7+
import { UploadCard } from "./components/UploadCard";
8+
import CustomPromptCard from "./components/CustomPromptCard";
9+
import Link from "next/link";
21510

216-
// Find the next available custom card slot
217-
const nextCustomCardIndex = customGenerations.runIds.findIndex(
218-
(runId) => runId === null
219-
);
11+
const promptTitles = {
12+
"isolated-table": "Clean Product Shot",
13+
"lifestyle-scene": "Lifestyle Scene",
14+
"hero-shot": "Hero Shot",
15+
};
22016

221-
// Check if top row is complete (upload + 3 generated images)
222-
const topRowGenerationsComplete =
223-
uploadedImageUrl && productAnalysis ? true : false;
17+
export function ProductImageGenerator() {
18+
const searchParams = useSearchParams();
19+
const publicAccessToken = searchParams.get("publicAccessToken");
20+
const generateToken = searchParams.get("triggerToken");
21+
const fileUrl = searchParams.get("fileUrl");
22+
const runId = searchParams.get("runId");
22423

22524
return (
22625
<div className="min-h-screen bg-gray-100/20 ">
@@ -229,7 +28,9 @@ export default function ProductImageGenerator({
22928
<div className="flex h-14 items-center justify-between">
23029
<div className="flex items-center space-x-4">
23130
<div className="flex items-center space-x-2">
232-
<WandSparklesIcon className="h-5 w-5 text-purple-500" />
31+
<Link href="/" className="flex items-center space-x-2">
32+
<WandSparklesIcon className="h-5 w-5 text-purple-500" />
33+
</Link>
23334
<h1 className="text-xl font-bold text-foreground">ImageFlow</h1>
23435
</div>
23536
</div>
@@ -265,69 +66,47 @@ export default function ProductImageGenerator({
26566
</p>
26667
</div>
26768
<div>
268-
<Button
269-
variant={hasGeneratedImages ? "default" : "outline"}
270-
disabled={!hasGeneratedImages}
271-
onClick={handleDownloadAll}
272-
className={
273-
!hasGeneratedImages
274-
? "opacity-50 cursor-not-allowed"
275-
: "cursor-pointer"
276-
}
277-
>
69+
<Button variant={"default"} className={"cursor-pointer"}>
27870
<Download className="h-4 w-4 mr-1" />
279-
{hasGeneratedImages
280-
? `Download ${totalGeneratedImages} image${
281-
totalGeneratedImages === 1 ? "" : "s"
282-
} `
283-
: "Download images"}
71+
Download images
28472
</Button>
28573
</div>
28674
</div>
28775

28876
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
28977
<UploadCard
290-
triggerToken={triggerToken}
291-
onUploadComplete={handleUploadComplete}
78+
runId={runId ?? undefined}
79+
accessToken={publicAccessToken ?? undefined}
80+
fileUrl={fileUrl ?? undefined}
29281
/>
29382
<GeneratedCard
294-
baseImageUrl={uploadedImageUrl}
295-
productAnalysis={productAnalysis}
296-
promptId="isolated-table"
83+
id="isolated-table"
84+
runId={runId ?? undefined}
85+
accessToken={publicAccessToken ?? undefined}
29786
promptTitle={promptTitles["isolated-table"]}
298-
onGenerationComplete={handlePresetGenerationComplete}
29987
/>
30088
<GeneratedCard
301-
baseImageUrl={uploadedImageUrl}
302-
productAnalysis={productAnalysis}
303-
promptId="lifestyle-scene"
89+
id="lifestyle-scene"
90+
runId={runId ?? undefined}
91+
accessToken={publicAccessToken ?? undefined}
30492
promptTitle={promptTitles["lifestyle-scene"]}
305-
onGenerationComplete={handlePresetGenerationComplete}
30693
/>
30794
<GeneratedCard
308-
baseImageUrl={uploadedImageUrl}
309-
productAnalysis={productAnalysis}
310-
promptId="hero-shot"
95+
id="hero-shot"
96+
runId={runId ?? undefined}
97+
accessToken={publicAccessToken ?? undefined}
31198
promptTitle={promptTitles["hero-shot"]}
312-
onGenerationComplete={handlePresetGenerationComplete}
31399
/>
314100
</div>
315101

316102
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
317103
{Array.from({ length: 4 }).map((_, index) => {
318104
return (
319105
<CustomPromptCard
106+
id={`custom-prompt-${index}`}
320107
key={`custom-prompt-${index}`}
321-
baseImageUrl={uploadedImageUrl}
322-
productAnalysis={productAnalysis}
323-
onGenerationComplete={(runId, prompt, imageUrl) =>
324-
handleCustomGenerationComplete(
325-
runId,
326-
prompt,
327-
index,
328-
imageUrl
329-
)
330-
}
108+
fileUrl={fileUrl ?? undefined}
109+
generateToken={generateToken ?? undefined}
331110
/>
332111
);
333112
})}

product-image-generator/app/actions.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
"use server";
22

33
import { auth, tasks } from "@trigger.dev/sdk";
4-
import type {
5-
generateAndUploadImage,
6-
StylePrompt,
7-
} from "../src/trigger/generate-image-and-upload";
8-
import type { uploadImageToR2 } from "../src/trigger/upload-image-and-analyze";
4+
import type { generateImage, StylePrompt } from "./trigger/generate-images";
5+
import type { uploadImageToR2 } from "./trigger/upload-image-and-analyze";
96
import type { ProductAnalysis } from "./types/trigger";
107

118
export async function triggerUploadTask(payload: {
@@ -46,7 +43,7 @@ export async function triggerGenerationTask(payload: {
4643
customPrompt?: string;
4744
}) {
4845
try {
49-
const handle = await tasks.trigger<typeof generateAndUploadImage>(
46+
const handle = await tasks.trigger<typeof generateImage>(
5047
"generate-image-and-upload",
5148
payload
5249
);

0 commit comments

Comments
 (0)