Zlr3-NFJ@NFaJlhyfcw{k^vvtGl`N9xSo**rDW4S}i
zM9{fMPWo%4wYDG~BZ18BD+}h|GQKc-g^{++3MY>}W_uq7jGHx{mwE9fZiPCoxN$+7
zrODGGJrOkcPQUB(FD5aoS4g~7#6NR^ma7-!>mHuJfY5kTe6PpNNKC9GGRiu^L31uG
z$7v`*JknQHsYB!Tm_W{a32TM099djW%5e+j0Ve_ct}IM>XLF1Ap+YvcrLV=|CKo6S
zb+9Nl3_YdKP6%Cxy@6TxZ>;4&nTneadr
z_ES90ydCev)LV!dN=#(*f}|ZORFdvkYBni^aLbUk>BajeWIOcmHP#8S)*2U~QKI%S
zyrLmtPqb&TphJ;>yAxri#;{uyk`JJqODDw%(Z=2`1uc}br^V%>j!gS)D*q*f_-qf8&D;W1dJgQMlaH5er
zN2U<%Smb7==vE}dDI8K7cKz!vs^73o9f>2sgiTzWcwY|BMYHH5%Vn7#kiw&eItCqa
zIkR2~Q}>X=Ar8W|^Ms41Fm8o6IB2_j60eOeBB1Br!boW7JnoeX6Gs)?7rW0^5psc-
zjS16yb>dFn>KPOF;imD}e!enuIniFzv}n$m2#gCCv4jM#ArwlzZ$7@9&XkFxZ4n!V
zj3dyiwW4Ki2QG{@i>yuZXQizw_OkZI^-3otXC{!(lUpJF33gI60ak;Uqitp74|B6I
zgg{b=Iz}WkhCGj1M=hu4#Aw173YxIVbISaoc
z-nLZC*6Tgivd5V`K%GxhBsp@SUU60-rfc$=wb>zdJzXS&-5(NRRodFk;Kxk!S(O(a0e7oY=E(
zAyS;Ow?6Q&XA+cnkCb{28_1N8H#?J!*$MmIwLq^*T_9-z^&UE@A(z9oGYtFy6EZef
LrJugUA?W`A8`#=m
literal 0
HcmV?d00001
diff --git a/product-image-generator/app/globals.css b/product-image-generator/app/globals.css
new file mode 100644
index 0000000..97afb5e
--- /dev/null
+++ b/product-image-generator/app/globals.css
@@ -0,0 +1,122 @@
+@import "tailwindcss";
+@import "tw-animate-css";
+
+@custom-variant dark (&:is(.dark *));
+
+@theme inline {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --font-sans: var(--font-geist-sans);
+ --font-mono: var(--font-geist-mono);
+ --color-sidebar-ring: var(--sidebar-ring);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar: var(--sidebar);
+ --color-chart-5: var(--chart-5);
+ --color-chart-4: var(--chart-4);
+ --color-chart-3: var(--chart-3);
+ --color-chart-2: var(--chart-2);
+ --color-chart-1: var(--chart-1);
+ --color-ring: var(--ring);
+ --color-input: var(--input);
+ --color-border: var(--border);
+ --color-destructive: var(--destructive);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-accent: var(--accent);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-muted: var(--muted);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-secondary: var(--secondary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-primary: var(--primary);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-popover: var(--popover);
+ --color-card-foreground: var(--card-foreground);
+ --color-card: var(--card);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+}
+
+:root {
+ --radius: 0.625rem;
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.141 0.005 285.823);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.141 0.005 285.823);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.141 0.005 285.823);
+ --primary: oklch(0.21 0.006 285.885);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.967 0.001 286.375);
+ --secondary-foreground: oklch(0.21 0.006 285.885);
+ --muted: oklch(0.967 0.001 286.375);
+ --muted-foreground: oklch(0.552 0.016 285.938);
+ --accent: oklch(0.967 0.001 286.375);
+ --accent-foreground: oklch(0.21 0.006 285.885);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.92 0.004 286.32);
+ --input: oklch(0.92 0.004 286.32);
+ --ring: oklch(0.705 0.015 286.067);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.141 0.005 285.823);
+ --sidebar-primary: oklch(0.21 0.006 285.885);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.967 0.001 286.375);
+ --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
+ --sidebar-border: oklch(0.92 0.004 286.32);
+ --sidebar-ring: oklch(0.705 0.015 286.067);
+}
+
+.dark {
+ --background: oklch(0.141 0.005 285.823);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.21 0.006 285.885);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.21 0.006 285.885);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.92 0.004 286.32);
+ --primary-foreground: oklch(0.21 0.006 285.885);
+ --secondary: oklch(0.274 0.006 286.033);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.274 0.006 286.033);
+ --muted-foreground: oklch(0.705 0.015 286.067);
+ --accent: oklch(0.274 0.006 286.033);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.552 0.016 285.938);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.21 0.006 285.885);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.274 0.006 286.033);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.552 0.016 285.938);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/product-image-generator/app/layout.tsx b/product-image-generator/app/layout.tsx
new file mode 100644
index 0000000..bb54325
--- /dev/null
+++ b/product-image-generator/app/layout.tsx
@@ -0,0 +1,38 @@
+import type React from "react";
+import type { Metadata } from "next";
+import { GeistSans } from "geist/font/sans";
+import { GeistMono } from "geist/font/mono";
+import { Playfair_Display } from "next/font/google";
+import { Analytics } from "@vercel/analytics/next";
+import { Suspense } from "react";
+import "./globals.css";
+
+const playfairDisplay = Playfair_Display({
+ subsets: ["latin"],
+ display: "swap",
+ variable: "--font-playfair",
+ weight: ["400", "700"],
+});
+
+export const metadata: Metadata = {
+ title: "ImageFlow - Modern Image Management",
+ description: "Professional image management and organization tool",
+ generator: "v0.app",
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/product-image-generator/app/page.tsx b/product-image-generator/app/page.tsx
new file mode 100644
index 0000000..c234fb2
--- /dev/null
+++ b/product-image-generator/app/page.tsx
@@ -0,0 +1,190 @@
+"use client";
+
+import { Button } from "@/components/ui/button";
+import { Card } from "@/components/ui/card";
+import { Upload, User, Settings, Home, ImageIcon } from "lucide-react";
+import { useState, useRef } from "react";
+import { triggerHelloWorld } from "./actions";
+import type { helloWorldTask } from "@/src/trigger/example";
+
+export default function ImageManagementApp() {
+ const [isDragOver, setIsDragOver] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+ const [runId, setRunId] = useState(null);
+ const [error, setError] = useState(null);
+ const fileInputRef = useRef(null);
+
+ // Trigger task via Server Action
+ const triggerTask = async (payload: string) => {
+ setIsLoading(true);
+ setError(null);
+
+ try {
+ const result = await triggerHelloWorld(payload);
+
+ if (result.success) {
+ setRunId(result.runId || null);
+ } else {
+ setError(result.error || "Failed to trigger task");
+ }
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Failed to trigger task");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleDragOver = (e: React.DragEvent) => {
+ e.preventDefault();
+ setIsDragOver(true);
+ };
+
+ const handleDragLeave = (e: React.DragEvent) => {
+ e.preventDefault();
+ setIsDragOver(false);
+ };
+
+ const handleDrop = async (e: React.DragEvent) => {
+ e.preventDefault();
+ setIsDragOver(false);
+
+ const files = Array.from(e.dataTransfer.files);
+ const imageFile = files.find((file) => file.type.startsWith("image/"));
+
+ if (imageFile) {
+ await triggerTask(`Hello from ${imageFile.name}!`);
+ }
+ };
+
+ const handleFileSelect = async (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (file && file.type.startsWith("image/")) {
+ await triggerTask(`Hello from ${file.name}!`);
+ }
+ };
+
+ return (
+
+ {/* Navigation Header */}
+
+
+ {/* Main Content */}
+
+
+
+ Image Gallery
+
+
+ Upload and organize your images with our intuitive drag-and-drop
+ interface
+
+
+
+ {/* Image Grid */}
+
+ {/* First Slot - Drag and Drop Area */}
+
fileInputRef.current?.click()}
+ >
+
+
+ {isLoading ? (
+
+ ) : (
+
+ )}
+
+
+ {isLoading ? "Processing..." : "Drag and drop an image here"}
+
+
+ {isLoading ? "Please wait" : "or click to browse"}
+
+ {runId && (
+
+ Task triggered! Run ID: {runId}
+
+ )}
+ {error && (
+
Error: {error}
+ )}
+
+
+
+
+ {/* Remaining 7 Slots - Blank States */}
+ {Array.from({ length: 7 }).map((_, index) => (
+
+
+
+ ))}
+
+
+ {/* Action Buttons */}
+
+
+
+ Upload Images
+
+
+
+ Organize
+
+
+
+
+ );
+}
diff --git a/product-image-generator/components.json b/product-image-generator/components.json
new file mode 100644
index 0000000..761072a
--- /dev/null
+++ b/product-image-generator/components.json
@@ -0,0 +1,22 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "src/app/globals.css",
+ "baseColor": "zinc",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "iconLibrary": "lucide",
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "registries": {}
+}
diff --git a/product-image-generator/components/ui/button.tsx b/product-image-generator/components/ui/button.tsx
new file mode 100644
index 0000000..a2df8dc
--- /dev/null
+++ b/product-image-generator/components/ui/button.tsx
@@ -0,0 +1,59 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
+ ghost:
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+ icon: "size-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? Slot : "button"
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/product-image-generator/components/ui/card.tsx b/product-image-generator/components/ui/card.tsx
new file mode 100644
index 0000000..d05bbc6
--- /dev/null
+++ b/product-image-generator/components/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Card({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+}
diff --git a/product-image-generator/eslint.config.mjs b/product-image-generator/eslint.config.mjs
new file mode 100644
index 0000000..719cea2
--- /dev/null
+++ b/product-image-generator/eslint.config.mjs
@@ -0,0 +1,25 @@
+import { dirname } from "path";
+import { fileURLToPath } from "url";
+import { FlatCompat } from "@eslint/eslintrc";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+const compat = new FlatCompat({
+ baseDirectory: __dirname,
+});
+
+const eslintConfig = [
+ ...compat.extends("next/core-web-vitals", "next/typescript"),
+ {
+ ignores: [
+ "node_modules/**",
+ ".next/**",
+ "out/**",
+ "build/**",
+ "next-env.d.ts",
+ ],
+ },
+];
+
+export default eslintConfig;
diff --git a/product-image-generator/lib/utils.ts b/product-image-generator/lib/utils.ts
new file mode 100644
index 0000000..bd0c391
--- /dev/null
+++ b/product-image-generator/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/product-image-generator/package.json b/product-image-generator/package.json
new file mode 100644
index 0000000..035608f
--- /dev/null
+++ b/product-image-generator/package.json
@@ -0,0 +1,76 @@
+{
+ "name": "my-v0-project",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@hookform/resolvers": "^3.10.0",
+ "@radix-ui/react-accordion": "1.2.2",
+ "@radix-ui/react-alert-dialog": "1.1.4",
+ "@radix-ui/react-aspect-ratio": "1.1.1",
+ "@radix-ui/react-avatar": "1.1.2",
+ "@radix-ui/react-checkbox": "1.1.3",
+ "@radix-ui/react-collapsible": "1.1.2",
+ "@radix-ui/react-context-menu": "2.2.4",
+ "@radix-ui/react-dialog": "1.1.4",
+ "@radix-ui/react-dropdown-menu": "2.1.4",
+ "@radix-ui/react-hover-card": "1.1.4",
+ "@radix-ui/react-label": "2.1.1",
+ "@radix-ui/react-menubar": "1.1.4",
+ "@radix-ui/react-navigation-menu": "1.2.3",
+ "@radix-ui/react-popover": "1.1.4",
+ "@radix-ui/react-progress": "1.1.1",
+ "@radix-ui/react-radio-group": "1.2.2",
+ "@radix-ui/react-scroll-area": "1.2.2",
+ "@radix-ui/react-select": "2.1.4",
+ "@radix-ui/react-separator": "1.1.1",
+ "@radix-ui/react-slider": "1.2.2",
+ "@radix-ui/react-slot": "1.1.1",
+ "@radix-ui/react-switch": "1.1.2",
+ "@radix-ui/react-tabs": "1.1.2",
+ "@radix-ui/react-toast": "1.2.4",
+ "@radix-ui/react-toggle": "1.1.1",
+ "@radix-ui/react-toggle-group": "1.1.1",
+ "@radix-ui/react-tooltip": "1.1.6",
+ "@trigger.dev/react-hooks": "^4.0.2",
+ "@trigger.dev/sdk": "^4.0.2",
+ "@vercel/analytics": "1.3.1",
+ "autoprefixer": "^10.4.20",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "cmdk": "1.0.4",
+ "date-fns": "4.1.0",
+ "embla-carousel-react": "8.5.1",
+ "geist": "^1.3.1",
+ "input-otp": "1.4.1",
+ "lucide-react": "^0.454.0",
+ "next": "14.2.25",
+ "next-themes": "^0.4.6",
+ "react": "^19",
+ "react-day-picker": "9.8.0",
+ "react-dom": "^19",
+ "react-hook-form": "^7.60.0",
+ "react-resizable-panels": "^2.1.7",
+ "recharts": "2.15.4",
+ "sonner": "^1.7.4",
+ "tailwind-merge": "^3.3.1",
+ "tailwindcss-animate": "^1.0.7",
+ "vaul": "^0.9.9",
+ "zod": "3.25.67"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4.1.9",
+ "@types/node": "^22",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "postcss": "^8.5",
+ "tailwindcss": "^4.1.9",
+ "tw-animate-css": "1.3.3",
+ "typescript": "^5"
+ }
+}
diff --git a/product-image-generator/pnpm-lock.yaml b/product-image-generator/pnpm-lock.yaml
new file mode 100644
index 0000000..463f900
--- /dev/null
+++ b/product-image-generator/pnpm-lock.yaml
@@ -0,0 +1,4004 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@hookform/resolvers':
+ specifier: ^3.10.0
+ version: 3.10.0(react-hook-form@7.62.0(react@19.1.0))
+ '@radix-ui/react-accordion':
+ specifier: 1.2.2
+ version: 1.2.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-alert-dialog':
+ specifier: 1.1.4
+ version: 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-aspect-ratio':
+ specifier: 1.1.1
+ version: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-avatar':
+ specifier: 1.1.2
+ version: 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-checkbox':
+ specifier: 1.1.3
+ version: 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-collapsible':
+ specifier: 1.1.2
+ version: 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-context-menu':
+ specifier: 2.2.4
+ version: 2.2.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-dialog':
+ specifier: 1.1.4
+ version: 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-dropdown-menu':
+ specifier: 2.1.4
+ version: 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-hover-card':
+ specifier: 1.1.4
+ version: 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-label':
+ specifier: 2.1.1
+ version: 2.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-menubar':
+ specifier: 1.1.4
+ version: 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-navigation-menu':
+ specifier: 1.2.3
+ version: 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-popover':
+ specifier: 1.1.4
+ version: 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-progress':
+ specifier: 1.1.1
+ version: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-radio-group':
+ specifier: 1.2.2
+ version: 1.2.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-scroll-area':
+ specifier: 1.2.2
+ version: 1.2.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-select':
+ specifier: 2.1.4
+ version: 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-separator':
+ specifier: 1.1.1
+ version: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slider':
+ specifier: 1.2.2
+ version: 1.2.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot':
+ specifier: 1.1.1
+ version: 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-switch':
+ specifier: 1.1.2
+ version: 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-tabs':
+ specifier: 1.1.2
+ version: 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-toast':
+ specifier: 1.2.4
+ version: 1.2.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-toggle':
+ specifier: 1.1.1
+ version: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-toggle-group':
+ specifier: 1.1.1
+ version: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-tooltip':
+ specifier: 1.1.6
+ version: 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@trigger.dev/react-hooks':
+ specifier: ^4.0.2
+ version: 4.0.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@trigger.dev/sdk':
+ specifier: ^4.0.2
+ version: 4.0.2(zod@3.25.67)
+ '@vercel/analytics':
+ specifier: 1.3.1
+ version: 1.3.1(next@14.2.25(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
+ autoprefixer:
+ specifier: ^10.4.20
+ version: 10.4.21(postcss@8.5.6)
+ class-variance-authority:
+ specifier: ^0.7.1
+ version: 0.7.1
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
+ cmdk:
+ specifier: 1.0.4
+ version: 1.0.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ date-fns:
+ specifier: 4.1.0
+ version: 4.1.0
+ embla-carousel-react:
+ specifier: 8.5.1
+ version: 8.5.1(react@19.1.0)
+ geist:
+ specifier: ^1.3.1
+ version: 1.4.2(next@14.2.25(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))
+ input-otp:
+ specifier: 1.4.1
+ version: 1.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ lucide-react:
+ specifier: ^0.454.0
+ version: 0.454.0(react@19.1.0)
+ next:
+ specifier: 14.2.25
+ version: 14.2.25(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ next-themes:
+ specifier: ^0.4.6
+ version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react:
+ specifier: ^19
+ version: 19.1.0
+ react-day-picker:
+ specifier: 9.8.0
+ version: 9.8.0(react@19.1.0)
+ react-dom:
+ specifier: ^19
+ version: 19.1.0(react@19.1.0)
+ react-hook-form:
+ specifier: ^7.60.0
+ version: 7.62.0(react@19.1.0)
+ react-resizable-panels:
+ specifier: ^2.1.7
+ version: 2.1.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ recharts:
+ specifier: 2.15.4
+ version: 2.15.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ sonner:
+ specifier: ^1.7.4
+ version: 1.7.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ tailwind-merge:
+ specifier: ^3.3.1
+ version: 3.3.1
+ tailwindcss-animate:
+ specifier: ^1.0.7
+ version: 1.0.7(tailwindcss@4.1.12)
+ vaul:
+ specifier: ^0.9.9
+ version: 0.9.9(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ zod:
+ specifier: 3.25.67
+ version: 3.25.67
+ devDependencies:
+ '@tailwindcss/postcss':
+ specifier: ^4.1.9
+ version: 4.1.12
+ '@types/node':
+ specifier: ^22
+ version: 22.18.1
+ '@types/react':
+ specifier: ^18
+ version: 18.3.24
+ '@types/react-dom':
+ specifier: ^18
+ version: 18.3.7(@types/react@18.3.24)
+ postcss:
+ specifier: ^8.5
+ version: 8.5.6
+ tailwindcss:
+ specifier: ^4.1.9
+ version: 4.1.12
+ tw-animate-css:
+ specifier: 1.3.3
+ version: 1.3.3
+ typescript:
+ specifier: ^5
+ version: 5.9.2
+
+packages:
+
+ '@alloc/quick-lru@5.2.0':
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
+
+ '@babel/runtime@7.28.3':
+ resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==}
+ engines: {node: '>=6.9.0'}
+
+ '@bugsnag/cuid@3.2.1':
+ resolution: {integrity: sha512-zpvN8xQ5rdRWakMd/BcVkdn2F8HKlDSbM3l7duueK590WmI1T0ObTLc1V/1e55r14WNjPd5AJTYX4yPEAFVi+Q==}
+
+ '@date-fns/tz@1.2.0':
+ resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==}
+
+ '@electric-sql/client@1.0.0-beta.1':
+ resolution: {integrity: sha512-Ei9jN3pDoGzc+a/bGqnB5ajb52IvSv7/n2btuyzUlcOHIR2kM9fqtYTJXPwZYKLkGZlHWlpHgWyRtrinkP2nHg==}
+
+ '@floating-ui/core@1.7.3':
+ resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
+
+ '@floating-ui/dom@1.7.4':
+ resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==}
+
+ '@floating-ui/react-dom@2.1.6':
+ resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/utils@0.2.10':
+ resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
+
+ '@google-cloud/precise-date@4.0.0':
+ resolution: {integrity: sha512-1TUx3KdaU3cN7nfCdNf+UVqA/PSX29Cjcox3fZZBtINlRrXVTmUkQnCKv2MbBUbCopbK4olAT1IHl76uZyCiVA==}
+ engines: {node: '>=14.0.0'}
+
+ '@hookform/resolvers@3.10.0':
+ resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==}
+ peerDependencies:
+ react-hook-form: ^7.0.0
+
+ '@isaacs/fs-minipass@4.0.1':
+ resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
+ engines: {node: '>=18.0.0'}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.30':
+ resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==}
+
+ '@jsonhero/path@1.0.21':
+ resolution: {integrity: sha512-gVUDj/92acpVoJwsVJ/RuWOaHyG4oFzn898WNGQItLCTQ+hOaVlEaImhwE1WqOTf+l3dGOUkbSiVKlb3q1hd1Q==}
+
+ '@next/env@14.2.25':
+ resolution: {integrity: sha512-JnzQ2cExDeG7FxJwqAksZ3aqVJrHjFwZQAEJ9gQZSoEhIow7SNoKZzju/AwQ+PLIR4NY8V0rhcVozx/2izDO0w==}
+
+ '@next/swc-darwin-arm64@14.2.25':
+ resolution: {integrity: sha512-09clWInF1YRd6le00vt750s3m7SEYNehz9C4PUcSu3bAdCTpjIV4aTYQZ25Ehrr83VR1rZeqtKUPWSI7GfuKZQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@next/swc-darwin-x64@14.2.25':
+ resolution: {integrity: sha512-V+iYM/QR+aYeJl3/FWWU/7Ix4b07ovsQ5IbkwgUK29pTHmq+5UxeDr7/dphvtXEq5pLB/PucfcBNh9KZ8vWbug==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@next/swc-linux-arm64-gnu@14.2.25':
+ resolution: {integrity: sha512-LFnV2899PJZAIEHQ4IMmZIgL0FBieh5keMnriMY1cK7ompR+JUd24xeTtKkcaw8QmxmEdhoE5Mu9dPSuDBgtTg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-arm64-musl@14.2.25':
+ resolution: {integrity: sha512-QC5y5PPTmtqFExcKWKYgUNkHeHE/z3lUsu83di488nyP0ZzQ3Yse2G6TCxz6nNsQwgAx1BehAJTZez+UQxzLfw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-x64-gnu@14.2.25':
+ resolution: {integrity: sha512-y6/ML4b9eQ2D/56wqatTJN5/JR8/xdObU2Fb1RBidnrr450HLCKr6IJZbPqbv7NXmje61UyxjF5kvSajvjye5w==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-linux-x64-musl@14.2.25':
+ resolution: {integrity: sha512-sPX0TSXHGUOZFvv96GoBXpB3w4emMqKeMgemrSxI7A6l55VBJp/RKYLwZIB9JxSqYPApqiREaIIap+wWq0RU8w==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-win32-arm64-msvc@14.2.25':
+ resolution: {integrity: sha512-ReO9S5hkA1DU2cFCsGoOEp7WJkhFzNbU/3VUF6XxNGUCQChyug6hZdYL/istQgfT/GWE6PNIg9cm784OI4ddxQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@next/swc-win32-ia32-msvc@14.2.25':
+ resolution: {integrity: sha512-DZ/gc0o9neuCDyD5IumyTGHVun2dCox5TfPQI/BJTYwpSNYM3CZDI4i6TOdjeq1JMo+Ug4kPSMuZdwsycwFbAw==}
+ engines: {node: '>= 10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@next/swc-win32-x64-msvc@14.2.25':
+ resolution: {integrity: sha512-KSznmS6eFjQ9RJ1nEc66kJvtGIL1iZMYmGEXsZPh2YtnLtqrgdVvKXJY2ScjjoFnG6nGLyPFR0UiEvDwVah4Tw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@opentelemetry/api-logs@0.203.0':
+ resolution: {integrity: sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ==}
+ engines: {node: '>=8.0.0'}
+
+ '@opentelemetry/api@1.9.0':
+ resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
+ engines: {node: '>=8.0.0'}
+
+ '@opentelemetry/context-async-hooks@2.0.1':
+ resolution: {integrity: sha512-XuY23lSI3d4PEqKA+7SLtAgwqIfc6E/E9eAQWLN1vlpC53ybO3o6jW4BsXo1xvz9lYyyWItfQDDLzezER01mCw==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.0.0 <1.10.0'
+
+ '@opentelemetry/core@2.0.1':
+ resolution: {integrity: sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.0.0 <1.10.0'
+
+ '@opentelemetry/exporter-logs-otlp-http@0.203.0':
+ resolution: {integrity: sha512-s0hys1ljqlMTbXx2XiplmMJg9wG570Z5lH7wMvrZX6lcODI56sG4HL03jklF63tBeyNwK2RV1/ntXGo3HgG4Qw==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.3.0
+
+ '@opentelemetry/exporter-trace-otlp-http@0.203.0':
+ resolution: {integrity: sha512-ZDiaswNYo0yq/cy1bBLJFe691izEJ6IgNmkjm4C6kE9ub/OMQqDXORx2D2j8fzTBTxONyzusbaZlqtfmyqURPw==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.3.0
+
+ '@opentelemetry/instrumentation@0.203.0':
+ resolution: {integrity: sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.3.0
+
+ '@opentelemetry/otlp-exporter-base@0.203.0':
+ resolution: {integrity: sha512-Wbxf7k+87KyvxFr5D7uOiSq/vHXWommvdnNE7vECO3tAhsA2GfOlpWINCMWUEPdHZ7tCXxw6Epp3vgx3jU7llQ==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.3.0
+
+ '@opentelemetry/otlp-transformer@0.203.0':
+ resolution: {integrity: sha512-Y8I6GgoCna0qDQ2W6GCRtaF24SnvqvA8OfeTi7fqigD23u8Jpb4R5KFv/pRvrlGagcCLICMIyh9wiejp4TXu/A==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.3.0
+
+ '@opentelemetry/resources@2.0.1':
+ resolution: {integrity: sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.3.0 <1.10.0'
+
+ '@opentelemetry/sdk-logs@0.203.0':
+ resolution: {integrity: sha512-vM2+rPq0Vi3nYA5akQD2f3QwossDnTDLvKbea6u/A2NZ3XDkPxMfo/PNrDoXhDUD/0pPo2CdH5ce/thn9K0kLw==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.4.0 <1.10.0'
+
+ '@opentelemetry/sdk-metrics@2.0.1':
+ resolution: {integrity: sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.9.0 <1.10.0'
+
+ '@opentelemetry/sdk-trace-base@2.0.1':
+ resolution: {integrity: sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.3.0 <1.10.0'
+
+ '@opentelemetry/sdk-trace-node@2.0.1':
+ resolution: {integrity: sha512-UhdbPF19pMpBtCWYP5lHbTogLWx9N0EBxtdagvkn5YtsAnCBZzL7SjktG+ZmupRgifsHMjwUaCCaVmqGfSADmA==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.0.0 <1.10.0'
+
+ '@opentelemetry/semantic-conventions@1.36.0':
+ resolution: {integrity: sha512-TtxJSRD8Ohxp6bKkhrm27JRHAxPczQA7idtcTOMYI+wQRRrfgqxHv1cFbCApcSnNjtXkmzFozn6jQtFrOmbjPQ==}
+ engines: {node: '>=14'}
+
+ '@protobufjs/aspromise@1.1.2':
+ resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
+
+ '@protobufjs/base64@1.1.2':
+ resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
+
+ '@protobufjs/codegen@2.0.4':
+ resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==}
+
+ '@protobufjs/eventemitter@1.1.0':
+ resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==}
+
+ '@protobufjs/fetch@1.1.0':
+ resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==}
+
+ '@protobufjs/float@1.0.2':
+ resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
+
+ '@protobufjs/inquire@1.1.0':
+ resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==}
+
+ '@protobufjs/path@1.1.2':
+ resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
+
+ '@protobufjs/pool@1.1.0':
+ resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
+
+ '@protobufjs/utf8@1.1.0':
+ resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
+
+ '@radix-ui/number@1.1.0':
+ resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==}
+
+ '@radix-ui/primitive@1.1.1':
+ resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==}
+
+ '@radix-ui/react-accordion@1.2.2':
+ resolution: {integrity: sha512-b1oh54x4DMCdGsB4/7ahiSrViXxaBwRPotiZNnYXjLha9vfuURSAZErki6qjDoSIV0eXx5v57XnTGVtGwnfp2g==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-alert-dialog@1.1.4':
+ resolution: {integrity: sha512-A6Kh23qZDLy3PSU4bh2UJZznOrUdHImIXqF8YtUa6CN73f8EOO9XlXSCd9IHyPvIquTaa/kwaSWzZTtUvgXVGw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-arrow@1.1.1':
+ resolution: {integrity: sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-aspect-ratio@1.1.1':
+ resolution: {integrity: sha512-kNU4FIpcFMBLkOUcgeIteH06/8JLBcYY6Le1iKenDGCYNYFX3TQqCZjzkOsz37h7r94/99GTb7YhEr98ZBJibw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-avatar@1.1.2':
+ resolution: {integrity: sha512-GaC7bXQZ5VgZvVvsJ5mu/AEbjYLnhhkoidOboC50Z6FFlLA03wG2ianUoH+zgDQ31/9gCF59bE4+2bBgTyMiig==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-checkbox@1.1.3':
+ resolution: {integrity: sha512-HD7/ocp8f1B3e6OHygH0n7ZKjONkhciy1Nh0yuBgObqThc3oyx+vuMfFHKAknXRHHWVE9XvXStxJFyjUmB8PIw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-collapsible@1.1.2':
+ resolution: {integrity: sha512-PliMB63vxz7vggcyq0IxNYk8vGDrLXVWw4+W4B8YnwI1s18x7YZYqlG9PLX7XxAJUi0g2DxP4XKJMFHh/iVh9A==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-collection@1.1.1':
+ resolution: {integrity: sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-compose-refs@1.1.1':
+ resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-compose-refs@1.1.2':
+ resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-context-menu@2.2.4':
+ resolution: {integrity: sha512-ap4wdGwK52rJxGkwukU1NrnEodsUFQIooANKu+ey7d6raQ2biTcEf8za1zr0mgFHieevRTB2nK4dJeN8pTAZGQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-context@1.1.1':
+ resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-dialog@1.1.4':
+ resolution: {integrity: sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-direction@1.1.0':
+ resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-dismissable-layer@1.1.3':
+ resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-dropdown-menu@2.1.4':
+ resolution: {integrity: sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-focus-guards@1.1.1':
+ resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-focus-scope@1.1.1':
+ resolution: {integrity: sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-hover-card@1.1.4':
+ resolution: {integrity: sha512-QSUUnRA3PQ2UhvoCv3eYvMnCAgGQW+sTu86QPuNb+ZMi+ZENd6UWpiXbcWDQ4AEaKF9KKpCHBeaJz9Rw6lRlaQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-id@1.1.0':
+ resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-id@1.1.1':
+ resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-label@2.1.1':
+ resolution: {integrity: sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-menu@2.1.4':
+ resolution: {integrity: sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-menubar@1.1.4':
+ resolution: {integrity: sha512-+KMpi7VAZuB46+1LD7a30zb5IxyzLgC8m8j42gk3N4TUCcViNQdX8FhoH1HDvYiA8quuqcek4R4bYpPn/SY1GA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-navigation-menu@1.2.3':
+ resolution: {integrity: sha512-IQWAsQ7dsLIYDrn0WqPU+cdM7MONTv9nqrLVYoie3BPiabSfUVDe6Fr+oEt0Cofsr9ONDcDe9xhmJbL1Uq1yKg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-popover@1.1.4':
+ resolution: {integrity: sha512-aUACAkXx8LaFymDma+HQVji7WhvEhpFJ7+qPz17Nf4lLZqtreGOFRiNQWQmhzp7kEWg9cOyyQJpdIMUMPc/CPw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-popper@1.2.1':
+ resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-portal@1.1.3':
+ resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-presence@1.1.2':
+ resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-primitive@2.0.1':
+ resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-primitive@2.1.3':
+ resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-progress@1.1.1':
+ resolution: {integrity: sha512-6diOawA84f/eMxFHcWut0aE1C2kyE9dOyCTQOMRR2C/qPiXz/X0SaiA/RLbapQaXUCmy0/hLMf9meSccD1N0pA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-radio-group@1.2.2':
+ resolution: {integrity: sha512-E0MLLGfOP0l8P/NxgVzfXJ8w3Ch8cdO6UDzJfDChu4EJDy+/WdO5LqpdY8PYnCErkmZH3gZhDL1K7kQ41fAHuQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-roving-focus@1.1.1':
+ resolution: {integrity: sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-scroll-area@1.2.2':
+ resolution: {integrity: sha512-EFI1N/S3YxZEW/lJ/H1jY3njlvTd8tBmgKEn4GHi51+aMm94i6NmAJstsm5cu3yJwYqYc93gpCPm21FeAbFk6g==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-select@2.1.4':
+ resolution: {integrity: sha512-pOkb2u8KgO47j/h7AylCj7dJsm69BXcjkrvTqMptFqsE2i0p8lHkfgneXKjAgPzBMivnoMyt8o4KiV4wYzDdyQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-separator@1.1.1':
+ resolution: {integrity: sha512-RRiNRSrD8iUiXriq/Y5n4/3iE8HzqgLHsusUSg5jVpU2+3tqcUFPJXHDymwEypunc2sWxDUS3UC+rkZRlHedsw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-slider@1.2.2':
+ resolution: {integrity: sha512-sNlU06ii1/ZcbHf8I9En54ZPW0Vil/yPVg4vQMcFNjrIx51jsHbFl1HYHQvCIWJSr1q0ZmA+iIs/ZTv8h7HHSA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-slot@1.1.1':
+ resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-slot@1.2.3':
+ resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-switch@1.1.2':
+ resolution: {integrity: sha512-zGukiWHjEdBCRyXvKR6iXAQG6qXm2esuAD6kDOi9Cn+1X6ev3ASo4+CsYaD6Fov9r/AQFekqnD/7+V0Cs6/98g==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-tabs@1.1.2':
+ resolution: {integrity: sha512-9u/tQJMcC2aGq7KXpGivMm1mgq7oRJKXphDwdypPd/j21j/2znamPU8WkXgnhUaTrSFNIt8XhOyCAupg8/GbwQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-toast@1.2.4':
+ resolution: {integrity: sha512-Sch9idFJHJTMH9YNpxxESqABcAFweJG4tKv+0zo0m5XBvUSL8FM5xKcJLFLXononpePs8IclyX1KieL5SDUNgA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-toggle-group@1.1.1':
+ resolution: {integrity: sha512-OgDLZEA30Ylyz8YSXvnGqIHtERqnUt1KUYTKdw/y8u7Ci6zGiJfXc02jahmcSNK3YcErqioj/9flWC9S1ihfwg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-toggle@1.1.1':
+ resolution: {integrity: sha512-i77tcgObYr743IonC1hrsnnPmszDRn8p+EGUsUt+5a/JFn28fxaM88Py6V2mc8J5kELMWishI0rLnuGLFD/nnQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-tooltip@1.1.6':
+ resolution: {integrity: sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-use-callback-ref@1.1.0':
+ resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-controllable-state@1.1.0':
+ resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-escape-keydown@1.1.0':
+ resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-layout-effect@1.1.0':
+ resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-layout-effect@1.1.1':
+ resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-previous@1.1.0':
+ resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-rect@1.1.0':
+ resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-size@1.1.0':
+ resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-visually-hidden@1.1.1':
+ resolution: {integrity: sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/rect@1.1.0':
+ resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
+
+ '@rollup/rollup-darwin-arm64@4.50.0':
+ resolution: {integrity: sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@socket.io/component-emitter@3.1.2':
+ resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
+
+ '@swc/counter@0.1.3':
+ resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
+
+ '@swc/helpers@0.5.5':
+ resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
+
+ '@tailwindcss/node@4.1.12':
+ resolution: {integrity: sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==}
+
+ '@tailwindcss/oxide-android-arm64@4.1.12':
+ resolution: {integrity: sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.12':
+ resolution: {integrity: sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.1.12':
+ resolution: {integrity: sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.12':
+ resolution: {integrity: sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12':
+ resolution: {integrity: sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.12':
+ resolution: {integrity: sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.12':
+ resolution: {integrity: sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.12':
+ resolution: {integrity: sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.12':
+ resolution: {integrity: sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.12':
+ resolution: {integrity: sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.12':
+ resolution: {integrity: sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.12':
+ resolution: {integrity: sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide@4.1.12':
+ resolution: {integrity: sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==}
+ engines: {node: '>= 10'}
+
+ '@tailwindcss/postcss@4.1.12':
+ resolution: {integrity: sha512-5PpLYhCAwf9SJEeIsSmCDLgyVfdBhdBpzX1OJ87anT9IVR0Z9pjM0FNixCAUAHGnMBGB8K99SwAheXrT0Kh6QQ==}
+
+ '@trigger.dev/core@4.0.2':
+ resolution: {integrity: sha512-hc/alfT7iVdJNZ5YSMbGR9FirLjURqdZ7tCBX4btKas0GDg6M5onwcQsJ3oom5TDp/Nrt+dHaviNMhFxhKCu3g==}
+ engines: {node: '>=18.20.0'}
+
+ '@trigger.dev/react-hooks@4.0.2':
+ resolution: {integrity: sha512-xIKRUFu+X+16HmrRvkdLpIR7Bl/z2VIc4mGqzvrbeo9/sc70K8t2yEn6S4e48p59mfUBZBk3kG7aUmvMI3MvIA==}
+ engines: {node: '>=18.20.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@trigger.dev/sdk@4.0.2':
+ resolution: {integrity: sha512-ulhWJRSHPXOHz0bMvkhAKThkW63x7lnjAb87LPi6dUps1YwwoOL8Nkr15xLXa73UrldPFT+9Y/GvQ9qpzU478w==}
+ engines: {node: '>=18.20.0'}
+ peerDependencies:
+ ai: ^4.2.0 || ^5.0.0
+ zod: ^3.0.0 || ^4.0.0
+ peerDependenciesMeta:
+ ai:
+ optional: true
+
+ '@types/cookie@0.4.1':
+ resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
+
+ '@types/cors@2.8.19':
+ resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==}
+
+ '@types/d3-array@3.2.1':
+ resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
+
+ '@types/d3-color@3.1.3':
+ resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+
+ '@types/d3-ease@3.0.2':
+ resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
+
+ '@types/d3-interpolate@3.0.4':
+ resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+
+ '@types/d3-path@3.1.1':
+ resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
+
+ '@types/d3-scale@4.0.9':
+ resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
+
+ '@types/d3-shape@3.1.7':
+ resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==}
+
+ '@types/d3-time@3.0.4':
+ resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
+
+ '@types/d3-timer@3.0.2':
+ resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
+
+ '@types/node@22.18.1':
+ resolution: {integrity: sha512-rzSDyhn4cYznVG+PCzGe1lwuMYJrcBS1fc3JqSa2PvtABwWo+dZ1ij5OVok3tqfpEBCBoaR4d7upFJk73HRJDw==}
+
+ '@types/prop-types@15.7.15':
+ resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
+
+ '@types/react-dom@18.3.7':
+ resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
+ peerDependencies:
+ '@types/react': ^18.0.0
+
+ '@types/react@18.3.24':
+ resolution: {integrity: sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==}
+
+ '@vercel/analytics@1.3.1':
+ resolution: {integrity: sha512-xhSlYgAuJ6Q4WQGkzYTLmXwhYl39sWjoMA3nHxfkvG+WdBT25c563a7QhwwKivEOZtPJXifYHR1m2ihoisbWyA==}
+ peerDependencies:
+ next: '>= 13'
+ react: ^18 || ^19
+ peerDependenciesMeta:
+ next:
+ optional: true
+ react:
+ optional: true
+
+ accepts@1.3.8:
+ resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
+ engines: {node: '>= 0.6'}
+
+ acorn-import-attributes@1.9.5:
+ resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==}
+ peerDependencies:
+ acorn: ^8
+
+ acorn@8.15.0:
+ resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ aria-hidden@1.2.6:
+ resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
+ engines: {node: '>=10'}
+
+ autoprefixer@10.4.21:
+ resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+
+ base64id@2.0.0:
+ resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
+ engines: {node: ^4.5.0 || >= 5.9}
+
+ bintrees@1.0.2:
+ resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==}
+
+ browserslist@4.25.4:
+ resolution: {integrity: sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ busboy@1.6.0:
+ resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
+ engines: {node: '>=10.16.0'}
+
+ caniuse-lite@1.0.30001739:
+ resolution: {integrity: sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==}
+
+ chalk@5.6.0:
+ resolution: {integrity: sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==}
+ engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+
+ chownr@3.0.0:
+ resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
+ engines: {node: '>=18'}
+
+ cjs-module-lexer@1.4.3:
+ resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==}
+
+ class-variance-authority@0.7.1:
+ resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
+
+ client-only@0.0.1:
+ resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
+ cmdk@1.0.4:
+ resolution: {integrity: sha512-AnsjfHyHpQ/EFeAnG216WY7A5LiYCoZzCSygiLvfXC3H3LFGCprErteUcszaVluGOhuOTbJS3jWHrSDYPBBygg==}
+ peerDependencies:
+ react: ^18 || ^19 || ^19.0.0-rc
+ react-dom: ^18 || ^19 || ^19.0.0-rc
+
+ cookie@0.4.2:
+ resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
+ engines: {node: '>= 0.6'}
+
+ copy-anything@3.0.5:
+ resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
+ engines: {node: '>=12.13'}
+
+ cors@2.8.5:
+ resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
+ engines: {node: '>= 0.10'}
+
+ cronstrue@2.59.0:
+ resolution: {integrity: sha512-YKGmAy84hKH+hHIIER07VCAHf9u0Ldelx1uU6EBxsRPDXIA1m5fsKmJfyC3xBhw6cVC/1i83VdbL4PvepTrt8A==}
+ hasBin: true
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ csstype@3.1.3:
+ resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+
+ d3-array@3.2.4:
+ resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
+ engines: {node: '>=12'}
+
+ d3-color@3.1.0:
+ resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+ engines: {node: '>=12'}
+
+ d3-ease@3.0.1:
+ resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+ engines: {node: '>=12'}
+
+ d3-format@3.1.0:
+ resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
+ engines: {node: '>=12'}
+
+ d3-interpolate@3.0.1:
+ resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+ engines: {node: '>=12'}
+
+ d3-path@3.1.0:
+ resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
+ engines: {node: '>=12'}
+
+ d3-scale@4.0.2:
+ resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
+ engines: {node: '>=12'}
+
+ d3-shape@3.2.0:
+ resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
+ engines: {node: '>=12'}
+
+ d3-time-format@4.1.0:
+ resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
+ engines: {node: '>=12'}
+
+ d3-time@3.1.0:
+ resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
+ engines: {node: '>=12'}
+
+ d3-timer@3.0.1:
+ resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+ engines: {node: '>=12'}
+
+ date-fns-jalali@4.1.0-0:
+ resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==}
+
+ date-fns@4.1.0:
+ resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
+
+ debug@4.3.7:
+ resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ debug@4.4.1:
+ resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ decimal.js-light@2.5.1:
+ resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
+
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
+ detect-libc@2.0.4:
+ resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
+ engines: {node: '>=8'}
+
+ detect-node-es@1.1.0:
+ resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
+
+ dom-helpers@5.2.1:
+ resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
+
+ electron-to-chromium@1.5.214:
+ resolution: {integrity: sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q==}
+
+ embla-carousel-react@8.5.1:
+ resolution: {integrity: sha512-z9Y0K84BJvhChXgqn2CFYbfEi6AwEr+FFVVKm/MqbTQ2zIzO1VQri6w67LcfpVF0AjbhwVMywDZqY4alYkjW5w==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+
+ embla-carousel-reactive-utils@8.5.1:
+ resolution: {integrity: sha512-n7VSoGIiiDIc4MfXF3ZRTO59KDp820QDuyBDGlt5/65+lumPHxX2JLz0EZ23hZ4eg4vZGUXwMkYv02fw2JVo/A==}
+ peerDependencies:
+ embla-carousel: 8.5.1
+
+ embla-carousel@8.5.1:
+ resolution: {integrity: sha512-JUb5+FOHobSiWQ2EJNaueCNT/cQU9L6XWBbWmorWPQT9bkbk+fhsuLr8wWrzXKagO3oWszBO7MSx+GfaRk4E6A==}
+
+ engine.io-client@6.5.4:
+ resolution: {integrity: sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==}
+
+ engine.io-parser@5.2.3:
+ resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
+ engines: {node: '>=10.0.0'}
+
+ engine.io@6.5.5:
+ resolution: {integrity: sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==}
+ engines: {node: '>=10.2.0'}
+
+ enhanced-resolve@5.18.3:
+ resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
+ engines: {node: '>=10.13.0'}
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ eventemitter3@4.0.7:
+ resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
+
+ eventsource-parser@3.0.6:
+ resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
+ engines: {node: '>=18.0.0'}
+
+ eventsource@3.0.7:
+ resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
+ engines: {node: '>=18.0.0'}
+
+ evt@2.5.9:
+ resolution: {integrity: sha512-GpjX476FSlttEGWHT8BdVMoI8wGXQGbEOtKcP4E+kggg+yJzXBZN2n4x7TS/zPBJ1DZqWI+rguZZApjjzQ0HpA==}
+
+ execa@8.0.1:
+ resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
+ engines: {node: '>=16.17'}
+
+ fast-equals@5.2.2:
+ resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==}
+ engines: {node: '>=6.0.0'}
+
+ fraction.js@4.3.7:
+ resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ geist@1.4.2:
+ resolution: {integrity: sha512-OQUga/KUc8ueijck6EbtT07L4tZ5+TZgjw8PyWfxo16sL5FWk7gNViPNU8hgCFjy6bJi9yuTP+CRpywzaGN8zw==}
+ peerDependencies:
+ next: '>=13.2.0'
+
+ get-nonce@1.0.1:
+ resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
+ engines: {node: '>=6'}
+
+ get-stream@8.0.1:
+ resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
+ engines: {node: '>=16'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
+ human-signals@5.0.0:
+ resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
+ engines: {node: '>=16.17.0'}
+
+ humanize-duration@3.33.0:
+ resolution: {integrity: sha512-vYJX7BSzn7EQ4SaP2lPYVy+icHDppB6k7myNeI3wrSRfwMS5+BHyGgzpHR0ptqJ2AQ6UuIKrclSg5ve6Ci4IAQ==}
+
+ import-in-the-middle@1.14.2:
+ resolution: {integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==}
+
+ input-otp@1.4.1:
+ resolution: {integrity: sha512-+yvpmKYKHi9jIGngxagY9oWiiblPB7+nEO75F2l2o4vs+6vpPZZmUl4tBNYuTCvQjhvEIbdNeJu70bhfYP2nbw==}
+ peerDependencies:
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
+
+ internmap@2.0.3:
+ resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
+ engines: {node: '>=12'}
+
+ is-core-module@2.16.1:
+ resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+ engines: {node: '>= 0.4'}
+
+ is-stream@3.0.0:
+ resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ is-what@4.1.16:
+ resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
+ engines: {node: '>=12.13'}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ jiti@2.5.1:
+ resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==}
+ hasBin: true
+
+ jose@5.10.0:
+ resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ lightningcss-darwin-arm64@1.30.1:
+ resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.30.1:
+ resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.30.1:
+ resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.30.1:
+ resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.30.1:
+ resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.30.1:
+ resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.30.1:
+ resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.30.1:
+ resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.30.1:
+ resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.30.1:
+ resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.30.1:
+ resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==}
+ engines: {node: '>= 12.0.0'}
+
+ lodash@4.17.21:
+ resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+ long@5.3.2:
+ resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ lucide-react@0.454.0:
+ resolution: {integrity: sha512-hw7zMDwykCLnEzgncEEjHeA6+45aeEzRYuKHuyRSOPkhko+J3ySGjGIzu+mmMfDFG1vazHepMaYFYHbTFAZAAQ==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc
+
+ magic-string@0.30.18:
+ resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==}
+
+ merge-stream@2.0.0:
+ resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
+ mimic-fn@4.0.0:
+ resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
+ engines: {node: '>=12'}
+
+ minimal-polyfills@2.2.3:
+ resolution: {integrity: sha512-oxdmJ9cL+xV72h0xYxp4tP2d5/fTBpP45H8DIOn9pASuF8a3IYTf+25fMGDYGiWW+MFsuog6KD6nfmhZJQ+uUw==}
+
+ minipass@7.1.2:
+ resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minizlib@3.0.2:
+ resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==}
+ engines: {node: '>= 18'}
+
+ mkdirp@3.0.1:
+ resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ module-details-from-path@1.0.4:
+ resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ nanoid@3.3.8:
+ resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ negotiator@0.6.3:
+ resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
+ engines: {node: '>= 0.6'}
+
+ next-themes@0.4.6:
+ resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
+ peerDependencies:
+ react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+
+ next@14.2.25:
+ resolution: {integrity: sha512-N5M7xMc4wSb4IkPvEV5X2BRRXUmhVHNyaXwEM86+voXthSZz8ZiRyQW4p9mwAoAPIm6OzuVZtn7idgEJeAJN3Q==}
+ engines: {node: '>=18.17.0'}
+ hasBin: true
+ peerDependencies:
+ '@opentelemetry/api': ^1.1.0
+ '@playwright/test': ^1.41.2
+ react: ^18.2.0
+ react-dom: ^18.2.0
+ sass: ^1.3.0
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+ '@playwright/test':
+ optional: true
+ sass:
+ optional: true
+
+ node-releases@2.0.19:
+ resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
+
+ normalize-range@0.1.2:
+ resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
+ engines: {node: '>=0.10.0'}
+
+ npm-run-path@5.3.0:
+ resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ onetime@6.0.0:
+ resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
+ engines: {node: '>=12'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-key@4.0.0:
+ resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
+ engines: {node: '>=12'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ postcss-value-parser@4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+
+ postcss@8.4.31:
+ resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ prom-client@15.1.3:
+ resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==}
+ engines: {node: ^16 || ^18 || >=20}
+
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+ protobufjs@7.5.4:
+ resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==}
+ engines: {node: '>=12.0.0'}
+
+ react-day-picker@9.8.0:
+ resolution: {integrity: sha512-E0yhhg7R+pdgbl/2toTb0xBhsEAtmAx1l7qjIWYfcxOy8w4rTSVfbtBoSzVVhPwKP/5E9iL38LivzoE3AQDhCQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ react: '>=16.8.0'
+
+ react-dom@19.1.0:
+ resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
+ peerDependencies:
+ react: ^19.1.0
+
+ react-hook-form@7.62.0:
+ resolution: {integrity: sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18 || ^19
+
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react-is@18.3.1:
+ resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+
+ react-remove-scroll-bar@2.3.8:
+ resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-remove-scroll@2.7.1:
+ resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-resizable-panels@2.1.9:
+ resolution: {integrity: sha512-z77+X08YDIrgAes4jl8xhnUu1LNIRp4+E7cv4xHmLOxxUPO/ML7PSrE813b90vj7xvQ1lcf7g2uA9GeMZonjhQ==}
+ peerDependencies:
+ react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+
+ react-smooth@4.0.4:
+ resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ react-style-singleton@2.2.3:
+ resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-transition-group@4.4.5:
+ resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
+ peerDependencies:
+ react: '>=16.6.0'
+ react-dom: '>=16.6.0'
+
+ react@19.1.0:
+ resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
+ engines: {node: '>=0.10.0'}
+
+ recharts-scale@0.4.5:
+ resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==}
+
+ recharts@2.15.4:
+ resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ require-in-the-middle@7.5.2:
+ resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==}
+ engines: {node: '>=8.6.0'}
+
+ resolve@1.22.10:
+ resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ run-exclusive@2.2.19:
+ resolution: {integrity: sha512-K3mdoAi7tjJ/qT7Flj90L7QyPozwUaAG+CVhkdDje4HLKXUYC3N/Jzkau3flHVDLQVhiHBtcimVodMjN9egYbA==}
+
+ scheduler@0.26.0:
+ resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
+
+ server-only@0.0.1:
+ resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
+ slug@6.1.0:
+ resolution: {integrity: sha512-x6vLHCMasg4DR2LPiyFGI0gJJhywY6DTiGhCrOMzb3SOk/0JVLIaL4UhyFSHu04SD3uAavrKY/K3zZ3i6iRcgA==}
+
+ socket.io-adapter@2.5.5:
+ resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==}
+
+ socket.io-client@4.7.5:
+ resolution: {integrity: sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==}
+ engines: {node: '>=10.0.0'}
+
+ socket.io-parser@4.2.4:
+ resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
+ engines: {node: '>=10.0.0'}
+
+ socket.io@4.7.4:
+ resolution: {integrity: sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw==}
+ engines: {node: '>=10.2.0'}
+
+ sonner@1.7.4:
+ resolution: {integrity: sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==}
+ peerDependencies:
+ react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ std-env@3.9.0:
+ resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
+
+ streamsearch@1.1.0:
+ resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
+ engines: {node: '>=10.0.0'}
+
+ strip-final-newline@3.0.0:
+ resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
+ engines: {node: '>=12'}
+
+ styled-jsx@5.1.1:
+ resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==}
+ engines: {node: '>= 12.0.0'}
+ peerDependencies:
+ '@babel/core': '*'
+ babel-plugin-macros: '*'
+ react: '>= 16.8.0 || 17.x.x || ^18.0.0-0'
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ babel-plugin-macros:
+ optional: true
+
+ superjson@2.2.2:
+ resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==}
+ engines: {node: '>=16'}
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ swr@2.3.6:
+ resolution: {integrity: sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==}
+ peerDependencies:
+ react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ tailwind-merge@3.3.1:
+ resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
+
+ tailwindcss-animate@1.0.7:
+ resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || insiders'
+
+ tailwindcss@4.1.12:
+ resolution: {integrity: sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==}
+
+ tapable@2.2.3:
+ resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==}
+ engines: {node: '>=6'}
+
+ tar@7.4.3:
+ resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
+ engines: {node: '>=18'}
+
+ tdigest@0.1.2:
+ resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==}
+
+ tiny-invariant@1.3.3:
+ resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
+
+ tinyexec@0.3.2:
+ resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+
+ tsafe@1.8.5:
+ resolution: {integrity: sha512-LFWTWQrW6rwSY+IBNFl2ridGfUzVsPwrZ26T4KUJww/py8rzaQ/SY+MIz6YROozpUCaRcuISqagmlwub9YT9kw==}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ tw-animate-css@1.3.3:
+ resolution: {integrity: sha512-tXE2TRWrskc4TU3RDd7T8n8Np/wCfoeH9gz22c7PzYqNPQ9FBGFbWWzwL0JyHcFp+jHozmF76tbHfPAx22ua2Q==}
+
+ typescript@5.9.2:
+ resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ ulid@2.4.0:
+ resolution: {integrity: sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg==}
+ hasBin: true
+
+ uncrypto@0.1.3:
+ resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
+
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+ update-browserslist-db@1.1.3:
+ resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ use-callback-ref@1.3.3:
+ resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-sidecar@1.1.3:
+ resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-sync-external-store@1.5.0:
+ resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ uuid@9.0.1:
+ resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
+ hasBin: true
+
+ vary@1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+
+ vaul@0.9.9:
+ resolution: {integrity: sha512-7afKg48srluhZwIkaU+lgGtFCUsYBSGOl8vcc8N/M3YQlZFlynHD15AE+pwrYdc826o7nrIND4lL9Y6b9WWZZQ==}
+ peerDependencies:
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+
+ victory-vendor@36.9.2:
+ resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ ws@8.17.1:
+ resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ ws@8.18.3:
+ resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ xmlhttprequest-ssl@2.0.0:
+ resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==}
+ engines: {node: '>=0.4.0'}
+
+ yallist@5.0.0:
+ resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
+ engines: {node: '>=18'}
+
+ zod-error@1.5.0:
+ resolution: {integrity: sha512-zzopKZ/skI9iXpqCEPj+iLCKl9b88E43ehcU+sbRoHuwGd9F1IDVGQ70TyO6kmfiRL1g4IXkjsXK+g1gLYl4WQ==}
+
+ zod-validation-error@1.5.0:
+ resolution: {integrity: sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw==}
+ engines: {node: '>=16.0.0'}
+ peerDependencies:
+ zod: ^3.18.0
+
+ zod@3.25.67:
+ resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==}
+
+ zod@3.25.76:
+ resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
+
+snapshots:
+
+ '@alloc/quick-lru@5.2.0': {}
+
+ '@babel/runtime@7.28.3': {}
+
+ '@bugsnag/cuid@3.2.1': {}
+
+ '@date-fns/tz@1.2.0': {}
+
+ '@electric-sql/client@1.0.0-beta.1':
+ optionalDependencies:
+ '@rollup/rollup-darwin-arm64': 4.50.0
+
+ '@floating-ui/core@1.7.3':
+ dependencies:
+ '@floating-ui/utils': 0.2.10
+
+ '@floating-ui/dom@1.7.4':
+ dependencies:
+ '@floating-ui/core': 1.7.3
+ '@floating-ui/utils': 0.2.10
+
+ '@floating-ui/react-dom@2.1.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@floating-ui/dom': 1.7.4
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
+ '@floating-ui/utils@0.2.10': {}
+
+ '@google-cloud/precise-date@4.0.0': {}
+
+ '@hookform/resolvers@3.10.0(react-hook-form@7.62.0(react@19.1.0))':
+ dependencies:
+ react-hook-form: 7.62.0(react@19.1.0)
+
+ '@isaacs/fs-minipass@4.0.1':
+ dependencies:
+ minipass: 7.1.2
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.30
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.30
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.30':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@jsonhero/path@1.0.21': {}
+
+ '@next/env@14.2.25': {}
+
+ '@next/swc-darwin-arm64@14.2.25':
+ optional: true
+
+ '@next/swc-darwin-x64@14.2.25':
+ optional: true
+
+ '@next/swc-linux-arm64-gnu@14.2.25':
+ optional: true
+
+ '@next/swc-linux-arm64-musl@14.2.25':
+ optional: true
+
+ '@next/swc-linux-x64-gnu@14.2.25':
+ optional: true
+
+ '@next/swc-linux-x64-musl@14.2.25':
+ optional: true
+
+ '@next/swc-win32-arm64-msvc@14.2.25':
+ optional: true
+
+ '@next/swc-win32-ia32-msvc@14.2.25':
+ optional: true
+
+ '@next/swc-win32-x64-msvc@14.2.25':
+ optional: true
+
+ '@opentelemetry/api-logs@0.203.0':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+
+ '@opentelemetry/api@1.9.0': {}
+
+ '@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0)':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+
+ '@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0)':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/semantic-conventions': 1.36.0
+
+ '@opentelemetry/exporter-logs-otlp-http@0.203.0(@opentelemetry/api@1.9.0)':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/api-logs': 0.203.0
+ '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/otlp-exporter-base': 0.203.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/otlp-transformer': 0.203.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-logs': 0.203.0(@opentelemetry/api@1.9.0)
+
+ '@opentelemetry/exporter-trace-otlp-http@0.203.0(@opentelemetry/api@1.9.0)':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/otlp-exporter-base': 0.203.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/otlp-transformer': 0.203.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0)
+
+ '@opentelemetry/instrumentation@0.203.0(@opentelemetry/api@1.9.0)':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/api-logs': 0.203.0
+ import-in-the-middle: 1.14.2
+ require-in-the-middle: 7.5.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@opentelemetry/otlp-exporter-base@0.203.0(@opentelemetry/api@1.9.0)':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/otlp-transformer': 0.203.0(@opentelemetry/api@1.9.0)
+
+ '@opentelemetry/otlp-transformer@0.203.0(@opentelemetry/api@1.9.0)':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/api-logs': 0.203.0
+ '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-logs': 0.203.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-metrics': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0)
+ protobufjs: 7.5.4
+
+ '@opentelemetry/resources@2.0.1(@opentelemetry/api@1.9.0)':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/semantic-conventions': 1.36.0
+
+ '@opentelemetry/sdk-logs@0.203.0(@opentelemetry/api@1.9.0)':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/api-logs': 0.203.0
+ '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0)
+
+ '@opentelemetry/sdk-metrics@2.0.1(@opentelemetry/api@1.9.0)':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0)
+
+ '@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0)':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/semantic-conventions': 1.36.0
+
+ '@opentelemetry/sdk-trace-node@2.0.1(@opentelemetry/api@1.9.0)':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/context-async-hooks': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0)
+
+ '@opentelemetry/semantic-conventions@1.36.0': {}
+
+ '@protobufjs/aspromise@1.1.2': {}
+
+ '@protobufjs/base64@1.1.2': {}
+
+ '@protobufjs/codegen@2.0.4': {}
+
+ '@protobufjs/eventemitter@1.1.0': {}
+
+ '@protobufjs/fetch@1.1.0':
+ dependencies:
+ '@protobufjs/aspromise': 1.1.2
+ '@protobufjs/inquire': 1.1.0
+
+ '@protobufjs/float@1.0.2': {}
+
+ '@protobufjs/inquire@1.1.0': {}
+
+ '@protobufjs/path@1.1.2': {}
+
+ '@protobufjs/pool@1.1.0': {}
+
+ '@protobufjs/utf8@1.1.0': {}
+
+ '@radix-ui/number@1.1.0': {}
+
+ '@radix-ui/primitive@1.1.1': {}
+
+ '@radix-ui/react-accordion@1.2.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-collapsible': 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-alert-dialog@1.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-dialog': 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-arrow@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-aspect-ratio@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-avatar@1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-checkbox@1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-collapsible@1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-collection@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-context-menu@2.2.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-menu': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-context@1.1.1(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-dialog@1.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ aria-hidden: 1.2.6
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-remove-scroll: 2.7.1(@types/react@18.3.24)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-direction@1.1.0(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-dropdown-menu@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-menu': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-focus-scope@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-hover-card@1.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-id@1.1.0(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-id@1.1.1(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-label@2.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-menu@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ aria-hidden: 1.2.6
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-remove-scroll: 2.7.1(@types/react@18.3.24)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-menubar@1.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-menu': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-navigation-menu@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-visually-hidden': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-popover@1.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ aria-hidden: 1.2.6
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-remove-scroll: 2.7.1(@types/react@18.3.24)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-popper@1.2.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-arrow': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/rect': 1.1.0
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-portal@1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-presence@1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-primitive@2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-slot': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-slot': 1.2.3(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-progress@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-radio-group@1.2.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-roving-focus@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-scroll-area@1.2.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/number': 1.1.0
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-select@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/number': 1.1.0
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-visually-hidden': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ aria-hidden: 1.2.6
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-remove-scroll: 2.7.1(@types/react@18.3.24)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-separator@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-slider@1.2.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/number': 1.1.0
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-slot@1.1.1(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-slot@1.2.3(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-switch@1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-tabs@1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-toast@1.2.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-visually-hidden': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-toggle-group@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-toggle': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-toggle@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-tooltip@1.1.6(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-visually-hidden': 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-use-previous@1.1.0(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-use-rect@1.1.0(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/rect': 1.1.0
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-use-size@1.1.0(@types/react@18.3.24)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.24)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ '@radix-ui/react-visually-hidden@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+ '@types/react-dom': 18.3.7(@types/react@18.3.24)
+
+ '@radix-ui/rect@1.1.0': {}
+
+ '@rollup/rollup-darwin-arm64@4.50.0':
+ optional: true
+
+ '@socket.io/component-emitter@3.1.2': {}
+
+ '@swc/counter@0.1.3': {}
+
+ '@swc/helpers@0.5.5':
+ dependencies:
+ '@swc/counter': 0.1.3
+ tslib: 2.8.1
+
+ '@tailwindcss/node@4.1.12':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.18.3
+ jiti: 2.5.1
+ lightningcss: 1.30.1
+ magic-string: 0.30.18
+ source-map-js: 1.2.1
+ tailwindcss: 4.1.12
+
+ '@tailwindcss/oxide-android-arm64@4.1.12':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.12':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.1.12':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.12':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.12':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.12':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.12':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.12':
+ optional: true
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.12':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.12':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.12':
+ optional: true
+
+ '@tailwindcss/oxide@4.1.12':
+ dependencies:
+ detect-libc: 2.0.4
+ tar: 7.4.3
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.1.12
+ '@tailwindcss/oxide-darwin-arm64': 4.1.12
+ '@tailwindcss/oxide-darwin-x64': 4.1.12
+ '@tailwindcss/oxide-freebsd-x64': 4.1.12
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.12
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.1.12
+ '@tailwindcss/oxide-linux-arm64-musl': 4.1.12
+ '@tailwindcss/oxide-linux-x64-gnu': 4.1.12
+ '@tailwindcss/oxide-linux-x64-musl': 4.1.12
+ '@tailwindcss/oxide-wasm32-wasi': 4.1.12
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.1.12
+ '@tailwindcss/oxide-win32-x64-msvc': 4.1.12
+
+ '@tailwindcss/postcss@4.1.12':
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ '@tailwindcss/node': 4.1.12
+ '@tailwindcss/oxide': 4.1.12
+ postcss: 8.5.6
+ tailwindcss: 4.1.12
+
+ '@trigger.dev/core@4.0.2':
+ dependencies:
+ '@bugsnag/cuid': 3.2.1
+ '@electric-sql/client': 1.0.0-beta.1
+ '@google-cloud/precise-date': 4.0.0
+ '@jsonhero/path': 1.0.21
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/api-logs': 0.203.0
+ '@opentelemetry/core': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/exporter-logs-otlp-http': 0.203.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/exporter-trace-otlp-http': 0.203.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.203.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-logs': 0.203.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/semantic-conventions': 1.36.0
+ dequal: 2.0.3
+ eventsource: 3.0.7
+ eventsource-parser: 3.0.6
+ execa: 8.0.1
+ humanize-duration: 3.33.0
+ jose: 5.10.0
+ nanoid: 3.3.8
+ prom-client: 15.1.3
+ socket.io: 4.7.4
+ socket.io-client: 4.7.5
+ std-env: 3.9.0
+ superjson: 2.2.2
+ tinyexec: 0.3.2
+ uncrypto: 0.1.3
+ zod: 3.25.76
+ zod-error: 1.5.0
+ zod-validation-error: 1.5.0(zod@3.25.76)
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ '@trigger.dev/react-hooks@4.0.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@trigger.dev/core': 4.0.2
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ swr: 2.3.6(react@19.1.0)
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ '@trigger.dev/sdk@4.0.2(zod@3.25.67)':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/semantic-conventions': 1.36.0
+ '@trigger.dev/core': 4.0.2
+ chalk: 5.6.0
+ cronstrue: 2.59.0
+ debug: 4.4.1
+ evt: 2.5.9
+ slug: 6.1.0
+ ulid: 2.4.0
+ uncrypto: 0.1.3
+ uuid: 9.0.1
+ ws: 8.18.3
+ zod: 3.25.67
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ '@types/cookie@0.4.1': {}
+
+ '@types/cors@2.8.19':
+ dependencies:
+ '@types/node': 22.18.1
+
+ '@types/d3-array@3.2.1': {}
+
+ '@types/d3-color@3.1.3': {}
+
+ '@types/d3-ease@3.0.2': {}
+
+ '@types/d3-interpolate@3.0.4':
+ dependencies:
+ '@types/d3-color': 3.1.3
+
+ '@types/d3-path@3.1.1': {}
+
+ '@types/d3-scale@4.0.9':
+ dependencies:
+ '@types/d3-time': 3.0.4
+
+ '@types/d3-shape@3.1.7':
+ dependencies:
+ '@types/d3-path': 3.1.1
+
+ '@types/d3-time@3.0.4': {}
+
+ '@types/d3-timer@3.0.2': {}
+
+ '@types/node@22.18.1':
+ dependencies:
+ undici-types: 6.21.0
+
+ '@types/prop-types@15.7.15': {}
+
+ '@types/react-dom@18.3.7(@types/react@18.3.24)':
+ dependencies:
+ '@types/react': 18.3.24
+
+ '@types/react@18.3.24':
+ dependencies:
+ '@types/prop-types': 15.7.15
+ csstype: 3.1.3
+
+ '@vercel/analytics@1.3.1(next@14.2.25(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ server-only: 0.0.1
+ optionalDependencies:
+ next: 14.2.25(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+
+ accepts@1.3.8:
+ dependencies:
+ mime-types: 2.1.35
+ negotiator: 0.6.3
+
+ acorn-import-attributes@1.9.5(acorn@8.15.0):
+ dependencies:
+ acorn: 8.15.0
+
+ acorn@8.15.0: {}
+
+ aria-hidden@1.2.6:
+ dependencies:
+ tslib: 2.8.1
+
+ autoprefixer@10.4.21(postcss@8.5.6):
+ dependencies:
+ browserslist: 4.25.4
+ caniuse-lite: 1.0.30001739
+ fraction.js: 4.3.7
+ normalize-range: 0.1.2
+ picocolors: 1.1.1
+ postcss: 8.5.6
+ postcss-value-parser: 4.2.0
+
+ base64id@2.0.0: {}
+
+ bintrees@1.0.2: {}
+
+ browserslist@4.25.4:
+ dependencies:
+ caniuse-lite: 1.0.30001739
+ electron-to-chromium: 1.5.214
+ node-releases: 2.0.19
+ update-browserslist-db: 1.1.3(browserslist@4.25.4)
+
+ busboy@1.6.0:
+ dependencies:
+ streamsearch: 1.1.0
+
+ caniuse-lite@1.0.30001739: {}
+
+ chalk@5.6.0: {}
+
+ chownr@3.0.0: {}
+
+ cjs-module-lexer@1.4.3: {}
+
+ class-variance-authority@0.7.1:
+ dependencies:
+ clsx: 2.1.1
+
+ client-only@0.0.1: {}
+
+ clsx@2.1.1: {}
+
+ cmdk@1.0.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ '@radix-ui/react-dialog': 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@18.3.24)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ use-sync-external-store: 1.5.0(react@19.1.0)
+ transitivePeerDependencies:
+ - '@types/react'
+ - '@types/react-dom'
+
+ cookie@0.4.2: {}
+
+ copy-anything@3.0.5:
+ dependencies:
+ is-what: 4.1.16
+
+ cors@2.8.5:
+ dependencies:
+ object-assign: 4.1.1
+ vary: 1.1.2
+
+ cronstrue@2.59.0: {}
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ csstype@3.1.3: {}
+
+ d3-array@3.2.4:
+ dependencies:
+ internmap: 2.0.3
+
+ d3-color@3.1.0: {}
+
+ d3-ease@3.0.1: {}
+
+ d3-format@3.1.0: {}
+
+ d3-interpolate@3.0.1:
+ dependencies:
+ d3-color: 3.1.0
+
+ d3-path@3.1.0: {}
+
+ d3-scale@4.0.2:
+ dependencies:
+ d3-array: 3.2.4
+ d3-format: 3.1.0
+ d3-interpolate: 3.0.1
+ d3-time: 3.1.0
+ d3-time-format: 4.1.0
+
+ d3-shape@3.2.0:
+ dependencies:
+ d3-path: 3.1.0
+
+ d3-time-format@4.1.0:
+ dependencies:
+ d3-time: 3.1.0
+
+ d3-time@3.1.0:
+ dependencies:
+ d3-array: 3.2.4
+
+ d3-timer@3.0.1: {}
+
+ date-fns-jalali@4.1.0-0: {}
+
+ date-fns@4.1.0: {}
+
+ debug@4.3.7:
+ dependencies:
+ ms: 2.1.3
+
+ debug@4.4.1:
+ dependencies:
+ ms: 2.1.3
+
+ decimal.js-light@2.5.1: {}
+
+ dequal@2.0.3: {}
+
+ detect-libc@2.0.4: {}
+
+ detect-node-es@1.1.0: {}
+
+ dom-helpers@5.2.1:
+ dependencies:
+ '@babel/runtime': 7.28.3
+ csstype: 3.1.3
+
+ electron-to-chromium@1.5.214: {}
+
+ embla-carousel-react@8.5.1(react@19.1.0):
+ dependencies:
+ embla-carousel: 8.5.1
+ embla-carousel-reactive-utils: 8.5.1(embla-carousel@8.5.1)
+ react: 19.1.0
+
+ embla-carousel-reactive-utils@8.5.1(embla-carousel@8.5.1):
+ dependencies:
+ embla-carousel: 8.5.1
+
+ embla-carousel@8.5.1: {}
+
+ engine.io-client@6.5.4:
+ dependencies:
+ '@socket.io/component-emitter': 3.1.2
+ debug: 4.3.7
+ engine.io-parser: 5.2.3
+ ws: 8.17.1
+ xmlhttprequest-ssl: 2.0.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ engine.io-parser@5.2.3: {}
+
+ engine.io@6.5.5:
+ dependencies:
+ '@types/cookie': 0.4.1
+ '@types/cors': 2.8.19
+ '@types/node': 22.18.1
+ accepts: 1.3.8
+ base64id: 2.0.0
+ cookie: 0.4.2
+ cors: 2.8.5
+ debug: 4.3.7
+ engine.io-parser: 5.2.3
+ ws: 8.17.1
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ enhanced-resolve@5.18.3:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.2.3
+
+ escalade@3.2.0: {}
+
+ eventemitter3@4.0.7: {}
+
+ eventsource-parser@3.0.6: {}
+
+ eventsource@3.0.7:
+ dependencies:
+ eventsource-parser: 3.0.6
+
+ evt@2.5.9:
+ dependencies:
+ minimal-polyfills: 2.2.3
+ run-exclusive: 2.2.19
+ tsafe: 1.8.5
+
+ execa@8.0.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ get-stream: 8.0.1
+ human-signals: 5.0.0
+ is-stream: 3.0.0
+ merge-stream: 2.0.0
+ npm-run-path: 5.3.0
+ onetime: 6.0.0
+ signal-exit: 4.1.0
+ strip-final-newline: 3.0.0
+
+ fast-equals@5.2.2: {}
+
+ fraction.js@4.3.7: {}
+
+ function-bind@1.1.2: {}
+
+ geist@1.4.2(next@14.2.25(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)):
+ dependencies:
+ next: 14.2.25(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+
+ get-nonce@1.0.1: {}
+
+ get-stream@8.0.1: {}
+
+ graceful-fs@4.2.11: {}
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
+ human-signals@5.0.0: {}
+
+ humanize-duration@3.33.0: {}
+
+ import-in-the-middle@1.14.2:
+ dependencies:
+ acorn: 8.15.0
+ acorn-import-attributes: 1.9.5(acorn@8.15.0)
+ cjs-module-lexer: 1.4.3
+ module-details-from-path: 1.0.4
+
+ input-otp@1.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
+ internmap@2.0.3: {}
+
+ is-core-module@2.16.1:
+ dependencies:
+ hasown: 2.0.2
+
+ is-stream@3.0.0: {}
+
+ is-what@4.1.16: {}
+
+ isexe@2.0.0: {}
+
+ jiti@2.5.1: {}
+
+ jose@5.10.0: {}
+
+ js-tokens@4.0.0: {}
+
+ lightningcss-darwin-arm64@1.30.1:
+ optional: true
+
+ lightningcss-darwin-x64@1.30.1:
+ optional: true
+
+ lightningcss-freebsd-x64@1.30.1:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.30.1:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.30.1:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.30.1:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.30.1:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.30.1:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.30.1:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.30.1:
+ optional: true
+
+ lightningcss@1.30.1:
+ dependencies:
+ detect-libc: 2.0.4
+ optionalDependencies:
+ lightningcss-darwin-arm64: 1.30.1
+ lightningcss-darwin-x64: 1.30.1
+ lightningcss-freebsd-x64: 1.30.1
+ lightningcss-linux-arm-gnueabihf: 1.30.1
+ lightningcss-linux-arm64-gnu: 1.30.1
+ lightningcss-linux-arm64-musl: 1.30.1
+ lightningcss-linux-x64-gnu: 1.30.1
+ lightningcss-linux-x64-musl: 1.30.1
+ lightningcss-win32-arm64-msvc: 1.30.1
+ lightningcss-win32-x64-msvc: 1.30.1
+
+ lodash@4.17.21: {}
+
+ long@5.3.2: {}
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ lucide-react@0.454.0(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+
+ magic-string@0.30.18:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ merge-stream@2.0.0: {}
+
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
+ mimic-fn@4.0.0: {}
+
+ minimal-polyfills@2.2.3: {}
+
+ minipass@7.1.2: {}
+
+ minizlib@3.0.2:
+ dependencies:
+ minipass: 7.1.2
+
+ mkdirp@3.0.1: {}
+
+ module-details-from-path@1.0.4: {}
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.11: {}
+
+ nanoid@3.3.8: {}
+
+ negotiator@0.6.3: {}
+
+ next-themes@0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
+ next@14.2.25(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ '@next/env': 14.2.25
+ '@swc/helpers': 0.5.5
+ busboy: 1.6.0
+ caniuse-lite: 1.0.30001739
+ graceful-fs: 4.2.11
+ postcss: 8.4.31
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ styled-jsx: 5.1.1(react@19.1.0)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 14.2.25
+ '@next/swc-darwin-x64': 14.2.25
+ '@next/swc-linux-arm64-gnu': 14.2.25
+ '@next/swc-linux-arm64-musl': 14.2.25
+ '@next/swc-linux-x64-gnu': 14.2.25
+ '@next/swc-linux-x64-musl': 14.2.25
+ '@next/swc-win32-arm64-msvc': 14.2.25
+ '@next/swc-win32-ia32-msvc': 14.2.25
+ '@next/swc-win32-x64-msvc': 14.2.25
+ '@opentelemetry/api': 1.9.0
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+
+ node-releases@2.0.19: {}
+
+ normalize-range@0.1.2: {}
+
+ npm-run-path@5.3.0:
+ dependencies:
+ path-key: 4.0.0
+
+ object-assign@4.1.1: {}
+
+ onetime@6.0.0:
+ dependencies:
+ mimic-fn: 4.0.0
+
+ path-key@3.1.1: {}
+
+ path-key@4.0.0: {}
+
+ path-parse@1.0.7: {}
+
+ picocolors@1.1.1: {}
+
+ postcss-value-parser@4.2.0: {}
+
+ postcss@8.4.31:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ prom-client@15.1.3:
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ tdigest: 0.1.2
+
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
+ protobufjs@7.5.4:
+ dependencies:
+ '@protobufjs/aspromise': 1.1.2
+ '@protobufjs/base64': 1.1.2
+ '@protobufjs/codegen': 2.0.4
+ '@protobufjs/eventemitter': 1.1.0
+ '@protobufjs/fetch': 1.1.0
+ '@protobufjs/float': 1.0.2
+ '@protobufjs/inquire': 1.1.0
+ '@protobufjs/path': 1.1.2
+ '@protobufjs/pool': 1.1.0
+ '@protobufjs/utf8': 1.1.0
+ '@types/node': 22.18.1
+ long: 5.3.2
+
+ react-day-picker@9.8.0(react@19.1.0):
+ dependencies:
+ '@date-fns/tz': 1.2.0
+ date-fns: 4.1.0
+ date-fns-jalali: 4.1.0-0
+ react: 19.1.0
+
+ react-dom@19.1.0(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ scheduler: 0.26.0
+
+ react-hook-form@7.62.0(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+
+ react-is@16.13.1: {}
+
+ react-is@18.3.1: {}
+
+ react-remove-scroll-bar@2.3.8(@types/react@18.3.24)(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ react-style-singleton: 2.2.3(@types/react@18.3.24)(react@19.1.0)
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ react-remove-scroll@2.7.1(@types/react@18.3.24)(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ react-remove-scroll-bar: 2.3.8(@types/react@18.3.24)(react@19.1.0)
+ react-style-singleton: 2.2.3(@types/react@18.3.24)(react@19.1.0)
+ tslib: 2.8.1
+ use-callback-ref: 1.3.3(@types/react@18.3.24)(react@19.1.0)
+ use-sidecar: 1.1.3(@types/react@18.3.24)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ react-resizable-panels@2.1.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
+ react-smooth@4.0.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ fast-equals: 5.2.2
+ prop-types: 15.8.1
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-transition-group: 4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+
+ react-style-singleton@2.2.3(@types/react@18.3.24)(react@19.1.0):
+ dependencies:
+ get-nonce: 1.0.1
+ react: 19.1.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ react-transition-group@4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ '@babel/runtime': 7.28.3
+ dom-helpers: 5.2.1
+ loose-envify: 1.4.0
+ prop-types: 15.8.1
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
+ react@19.1.0: {}
+
+ recharts-scale@0.4.5:
+ dependencies:
+ decimal.js-light: 2.5.1
+
+ recharts@2.15.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ clsx: 2.1.1
+ eventemitter3: 4.0.7
+ lodash: 4.17.21
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-is: 18.3.1
+ react-smooth: 4.0.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ recharts-scale: 0.4.5
+ tiny-invariant: 1.3.3
+ victory-vendor: 36.9.2
+
+ require-in-the-middle@7.5.2:
+ dependencies:
+ debug: 4.4.1
+ module-details-from-path: 1.0.4
+ resolve: 1.22.10
+ transitivePeerDependencies:
+ - supports-color
+
+ resolve@1.22.10:
+ dependencies:
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ run-exclusive@2.2.19:
+ dependencies:
+ minimal-polyfills: 2.2.3
+
+ scheduler@0.26.0: {}
+
+ server-only@0.0.1: {}
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ signal-exit@4.1.0: {}
+
+ slug@6.1.0: {}
+
+ socket.io-adapter@2.5.5:
+ dependencies:
+ debug: 4.3.7
+ ws: 8.17.1
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ socket.io-client@4.7.5:
+ dependencies:
+ '@socket.io/component-emitter': 3.1.2
+ debug: 4.3.7
+ engine.io-client: 6.5.4
+ socket.io-parser: 4.2.4
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ socket.io-parser@4.2.4:
+ dependencies:
+ '@socket.io/component-emitter': 3.1.2
+ debug: 4.3.7
+ transitivePeerDependencies:
+ - supports-color
+
+ socket.io@4.7.4:
+ dependencies:
+ accepts: 1.3.8
+ base64id: 2.0.0
+ cors: 2.8.5
+ debug: 4.3.7
+ engine.io: 6.5.5
+ socket.io-adapter: 2.5.5
+ socket.io-parser: 4.2.4
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ sonner@1.7.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
+ source-map-js@1.2.1: {}
+
+ std-env@3.9.0: {}
+
+ streamsearch@1.1.0: {}
+
+ strip-final-newline@3.0.0: {}
+
+ styled-jsx@5.1.1(react@19.1.0):
+ dependencies:
+ client-only: 0.0.1
+ react: 19.1.0
+
+ superjson@2.2.2:
+ dependencies:
+ copy-anything: 3.0.5
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ swr@2.3.6(react@19.1.0):
+ dependencies:
+ dequal: 2.0.3
+ react: 19.1.0
+ use-sync-external-store: 1.5.0(react@19.1.0)
+
+ tailwind-merge@3.3.1: {}
+
+ tailwindcss-animate@1.0.7(tailwindcss@4.1.12):
+ dependencies:
+ tailwindcss: 4.1.12
+
+ tailwindcss@4.1.12: {}
+
+ tapable@2.2.3: {}
+
+ tar@7.4.3:
+ dependencies:
+ '@isaacs/fs-minipass': 4.0.1
+ chownr: 3.0.0
+ minipass: 7.1.2
+ minizlib: 3.0.2
+ mkdirp: 3.0.1
+ yallist: 5.0.0
+
+ tdigest@0.1.2:
+ dependencies:
+ bintrees: 1.0.2
+
+ tiny-invariant@1.3.3: {}
+
+ tinyexec@0.3.2: {}
+
+ tsafe@1.8.5: {}
+
+ tslib@2.8.1: {}
+
+ tw-animate-css@1.3.3: {}
+
+ typescript@5.9.2: {}
+
+ ulid@2.4.0: {}
+
+ uncrypto@0.1.3: {}
+
+ undici-types@6.21.0: {}
+
+ update-browserslist-db@1.1.3(browserslist@4.25.4):
+ dependencies:
+ browserslist: 4.25.4
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ use-callback-ref@1.3.3(@types/react@18.3.24)(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ use-sidecar@1.1.3(@types/react@18.3.24)(react@19.1.0):
+ dependencies:
+ detect-node-es: 1.1.0
+ react: 19.1.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.24
+
+ use-sync-external-store@1.5.0(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+
+ uuid@9.0.1: {}
+
+ vary@1.1.2: {}
+
+ vaul@0.9.9(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ '@radix-ui/react-dialog': 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ transitivePeerDependencies:
+ - '@types/react'
+ - '@types/react-dom'
+
+ victory-vendor@36.9.2:
+ dependencies:
+ '@types/d3-array': 3.2.1
+ '@types/d3-ease': 3.0.2
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-scale': 4.0.9
+ '@types/d3-shape': 3.1.7
+ '@types/d3-time': 3.0.4
+ '@types/d3-timer': 3.0.2
+ d3-array: 3.2.4
+ d3-ease: 3.0.1
+ d3-interpolate: 3.0.1
+ d3-scale: 4.0.2
+ d3-shape: 3.2.0
+ d3-time: 3.1.0
+ d3-timer: 3.0.1
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ ws@8.17.1: {}
+
+ ws@8.18.3: {}
+
+ xmlhttprequest-ssl@2.0.0: {}
+
+ yallist@5.0.0: {}
+
+ zod-error@1.5.0:
+ dependencies:
+ zod: 3.25.67
+
+ zod-validation-error@1.5.0(zod@3.25.76):
+ dependencies:
+ zod: 3.25.76
+
+ zod@3.25.67: {}
+
+ zod@3.25.76: {}
diff --git a/product-image-generator/postcss.config.mjs b/product-image-generator/postcss.config.mjs
new file mode 100644
index 0000000..c7bcb4b
--- /dev/null
+++ b/product-image-generator/postcss.config.mjs
@@ -0,0 +1,5 @@
+const config = {
+ plugins: ["@tailwindcss/postcss"],
+};
+
+export default config;
diff --git a/product-image-generator/public/file.svg b/product-image-generator/public/file.svg
new file mode 100644
index 0000000..004145c
--- /dev/null
+++ b/product-image-generator/public/file.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/product-image-generator/public/globe.svg b/product-image-generator/public/globe.svg
new file mode 100644
index 0000000..567f17b
--- /dev/null
+++ b/product-image-generator/public/globe.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/product-image-generator/public/next.svg b/product-image-generator/public/next.svg
new file mode 100644
index 0000000..5174b28
--- /dev/null
+++ b/product-image-generator/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/product-image-generator/public/vercel.svg b/product-image-generator/public/vercel.svg
new file mode 100644
index 0000000..7705396
--- /dev/null
+++ b/product-image-generator/public/vercel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/product-image-generator/public/window.svg b/product-image-generator/public/window.svg
new file mode 100644
index 0000000..b2b2a44
--- /dev/null
+++ b/product-image-generator/public/window.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/product-image-generator/src/trigger/example.ts b/product-image-generator/src/trigger/example.ts
new file mode 100644
index 0000000..871d8c0
--- /dev/null
+++ b/product-image-generator/src/trigger/example.ts
@@ -0,0 +1,16 @@
+import { logger, task, wait } from "@trigger.dev/sdk/v3";
+
+export const helloWorldTask = task({
+ id: "hello-world",
+ // Set an optional maxDuration to prevent tasks from running indefinitely
+ maxDuration: 300, // Stop executing after 300 secs (5 mins) of compute
+ run: async (payload: any, { ctx }) => {
+ logger.log("Hello, world!", { payload, ctx });
+
+ await wait.for({ seconds: 5 });
+
+ return {
+ message: "Hello, world!",
+ };
+ },
+});
diff --git a/product-image-generator/src/trigger/index.ts b/product-image-generator/src/trigger/index.ts
new file mode 100644
index 0000000..a11d031
--- /dev/null
+++ b/product-image-generator/src/trigger/index.ts
@@ -0,0 +1 @@
+export { helloWorldTask } from "./example";
diff --git a/product-image-generator/trigger.config.ts b/product-image-generator/trigger.config.ts
new file mode 100644
index 0000000..727e7e5
--- /dev/null
+++ b/product-image-generator/trigger.config.ts
@@ -0,0 +1,22 @@
+import { defineConfig } from "@trigger.dev/sdk/v3";
+
+export default defineConfig({
+ project: "proj_xjminyoyogxbrfipkrkt",
+ runtime: "node",
+ logLevel: "log",
+ // The max compute seconds a task is allowed to run. If the task run exceeds this duration, it will be stopped.
+ // You can override this on an individual task.
+ // See https://trigger.dev/docs/runs/max-duration
+ maxDuration: 3600,
+ retries: {
+ enabledInDev: true,
+ default: {
+ maxAttempts: 3,
+ minTimeoutInMs: 1000,
+ maxTimeoutInMs: 10000,
+ factor: 2,
+ randomize: true,
+ },
+ },
+ dirs: ["./src/trigger"],
+});
diff --git a/product-image-generator/tsconfig.json b/product-image-generator/tsconfig.json
new file mode 100644
index 0000000..c133409
--- /dev/null
+++ b/product-image-generator/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
From 81a084a59b6634dd10eeb6b6145109bcdb3a83b1 Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Fri, 5 Sep 2025 09:47:37 +0100
Subject: [PATCH 033/232] Implemented drag and drop, deleted hello world and
other changes
---
product-image-generator/app/actions.ts | 67 +-
.../app/components/UploadCard.tsx | 243 ++++
.../{ => app}/components/ui/button.tsx | 0
.../{ => app}/components/ui/card.tsx | 0
.../{ => app}/lib/utils.ts | 0
product-image-generator/app/page.tsx | 118 +-
product-image-generator/package.json | 1 +
product-image-generator/pnpm-lock.yaml | 1191 +++++++++++++++++
.../src/trigger/example.ts | 16 -
.../src/trigger/image-upload.ts | 122 ++
product-image-generator/src/trigger/index.ts | 1 -
product-image-generator/trigger.config.ts | 2 +-
product-image-generator/tsconfig.json | 11 +-
13 files changed, 1631 insertions(+), 141 deletions(-)
create mode 100644 product-image-generator/app/components/UploadCard.tsx
rename product-image-generator/{ => app}/components/ui/button.tsx (100%)
rename product-image-generator/{ => app}/components/ui/card.tsx (100%)
rename product-image-generator/{ => app}/lib/utils.ts (100%)
delete mode 100644 product-image-generator/src/trigger/example.ts
create mode 100644 product-image-generator/src/trigger/image-upload.ts
delete mode 100644 product-image-generator/src/trigger/index.ts
diff --git a/product-image-generator/app/actions.ts b/product-image-generator/app/actions.ts
index b566236..98611d5 100644
--- a/product-image-generator/app/actions.ts
+++ b/product-image-generator/app/actions.ts
@@ -1,24 +1,75 @@
"use server";
-import { tasks } from "@trigger.dev/sdk";
-import type { helloWorldTask } from "@/src/trigger/example";
+import { auth, tasks } from "@trigger.dev/sdk/v3";
+import type { uploadImageToR2 } from "../src/trigger/image-upload";
-export async function triggerHelloWorld(message: string) {
+export async function createPublicAccessToken(runId: string) {
try {
- const handle = await tasks.trigger(
- "hello-world",
- message,
+ const publicAccessToken = await auth.createPublicToken({
+ scopes: {
+ read: {
+ runs: [runId],
+ },
+ },
+ });
+
+ return {
+ success: true as const,
+ token: publicAccessToken,
+ };
+ } catch (error) {
+ console.error("Error creating public access token:", error);
+ return {
+ success: false as const,
+ error: "Failed to create access token",
+ };
+ }
+}
+
+export async function uploadImageToR2Action(formData: FormData) {
+ try {
+ const file = formData.get("image") as File;
+ if (!file) {
+ return {
+ success: false as const,
+ error: "No file provided",
+ };
+ }
+
+ // Convert file to base64
+ const bytes = await file.arrayBuffer();
+ const buffer = Buffer.from(bytes);
+ const base64 = buffer.toString("base64");
+
+ const handle = await tasks.trigger(
+ "upload-image-to-r2",
+ {
+ imageBuffer: base64,
+ fileName: file.name,
+ contentType: file.type,
+ },
);
+ // Create a public access token for this specific run
+ const tokenResult = await createPublicAccessToken(handle.id);
+
+ if (!tokenResult.success) {
+ return {
+ success: false as const,
+ error: tokenResult.error,
+ };
+ }
+
return {
success: true as const,
runId: handle.id,
+ accessToken: tokenResult.token,
};
} catch (error) {
- console.error("Error triggering hello-world task:", error);
+ console.error("Error triggering image upload task:", error);
return {
success: false as const,
- error: "Failed to trigger task",
+ error: "Failed to upload image",
};
}
}
diff --git a/product-image-generator/app/components/UploadCard.tsx b/product-image-generator/app/components/UploadCard.tsx
new file mode 100644
index 0000000..1c4857e
--- /dev/null
+++ b/product-image-generator/app/components/UploadCard.tsx
@@ -0,0 +1,243 @@
+"use client";
+
+import { Button } from "./ui/button";
+import { Card } from "./ui/card";
+import { Upload } from "lucide-react";
+import { useRef, useState, useEffect } from "react";
+import { uploadImageToR2Action } from "../actions";
+import { runs, configure } from "@trigger.dev/sdk/v3";
+
+export default function UploadCard() {
+ const [isDragOver, setIsDragOver] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+ const [runId, setRunId] = useState(null);
+ const [accessToken, setAccessToken] = useState(null);
+ const [error, setError] = useState(null);
+ const [uploadedImageUrl, setUploadedImageUrl] = useState(null);
+ const [uploadProgress, setUploadProgress] = useState("idle");
+ const [progressMessage, setProgressMessage] = useState("");
+ const [progressStep, setProgressStep] = useState<{
+ step: number;
+ total: number;
+ } | null>(null);
+ const fileInputRef = useRef(null);
+
+ // Subscribe to run updates when runId and accessToken are available
+ useEffect(() => {
+ if (!runId || !accessToken) return;
+
+ const subscribeToRun = async () => {
+ try {
+ // Configure the SDK with the access token for client-side authentication
+ configure({
+ secretKey: accessToken,
+ });
+
+ for await (const run of runs.subscribeToRun(runId)) {
+ // Update progress from metadata
+ if (run.metadata?.progress) {
+ const progress = run.metadata.progress as {
+ step: number;
+ total: number;
+ message: string;
+ };
+ setProgressMessage(progress.message);
+ setProgressStep({ step: progress.step, total: progress.total });
+ }
+
+ // Handle completion
+ if (run.status === "COMPLETED" && run.output) {
+ setUploadedImageUrl(run.output.publicUrl);
+ setUploadProgress("completed");
+ setProgressMessage("Upload completed!");
+ setIsLoading(false);
+ break;
+ } else if (run.status === "FAILED") {
+ const errorMsg = run.metadata?.error || "Upload failed";
+ setError(typeof errorMsg === "string" ? errorMsg : "Upload failed");
+ setUploadProgress("idle");
+ setProgressMessage("");
+ setIsLoading(false);
+ break;
+ }
+ }
+ } catch (err) {
+ setError("Failed to get task updates");
+ setUploadProgress("idle");
+ setIsLoading(false);
+ }
+ };
+
+ subscribeToRun();
+ }, [runId, accessToken]);
+
+ // Upload image with realtime subscription
+ const uploadImage = async (file: File) => {
+ setIsLoading(true);
+ setError(null);
+ setUploadProgress("uploading");
+ setUploadedImageUrl(null);
+ setProgressMessage("");
+ setProgressStep(null);
+
+ try {
+ // Create FormData and upload
+ const formData = new FormData();
+ formData.append("image", file);
+
+ const result = await uploadImageToR2Action(formData);
+
+ if (result.success && result.runId && result.accessToken) {
+ setRunId(result.runId);
+ setAccessToken(result.accessToken);
+ setUploadProgress("processing");
+ // The useEffect will handle the subscription
+ } else {
+ setError(result.error || "Failed to upload image");
+ setUploadProgress("idle");
+ setIsLoading(false);
+ }
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Failed to upload image");
+ setUploadProgress("idle");
+ setIsLoading(false);
+ }
+ };
+
+ const handleDragOver = (e: React.DragEvent) => {
+ e.preventDefault();
+ setIsDragOver(true);
+ };
+
+ const handleDragLeave = (e: React.DragEvent) => {
+ e.preventDefault();
+ setIsDragOver(false);
+ };
+
+ const handleDrop = async (e: React.DragEvent) => {
+ e.preventDefault();
+ setIsDragOver(false);
+
+ const files = Array.from(e.dataTransfer.files);
+ const imageFile = files.find((file) => file.type.startsWith("image/"));
+
+ if (imageFile) {
+ await uploadImage(imageFile);
+ }
+ };
+
+ const handleFileSelect = async (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (file && file.type.startsWith("image/")) {
+ await uploadImage(file);
+ }
+ };
+
+ const handleReset = () => {
+ setUploadedImageUrl(null);
+ setUploadProgress("idle");
+ setRunId(null);
+ setAccessToken(null);
+ setError(null);
+ setProgressMessage("");
+ setProgressStep(null);
+ };
+
+ return (
+ !uploadedImageUrl && fileInputRef.current?.click()}
+ >
+ {uploadedImageUrl ? (
+ // Show uploaded image
+
+
+ {uploadProgress === "processing" && (
+
+ )}
+
+ {
+ e.stopPropagation();
+ handleReset();
+ }}
+ >
+ Replace
+
+
+
+ ) : (
+ // Show upload area
+
+
+ {isLoading ? (
+
+ ) : (
+
+ )}
+
+
+ {uploadProgress === "uploading"
+ ? "Uploading..."
+ : uploadProgress === "processing"
+ ? progressMessage || "Processing..."
+ : "Drag and drop an image here"}
+
+
+ {isLoading
+ ? progressStep
+ ? `Step ${progressStep.step} of ${progressStep.total}`
+ : "Please wait"
+ : "or click to browse"}
+
+ {progressStep && uploadProgress === "processing" && (
+
+ )}
+ {runId && uploadProgress !== "idle" && (
+
Run ID: {runId}
+ )}
+ {error &&
Error: {error}
}
+
+ )}
+
+
+ );
+}
diff --git a/product-image-generator/components/ui/button.tsx b/product-image-generator/app/components/ui/button.tsx
similarity index 100%
rename from product-image-generator/components/ui/button.tsx
rename to product-image-generator/app/components/ui/button.tsx
diff --git a/product-image-generator/components/ui/card.tsx b/product-image-generator/app/components/ui/card.tsx
similarity index 100%
rename from product-image-generator/components/ui/card.tsx
rename to product-image-generator/app/components/ui/card.tsx
diff --git a/product-image-generator/lib/utils.ts b/product-image-generator/app/lib/utils.ts
similarity index 100%
rename from product-image-generator/lib/utils.ts
rename to product-image-generator/app/lib/utils.ts
diff --git a/product-image-generator/app/page.tsx b/product-image-generator/app/page.tsx
index c234fb2..612735d 100644
--- a/product-image-generator/app/page.tsx
+++ b/product-image-generator/app/page.tsx
@@ -1,68 +1,9 @@
-"use client";
-
-import { Button } from "@/components/ui/button";
-import { Card } from "@/components/ui/card";
-import { Upload, User, Settings, Home, ImageIcon } from "lucide-react";
-import { useState, useRef } from "react";
-import { triggerHelloWorld } from "./actions";
-import type { helloWorldTask } from "@/src/trigger/example";
+import { Home, ImageIcon, Settings, Upload, User } from "lucide-react";
+import { Button } from "./components/ui/button";
+import { Card } from "./components/ui/card";
+import UploadCard from "./components/UploadCard";
export default function ImageManagementApp() {
- const [isDragOver, setIsDragOver] = useState(false);
- const [isLoading, setIsLoading] = useState(false);
- const [runId, setRunId] = useState(null);
- const [error, setError] = useState(null);
- const fileInputRef = useRef(null);
-
- // Trigger task via Server Action
- const triggerTask = async (payload: string) => {
- setIsLoading(true);
- setError(null);
-
- try {
- const result = await triggerHelloWorld(payload);
-
- if (result.success) {
- setRunId(result.runId || null);
- } else {
- setError(result.error || "Failed to trigger task");
- }
- } catch (err) {
- setError(err instanceof Error ? err.message : "Failed to trigger task");
- } finally {
- setIsLoading(false);
- }
- };
-
- const handleDragOver = (e: React.DragEvent) => {
- e.preventDefault();
- setIsDragOver(true);
- };
-
- const handleDragLeave = (e: React.DragEvent) => {
- e.preventDefault();
- setIsDragOver(false);
- };
-
- const handleDrop = async (e: React.DragEvent) => {
- e.preventDefault();
- setIsDragOver(false);
-
- const files = Array.from(e.dataTransfer.files);
- const imageFile = files.find((file) => file.type.startsWith("image/"));
-
- if (imageFile) {
- await triggerTask(`Hello from ${imageFile.name}!`);
- }
- };
-
- const handleFileSelect = async (e: React.ChangeEvent) => {
- const file = e.target.files?.[0];
- if (file && file.type.startsWith("image/")) {
- await triggerTask(`Hello from ${file.name}!`);
- }
- };
-
return (
{/* Navigation Header */}
@@ -108,55 +49,8 @@ export default function ImageManagementApp() {
{/* Image Grid */}
- {/* First Slot - Drag and Drop Area */}
-
fileInputRef.current?.click()}
- >
-
-
- {isLoading ? (
-
- ) : (
-
- )}
-
-
- {isLoading ? "Processing..." : "Drag and drop an image here"}
-
-
- {isLoading ? "Please wait" : "or click to browse"}
-
- {runId && (
-
- Task triggered! Run ID: {runId}
-
- )}
- {error && (
-
Error: {error}
- )}
-
-
-
+ {/* First Slot - Upload Card */}
+
{/* Remaining 7 Slots - Blank States */}
{Array.from({ length: 7 }).map((_, index) => (
diff --git a/product-image-generator/package.json b/product-image-generator/package.json
index 035608f..c0fb4f3 100644
--- a/product-image-generator/package.json
+++ b/product-image-generator/package.json
@@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
+ "@aws-sdk/client-s3": "^3.882.0",
"@hookform/resolvers": "^3.10.0",
"@radix-ui/react-accordion": "1.2.2",
"@radix-ui/react-alert-dialog": "1.1.4",
diff --git a/product-image-generator/pnpm-lock.yaml b/product-image-generator/pnpm-lock.yaml
index 463f900..90e65b5 100644
--- a/product-image-generator/pnpm-lock.yaml
+++ b/product-image-generator/pnpm-lock.yaml
@@ -8,6 +8,9 @@ importers:
.:
dependencies:
+ '@aws-sdk/client-s3':
+ specifier: ^3.882.0
+ version: 3.882.0
'@hookform/resolvers':
specifier: ^3.10.0
version: 3.10.0(react-hook-form@7.62.0(react@19.1.0))
@@ -199,6 +202,157 @@ packages:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
+ '@aws-crypto/crc32@5.2.0':
+ resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
+ engines: {node: '>=16.0.0'}
+
+ '@aws-crypto/crc32c@5.2.0':
+ resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==}
+
+ '@aws-crypto/sha1-browser@5.2.0':
+ resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==}
+
+ '@aws-crypto/sha256-browser@5.2.0':
+ resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==}
+
+ '@aws-crypto/sha256-js@5.2.0':
+ resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==}
+ engines: {node: '>=16.0.0'}
+
+ '@aws-crypto/supports-web-crypto@5.2.0':
+ resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==}
+
+ '@aws-crypto/util@5.2.0':
+ resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==}
+
+ '@aws-sdk/client-s3@3.882.0':
+ resolution: {integrity: sha512-0IrBUOrBepQeuH025t+b4KqgBRQT+B//JlTU3+629WUGWwsWVfFkCTkn4xK/oQP9/K6npZtfDTuO6XfXSLimmg==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/client-sso@3.882.0':
+ resolution: {integrity: sha512-JFWJB+2PZvygDuqb4iWKCro1Tl5L4tGBXMHe94jYMYnfajYGm58bW3RsPj3cKD2+TvIMUSXmNriNv+LbDKZmNw==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/core@3.882.0':
+ resolution: {integrity: sha512-m43/gEDbxqxLT/Mbn/OA21TuFpyocOUzjiSA2HBnLQ3KivA4ez0nsW91vh0Sp3TOfLgiZbRbVhmI6XfsFinwBg==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/credential-provider-env@3.882.0':
+ resolution: {integrity: sha512-khhE1k+4XvGm8Mk6vVUbrVvEnx3r8E6dymSKSiAKf0lwsnKWAWd1RLGwLusqVgtGR4Jfsrbg7ox9MczIjgCiTg==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/credential-provider-http@3.882.0':
+ resolution: {integrity: sha512-j3mBF+Q6RU3u8t5O1KOWbQQCi0WNSl47sNIa1RvyN6qK1WIA8BxM1hB25mI9TMPrNZMFthljVec+JcNjRNG34A==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/credential-provider-ini@3.882.0':
+ resolution: {integrity: sha512-nUacsSYKyTUmv/Fqe0efihCRCabea5MZtGSZF0l2V8QBo39yJjw0wVmRK6G4bfm5lY7v2EVVIUCpiTvxRRUbHg==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/credential-provider-node@3.882.0':
+ resolution: {integrity: sha512-sELdV+leCfY+Bw8NQo3H65oIT+9thqZU0RWyv85EfZVvKEwWDt4McA7+Co1VkH+nCY21s5jz4SOqIrYuT0cSQg==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/credential-provider-process@3.882.0':
+ resolution: {integrity: sha512-S3BgGcaR+L7CQAQn3Ysy9KSnck7+hDicAGM/dYvvJ8GwZNIOc0542Y+ntpV1UYa7OuZPWzGy2v2NcJSCbYDXEA==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/credential-provider-sso@3.882.0':
+ resolution: {integrity: sha512-1pZRTKiDl6Oh/jP75lEoSkJrer1YEm8lMconB8dX9bsaWbp9cZeMJMK6pts5VQcveeOLr/8/U9TESboPjHBcyA==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/credential-provider-web-identity@3.882.0':
+ resolution: {integrity: sha512-EvpsD0Vcz5WgXjpC53KAQ2CkeUp0KwwiV6brgQTXl+9yV/M8M0aK5Qk5ep/MPbAn5gtbqXHaCkiExaN4YYOhCg==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/middleware-bucket-endpoint@3.873.0':
+ resolution: {integrity: sha512-b4bvr0QdADeTUs+lPc9Z48kXzbKHXQKgTvxx/jXDgSW9tv4KmYPO1gIj6Z9dcrBkRWQuUtSW3Tu2S5n6pe+zeg==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/middleware-expect-continue@3.873.0':
+ resolution: {integrity: sha512-GIqoc8WgRcf/opBOZXFLmplJQKwOMjiOMmDz9gQkaJ8FiVJoAp8EGVmK2TOWZMQUYsavvHYsHaor5R2xwPoGVg==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/middleware-flexible-checksums@3.882.0':
+ resolution: {integrity: sha512-VZSeGckiRNEUYNYni8JFGB+uFqPq6L+IWPXTOMh6RtpDpamDSqZLgDEfXqopc+Awxpz1sQbdxSHMm2HZlqVW2g==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/middleware-host-header@3.873.0':
+ resolution: {integrity: sha512-KZ/W1uruWtMOs7D5j3KquOxzCnV79KQW9MjJFZM/M0l6KI8J6V3718MXxFHsTjUE4fpdV6SeCNLV1lwGygsjJA==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/middleware-location-constraint@3.873.0':
+ resolution: {integrity: sha512-r+hIaORsW/8rq6wieDordXnA/eAu7xAPLue2InhoEX6ML7irP52BgiibHLpt9R0psiCzIHhju8qqKa4pJOrmiw==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/middleware-logger@3.876.0':
+ resolution: {integrity: sha512-cpWJhOuMSyz9oV25Z/CMHCBTgafDCbv7fHR80nlRrPdPZ8ETNsahwRgltXP1QJJ8r3X/c1kwpOR7tc+RabVzNA==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/middleware-recursion-detection@3.873.0':
+ resolution: {integrity: sha512-OtgY8EXOzRdEWR//WfPkA/fXl0+WwE8hq0y9iw2caNyKPtca85dzrrZWnPqyBK/cpImosrpR1iKMYr41XshsCg==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/middleware-sdk-s3@3.882.0':
+ resolution: {integrity: sha512-j5Ya7RKSQSKkpcLsO+Rh272zKD63JYkLKY/N8m5MVNWQafMdUbkZi7nwwjq7s5t7r3Pmz7a4gLf4n6ZEL5eaow==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/middleware-ssec@3.873.0':
+ resolution: {integrity: sha512-AF55J94BoiuzN7g3hahy0dXTVZahVi8XxRBLgzNp6yQf0KTng+hb/V9UQZVYY1GZaDczvvvnqC54RGe9OZZ9zQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/middleware-user-agent@3.882.0':
+ resolution: {integrity: sha512-IdLVpV2b0qryxFb/gNPwZoayLUdgmb41fWpLiIf99pyNwR7TGs/9Ri2amS3PnaQHuES947xYSYZ9Ej0kBgjHKg==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/nested-clients@3.882.0':
+ resolution: {integrity: sha512-IQkOtl/DhLV5+tJI7ZwjBDJO1lIoYOcmNQzcg8ly9RTdMoTcEtklevxmAwWB4DEFiIctUk2OSjHqhfWjeYredA==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/region-config-resolver@3.873.0':
+ resolution: {integrity: sha512-q9sPoef+BBG6PJnc4x60vK/bfVwvRWsPgcoQyIra057S/QGjq5VkjvNk6H8xedf6vnKlXNBwq9BaANBXnldUJg==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/signature-v4-multi-region@3.882.0':
+ resolution: {integrity: sha512-hAmA9BgL3nIRTGoOGjMXMqVtPhtPFKBFaqhgQkgmkzpbZ6aaGecNIqBfGxi9oezR4dnvI+PvKoRo2F8csF7fMA==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/token-providers@3.882.0':
+ resolution: {integrity: sha512-/Z6F8Cc+QjBMEPh3ZXy7JM1vMZCS41+Nh9VgdUwvvdJTA7LRXSDBRDL3cQPa7bii9unZ8SqsIC+7Nlw1LKwwJA==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/types@3.862.0':
+ resolution: {integrity: sha512-Bei+RL0cDxxV+lW2UezLbCYYNeJm6Nzee0TpW0FfyTRBhH9C1XQh4+x+IClriXvgBnRquTMMYsmJfvx8iyLKrg==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/util-arn-parser@3.873.0':
+ resolution: {integrity: sha512-qag+VTqnJWDn8zTAXX4wiVioa0hZDQMtbZcGRERVnLar4/3/VIKBhxX2XibNQXFu1ufgcRn4YntT/XEPecFWcg==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/util-endpoints@3.879.0':
+ resolution: {integrity: sha512-aVAJwGecYoEmbEFju3127TyJDF9qJsKDUUTRMDuS8tGn+QiWQFnfInmbt+el9GU1gEJupNTXV+E3e74y51fb7A==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/util-locate-window@3.873.0':
+ resolution: {integrity: sha512-xcVhZF6svjM5Rj89T1WzkjQmrTF6dpR2UvIHPMTnSZoNe6CixejPZ6f0JJ2kAhO8H+dUHwNBlsUgOTIKiK/Syg==}
+ engines: {node: '>=18.0.0'}
+
+ '@aws-sdk/util-user-agent-browser@3.873.0':
+ resolution: {integrity: sha512-AcRdbK6o19yehEcywI43blIBhOCSo6UgyWcuOJX5CFF8k39xm1ILCjQlRRjchLAxWrm0lU0Q7XV90RiMMFMZtA==}
+
+ '@aws-sdk/util-user-agent-node@3.882.0':
+ resolution: {integrity: sha512-7zPtGXeAs6UzKjrrSbMNiFMSLZ/2DWvJ26KBOasS3zQbL534yoNos4HUA3OOXSpKFBAIEcYWu6rzR4ptlvx50w==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ aws-crt: '>=1.0.0'
+ peerDependenciesMeta:
+ aws-crt:
+ optional: true
+
+ '@aws-sdk/xml-builder@3.873.0':
+ resolution: {integrity: sha512-kLO7k7cGJ6KaHiExSJWojZurF7SnGMDHXRuQunFnEoD0n1yB6Lqy/S/zHiQ7oJnBhPr9q0TW9qFkrsZb1Uc54w==}
+ engines: {node: '>=18.0.0'}
+
'@babel/runtime@7.28.3':
resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==}
engines: {node: '>=6.9.0'}
@@ -1091,6 +1245,218 @@ packages:
cpu: [arm64]
os: [darwin]
+ '@smithy/abort-controller@4.1.0':
+ resolution: {integrity: sha512-wEhSYznxOmx7EdwK1tYEWJF5+/wmSFsff9BfTOn8oO/+KPl3gsmThrb6MJlWbOC391+Ya31s5JuHiC2RlT80Zg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/chunked-blob-reader-native@4.1.0':
+ resolution: {integrity: sha512-Bnv0B3nSlfB2mPO0WgM49I/prl7+kamF042rrf3ezJ3Z4C7csPYvyYgZfXTGXwXfj1mAwDWjE/ybIf49PzFzvA==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/chunked-blob-reader@5.1.0':
+ resolution: {integrity: sha512-a36AtR7Q7XOhRPt6F/7HENmTWcB8kN7mDJcOFM/+FuKO6x88w8MQJfYCufMWh4fGyVkPjUh3Rrz/dnqFQdo6OQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/config-resolver@4.2.0':
+ resolution: {integrity: sha512-FA10YhPFLy23uxeWu7pOM2ctlw+gzbPMTZQwrZ8FRIfyJ/p8YIVz7AVTB5jjLD+QIerydyKcVMZur8qzzDILAQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/core@3.10.0':
+ resolution: {integrity: sha512-bXyD3Ij6b1qDymEYlEcF+QIjwb9gObwZNaRjETJsUEvSIzxFdynSQ3E4ysY7lUFSBzeWBNaFvX+5A0smbC2q6A==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/credential-provider-imds@4.1.0':
+ resolution: {integrity: sha512-iVwNhxTsCQTPdp++4C/d9xvaDmuEWhXi55qJobMp9QMaEHRGH3kErU4F8gohtdsawRqnUy/ANylCjKuhcR2mPw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/eventstream-codec@4.1.0':
+ resolution: {integrity: sha512-MSOb6pwG3Tss1UwlZMHC+rYergWCo4fwep3Y1fJxwdLLxReSaKFfXxPQhEHi/8LSNQFEcBYBxybgjXjw4jJWqQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/eventstream-serde-browser@4.1.0':
+ resolution: {integrity: sha512-VvHXoBoLos2OCdMtUvKWK7ckcvun6ZP4KBYhf38+kszk6BEuK9k8c3xbIMIpC6K4vTK72qHlHAdBoR9qU+F7xw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/eventstream-serde-config-resolver@4.2.0':
+ resolution: {integrity: sha512-T7YlcU0cP2bjAC4eXo9E6puqrrmqv5VHBL8bPMOMgEE1p4m+bwkDWRQpeiXqn/idoKM1qwXq8PvRLYmpbYB6uw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/eventstream-serde-node@4.1.0':
+ resolution: {integrity: sha512-WlIKVRkcPjwuN3x+e8+5KOI9nL6s93bxgWH+39VwwQMl+4FagKPtTM3VCumSoZJ9qn/CNl4W5mVdFFRkDF84lQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/eventstream-serde-universal@4.1.0':
+ resolution: {integrity: sha512-GjMezHHd0xrjJcWLAcnXlVePe7PY8KsdxzKeXcMn7V3vfIScGUpKQJrlSmEXwzFH9Mjl0G0EdOS5GzewZEwtxg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/fetch-http-handler@5.2.0':
+ resolution: {integrity: sha512-VZenjDdVaUGiy3hwQtxm75nhXZrhFG+3xyL93qCQAlYDyhT/jeDWM8/3r5uCFMlTmmyrIjiDyiOynVFchb0BSg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/hash-blob-browser@4.1.0':
+ resolution: {integrity: sha512-brRgh2qEYPHYImfqoQB/xfcT/CjSz9Z/dH2vURSS0lIw3bImFK5t15l4iypwRw4GtZlZTK/VsLqsR54OJWRerg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/hash-node@4.1.0':
+ resolution: {integrity: sha512-mXkJQ/6lAXTuoSsEH+d/fHa4ms4qV5LqYoPLYhmhCRTNcMMdg+4Ya8cMgU1W8+OR40eX0kzsExT7fAILqtTl2w==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/hash-stream-node@4.1.0':
+ resolution: {integrity: sha512-9TToqq62msanK/L6pV1ZAOm2+1VgCz9gE6/TVJhZXV352DnAItaO9jx6FFGujUDXrRJV0lpwe4c0vymz/vXMUQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/invalid-dependency@4.1.0':
+ resolution: {integrity: sha512-4/FcV6aCMzgpM4YyA/GRzTtG28G0RQJcWK722MmpIgzOyfSceWcI9T9c8matpHU9qYYLaWtk8pSGNCLn5kzDRw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/is-array-buffer@2.2.0':
+ resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==}
+ engines: {node: '>=14.0.0'}
+
+ '@smithy/is-array-buffer@4.1.0':
+ resolution: {integrity: sha512-ePTYUOV54wMogio+he4pBybe8fwg4sDvEVDBU8ZlHOZXbXK3/C0XfJgUCu6qAZcawv05ZhZzODGUerFBPsPUDQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/md5-js@4.1.0':
+ resolution: {integrity: sha512-RW1+/E3rv80254ekFqiUTM8ExtN0dG9dkUwU2x17rxS4Mn2ib3SrTCdayCiNbfj6xWHupzgOJB6iNoXiOzNe6g==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/middleware-content-length@4.1.0':
+ resolution: {integrity: sha512-x3dgLFubk/ClKVniJu+ELeZGk4mq7Iv0HgCRUlxNUIcerHTLVmq7Q5eGJL0tOnUltY6KFw5YOKaYxwdcMwox/w==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/middleware-endpoint@4.2.0':
+ resolution: {integrity: sha512-J1eCF7pPDwgv7fGwRd2+Y+H9hlIolF3OZ2PjptonzzyOXXGh/1KGJAHpEcY1EX+WLlclKu2yC5k+9jWXdUG4YQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/middleware-retry@4.2.0':
+ resolution: {integrity: sha512-raL5oWYf5ALl3jCJrajE8enKJEnV/2wZkKS6mb3ZRY2tg3nj66ssdWy5Ps8E6Yu8Wqh3Tt+Sb9LozjvwZupq+A==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/middleware-serde@4.1.0':
+ resolution: {integrity: sha512-CtLFYlHt7c2VcztyVRc+25JLV4aGpmaSv9F1sPB0AGFL6S+RPythkqpGDa2XBQLJQooKkjLA1g7Xe4450knShg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/middleware-stack@4.1.0':
+ resolution: {integrity: sha512-91Fuw4IKp0eK8PNhMXrHRcYA1jvbZ9BJGT91wwPy3bTQT8mHTcQNius/EhSQTlT9QUI3Ki1wjHeNXbWK0tO8YQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/node-config-provider@4.2.0':
+ resolution: {integrity: sha512-8/fpilqKurQ+f8nFvoFkJ0lrymoMJ+5/CQV5IcTv/MyKhk2Q/EFYCAgTSWHD4nMi9ux9NyBBynkyE9SLg2uSLA==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/node-http-handler@4.2.0':
+ resolution: {integrity: sha512-G4NV70B4hF9vBrUkkvNfWO6+QR4jYjeO4tc+4XrKCb4nPYj49V9Hu8Ftio7Mb0/0IlFyEOORudHrm+isY29nCA==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/property-provider@4.1.0':
+ resolution: {integrity: sha512-eksMjMHUlG5PwOUWO3k+rfLNOPVPJ70mUzyYNKb5lvyIuAwS4zpWGsxGiuT74DFWonW0xRNy+jgzGauUzX7SyA==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/protocol-http@5.2.0':
+ resolution: {integrity: sha512-bwjlh5JwdOQnA01be+5UvHK4HQz4iaRKlVG46hHSJuqi0Ribt3K06Z1oQ29i35Np4G9MCDgkOGcHVyLMreMcbg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/querystring-builder@4.1.0':
+ resolution: {integrity: sha512-JqTWmVIq4AF8R8OK/2cCCiQo5ZJ0SRPsDkDgLO5/3z8xxuUp1oMIBBjfuueEe+11hGTZ6rRebzYikpKc6yQV9Q==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/querystring-parser@4.1.0':
+ resolution: {integrity: sha512-VgdHhr8YTRsjOl4hnKFm7xEMOCRTnKw3FJ1nU+dlWNhdt/7eEtxtkdrJdx7PlRTabdANTmvyjE4umUl9cK4awg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/service-error-classification@4.1.0':
+ resolution: {integrity: sha512-UBpNFzBNmS20jJomuYn++Y+soF8rOK9AvIGjS9yGP6uRXF5rP18h4FDUsoNpWTlSsmiJ87e2DpZo9ywzSMH7PQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/shared-ini-file-loader@4.1.0':
+ resolution: {integrity: sha512-W0VMlz9yGdQ/0ZAgWICFjFHTVU0YSfGoCVpKaExRM/FDkTeP/yz8OKvjtGjs6oFokCRm0srgj/g4Cg0xuHu8Rw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/signature-v4@5.2.0':
+ resolution: {integrity: sha512-ObX1ZqG2DdZQlXx9mLD7yAR8AGb7yXurGm+iWx9x4l1fBZ8CZN2BRT09aSbcXVPZXWGdn5VtMuupjxhOTI2EjA==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/smithy-client@4.6.0':
+ resolution: {integrity: sha512-TvlIshqx5PIi0I0AiR+PluCpJ8olVG++xbYkAIGCUkByaMUlfOXLgjQTmYbr46k4wuDe8eHiTIlUflnjK2drPQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/types@4.4.0':
+ resolution: {integrity: sha512-4jY91NgZz+ZnSFcVzWwngOW6VuK3gR/ihTwSU1R/0NENe9Jd8SfWgbhDCAGUWL3bI7DiDSW7XF6Ui6bBBjrqXw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/url-parser@4.1.0':
+ resolution: {integrity: sha512-/LYEIOuO5B2u++tKr1NxNxhZTrr3A63jW8N73YTwVeUyAlbB/YM+hkftsvtKAcMt3ADYo0FsF1GY3anehffSVQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-base64@4.1.0':
+ resolution: {integrity: sha512-RUGd4wNb8GeW7xk+AY5ghGnIwM96V0l2uzvs/uVHf+tIuVX2WSvynk5CxNoBCsM2rQRSZElAo9rt3G5mJ/gktQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-body-length-browser@4.1.0':
+ resolution: {integrity: sha512-V2E2Iez+bo6bUMOTENPr6eEmepdY8Hbs+Uc1vkDKgKNA/brTJqOW/ai3JO1BGj9GbCeLqw90pbbH7HFQyFotGQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-body-length-node@4.1.0':
+ resolution: {integrity: sha512-BOI5dYjheZdgR9XiEM3HJcEMCXSoqbzu7CzIgYrx0UtmvtC3tC2iDGpJLsSRFffUpy8ymsg2ARMP5fR8mtuUQQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-buffer-from@2.2.0':
+ resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==}
+ engines: {node: '>=14.0.0'}
+
+ '@smithy/util-buffer-from@4.1.0':
+ resolution: {integrity: sha512-N6yXcjfe/E+xKEccWEKzK6M+crMrlwaCepKja0pNnlSkm6SjAeLKKA++er5Ba0I17gvKfN/ThV+ZOx/CntKTVw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-config-provider@4.1.0':
+ resolution: {integrity: sha512-swXz2vMjrP1ZusZWVTB/ai5gK+J8U0BWvP10v9fpcFvg+Xi/87LHvHfst2IgCs1i0v4qFZfGwCmeD/KNCdJZbQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-defaults-mode-browser@4.1.0':
+ resolution: {integrity: sha512-D27cLtJtC4EEeERJXS+JPoogz2tE5zeE3zhWSSu6ER5/wJ5gihUxIzoarDX6K1U27IFTHit5YfHqU4Y9RSGE0w==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-defaults-mode-node@4.1.0':
+ resolution: {integrity: sha512-gnZo3u5dP1o87plKupg39alsbeIY1oFFnCyV2nI/++pL19vTtBLgOyftLEjPjuXmoKn2B2rskX8b7wtC/+3Okg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-endpoints@3.1.0':
+ resolution: {integrity: sha512-5LFg48KkunBVGrNs3dnQgLlMXJLVo7k9sdZV5su3rjO3c3DmQ2LwUZI0Zr49p89JWK6sB7KmzyI2fVcDsZkwuw==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-hex-encoding@4.1.0':
+ resolution: {integrity: sha512-1LcueNN5GYC4tr8mo14yVYbh/Ur8jHhWOxniZXii+1+ePiIbsLZ5fEI0QQGtbRRP5mOhmooos+rLmVASGGoq5w==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-middleware@4.1.0':
+ resolution: {integrity: sha512-612onNcKyxhP7/YOTKFTb2F6sPYtMRddlT5mZvYf1zduzaGzkYhpYIPxIeeEwBZFjnvEqe53Ijl2cYEfJ9d6/Q==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-retry@4.1.0':
+ resolution: {integrity: sha512-5AGoBHb207xAKSVwaUnaER+L55WFY8o2RhlafELZR3mB0J91fpL+Qn+zgRkPzns3kccGaF2vy0HmNVBMWmN6dA==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-stream@4.3.0':
+ resolution: {integrity: sha512-ZOYS94jksDwvsCJtppHprUhsIscRnCKGr6FXCo3SxgQ31ECbza3wqDBqSy6IsAak+h/oAXb1+UYEBmDdseAjUQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-uri-escape@4.1.0':
+ resolution: {integrity: sha512-b0EFQkq35K5NHUYxU72JuoheM6+pytEVUGlTwiFxWFpmddA+Bpz3LgsPRIpBk8lnPE47yT7AF2Egc3jVnKLuPg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-utf8@2.3.0':
+ resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==}
+ engines: {node: '>=14.0.0'}
+
+ '@smithy/util-utf8@4.1.0':
+ resolution: {integrity: sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-waiter@4.1.0':
+ resolution: {integrity: sha512-IUuj2zpGdeKaY5OdGnU83BUJsv7OA9uw3rNVSOuvzLMXMpBTU+W6V0SsQh6iI32lKUJArlnEU4BIzp83hghR/g==}
+ engines: {node: '>=18.0.0'}
+
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
@@ -1256,6 +1622,9 @@ packages:
'@types/react@18.3.24':
resolution: {integrity: sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==}
+ '@types/uuid@9.0.8':
+ resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==}
+
'@vercel/analytics@1.3.1':
resolution: {integrity: sha512-xhSlYgAuJ6Q4WQGkzYTLmXwhYl39sWjoMA3nHxfkvG+WdBT25c563a7QhwwKivEOZtPJXifYHR1m2ihoisbWyA==}
peerDependencies:
@@ -1299,6 +1668,9 @@ packages:
bintrees@1.0.2:
resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==}
+ bowser@2.12.1:
+ resolution: {integrity: sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==}
+
browserslist@4.25.4:
resolution: {integrity: sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
@@ -1503,6 +1875,10 @@ packages:
resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==}
engines: {node: '>=6.0.0'}
+ fast-xml-parser@5.2.5:
+ resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==}
+ hasBin: true
+
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
@@ -1940,6 +2316,9 @@ packages:
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
engines: {node: '>=12'}
+ strnum@2.1.1:
+ resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==}
+
styled-jsx@5.1.1:
resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==}
engines: {node: '>= 12.0.0'}
@@ -2122,6 +2501,472 @@ snapshots:
'@alloc/quick-lru@5.2.0': {}
+ '@aws-crypto/crc32@5.2.0':
+ dependencies:
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.862.0
+ tslib: 2.8.1
+
+ '@aws-crypto/crc32c@5.2.0':
+ dependencies:
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.862.0
+ tslib: 2.8.1
+
+ '@aws-crypto/sha1-browser@5.2.0':
+ dependencies:
+ '@aws-crypto/supports-web-crypto': 5.2.0
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.862.0
+ '@aws-sdk/util-locate-window': 3.873.0
+ '@smithy/util-utf8': 2.3.0
+ tslib: 2.8.1
+
+ '@aws-crypto/sha256-browser@5.2.0':
+ dependencies:
+ '@aws-crypto/sha256-js': 5.2.0
+ '@aws-crypto/supports-web-crypto': 5.2.0
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.862.0
+ '@aws-sdk/util-locate-window': 3.873.0
+ '@smithy/util-utf8': 2.3.0
+ tslib: 2.8.1
+
+ '@aws-crypto/sha256-js@5.2.0':
+ dependencies:
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.862.0
+ tslib: 2.8.1
+
+ '@aws-crypto/supports-web-crypto@5.2.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@aws-crypto/util@5.2.0':
+ dependencies:
+ '@aws-sdk/types': 3.862.0
+ '@smithy/util-utf8': 2.3.0
+ tslib: 2.8.1
+
+ '@aws-sdk/client-s3@3.882.0':
+ dependencies:
+ '@aws-crypto/sha1-browser': 5.2.0
+ '@aws-crypto/sha256-browser': 5.2.0
+ '@aws-crypto/sha256-js': 5.2.0
+ '@aws-sdk/core': 3.882.0
+ '@aws-sdk/credential-provider-node': 3.882.0
+ '@aws-sdk/middleware-bucket-endpoint': 3.873.0
+ '@aws-sdk/middleware-expect-continue': 3.873.0
+ '@aws-sdk/middleware-flexible-checksums': 3.882.0
+ '@aws-sdk/middleware-host-header': 3.873.0
+ '@aws-sdk/middleware-location-constraint': 3.873.0
+ '@aws-sdk/middleware-logger': 3.876.0
+ '@aws-sdk/middleware-recursion-detection': 3.873.0
+ '@aws-sdk/middleware-sdk-s3': 3.882.0
+ '@aws-sdk/middleware-ssec': 3.873.0
+ '@aws-sdk/middleware-user-agent': 3.882.0
+ '@aws-sdk/region-config-resolver': 3.873.0
+ '@aws-sdk/signature-v4-multi-region': 3.882.0
+ '@aws-sdk/types': 3.862.0
+ '@aws-sdk/util-endpoints': 3.879.0
+ '@aws-sdk/util-user-agent-browser': 3.873.0
+ '@aws-sdk/util-user-agent-node': 3.882.0
+ '@aws-sdk/xml-builder': 3.873.0
+ '@smithy/config-resolver': 4.2.0
+ '@smithy/core': 3.10.0
+ '@smithy/eventstream-serde-browser': 4.1.0
+ '@smithy/eventstream-serde-config-resolver': 4.2.0
+ '@smithy/eventstream-serde-node': 4.1.0
+ '@smithy/fetch-http-handler': 5.2.0
+ '@smithy/hash-blob-browser': 4.1.0
+ '@smithy/hash-node': 4.1.0
+ '@smithy/hash-stream-node': 4.1.0
+ '@smithy/invalid-dependency': 4.1.0
+ '@smithy/md5-js': 4.1.0
+ '@smithy/middleware-content-length': 4.1.0
+ '@smithy/middleware-endpoint': 4.2.0
+ '@smithy/middleware-retry': 4.2.0
+ '@smithy/middleware-serde': 4.1.0
+ '@smithy/middleware-stack': 4.1.0
+ '@smithy/node-config-provider': 4.2.0
+ '@smithy/node-http-handler': 4.2.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/smithy-client': 4.6.0
+ '@smithy/types': 4.4.0
+ '@smithy/url-parser': 4.1.0
+ '@smithy/util-base64': 4.1.0
+ '@smithy/util-body-length-browser': 4.1.0
+ '@smithy/util-body-length-node': 4.1.0
+ '@smithy/util-defaults-mode-browser': 4.1.0
+ '@smithy/util-defaults-mode-node': 4.1.0
+ '@smithy/util-endpoints': 3.1.0
+ '@smithy/util-middleware': 4.1.0
+ '@smithy/util-retry': 4.1.0
+ '@smithy/util-stream': 4.3.0
+ '@smithy/util-utf8': 4.1.0
+ '@smithy/util-waiter': 4.1.0
+ '@types/uuid': 9.0.8
+ tslib: 2.8.1
+ uuid: 9.0.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/client-sso@3.882.0':
+ dependencies:
+ '@aws-crypto/sha256-browser': 5.2.0
+ '@aws-crypto/sha256-js': 5.2.0
+ '@aws-sdk/core': 3.882.0
+ '@aws-sdk/middleware-host-header': 3.873.0
+ '@aws-sdk/middleware-logger': 3.876.0
+ '@aws-sdk/middleware-recursion-detection': 3.873.0
+ '@aws-sdk/middleware-user-agent': 3.882.0
+ '@aws-sdk/region-config-resolver': 3.873.0
+ '@aws-sdk/types': 3.862.0
+ '@aws-sdk/util-endpoints': 3.879.0
+ '@aws-sdk/util-user-agent-browser': 3.873.0
+ '@aws-sdk/util-user-agent-node': 3.882.0
+ '@smithy/config-resolver': 4.2.0
+ '@smithy/core': 3.10.0
+ '@smithy/fetch-http-handler': 5.2.0
+ '@smithy/hash-node': 4.1.0
+ '@smithy/invalid-dependency': 4.1.0
+ '@smithy/middleware-content-length': 4.1.0
+ '@smithy/middleware-endpoint': 4.2.0
+ '@smithy/middleware-retry': 4.2.0
+ '@smithy/middleware-serde': 4.1.0
+ '@smithy/middleware-stack': 4.1.0
+ '@smithy/node-config-provider': 4.2.0
+ '@smithy/node-http-handler': 4.2.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/smithy-client': 4.6.0
+ '@smithy/types': 4.4.0
+ '@smithy/url-parser': 4.1.0
+ '@smithy/util-base64': 4.1.0
+ '@smithy/util-body-length-browser': 4.1.0
+ '@smithy/util-body-length-node': 4.1.0
+ '@smithy/util-defaults-mode-browser': 4.1.0
+ '@smithy/util-defaults-mode-node': 4.1.0
+ '@smithy/util-endpoints': 3.1.0
+ '@smithy/util-middleware': 4.1.0
+ '@smithy/util-retry': 4.1.0
+ '@smithy/util-utf8': 4.1.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/core@3.882.0':
+ dependencies:
+ '@aws-sdk/types': 3.862.0
+ '@aws-sdk/xml-builder': 3.873.0
+ '@smithy/core': 3.10.0
+ '@smithy/node-config-provider': 4.2.0
+ '@smithy/property-provider': 4.1.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/signature-v4': 5.2.0
+ '@smithy/smithy-client': 4.6.0
+ '@smithy/types': 4.4.0
+ '@smithy/util-base64': 4.1.0
+ '@smithy/util-body-length-browser': 4.1.0
+ '@smithy/util-middleware': 4.1.0
+ '@smithy/util-utf8': 4.1.0
+ fast-xml-parser: 5.2.5
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-env@3.882.0':
+ dependencies:
+ '@aws-sdk/core': 3.882.0
+ '@aws-sdk/types': 3.862.0
+ '@smithy/property-provider': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-http@3.882.0':
+ dependencies:
+ '@aws-sdk/core': 3.882.0
+ '@aws-sdk/types': 3.862.0
+ '@smithy/fetch-http-handler': 5.2.0
+ '@smithy/node-http-handler': 4.2.0
+ '@smithy/property-provider': 4.1.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/smithy-client': 4.6.0
+ '@smithy/types': 4.4.0
+ '@smithy/util-stream': 4.3.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-ini@3.882.0':
+ dependencies:
+ '@aws-sdk/core': 3.882.0
+ '@aws-sdk/credential-provider-env': 3.882.0
+ '@aws-sdk/credential-provider-http': 3.882.0
+ '@aws-sdk/credential-provider-process': 3.882.0
+ '@aws-sdk/credential-provider-sso': 3.882.0
+ '@aws-sdk/credential-provider-web-identity': 3.882.0
+ '@aws-sdk/nested-clients': 3.882.0
+ '@aws-sdk/types': 3.862.0
+ '@smithy/credential-provider-imds': 4.1.0
+ '@smithy/property-provider': 4.1.0
+ '@smithy/shared-ini-file-loader': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/credential-provider-node@3.882.0':
+ dependencies:
+ '@aws-sdk/credential-provider-env': 3.882.0
+ '@aws-sdk/credential-provider-http': 3.882.0
+ '@aws-sdk/credential-provider-ini': 3.882.0
+ '@aws-sdk/credential-provider-process': 3.882.0
+ '@aws-sdk/credential-provider-sso': 3.882.0
+ '@aws-sdk/credential-provider-web-identity': 3.882.0
+ '@aws-sdk/types': 3.862.0
+ '@smithy/credential-provider-imds': 4.1.0
+ '@smithy/property-provider': 4.1.0
+ '@smithy/shared-ini-file-loader': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/credential-provider-process@3.882.0':
+ dependencies:
+ '@aws-sdk/core': 3.882.0
+ '@aws-sdk/types': 3.862.0
+ '@smithy/property-provider': 4.1.0
+ '@smithy/shared-ini-file-loader': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-sso@3.882.0':
+ dependencies:
+ '@aws-sdk/client-sso': 3.882.0
+ '@aws-sdk/core': 3.882.0
+ '@aws-sdk/token-providers': 3.882.0
+ '@aws-sdk/types': 3.862.0
+ '@smithy/property-provider': 4.1.0
+ '@smithy/shared-ini-file-loader': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/credential-provider-web-identity@3.882.0':
+ dependencies:
+ '@aws-sdk/core': 3.882.0
+ '@aws-sdk/nested-clients': 3.882.0
+ '@aws-sdk/types': 3.862.0
+ '@smithy/property-provider': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/middleware-bucket-endpoint@3.873.0':
+ dependencies:
+ '@aws-sdk/types': 3.862.0
+ '@aws-sdk/util-arn-parser': 3.873.0
+ '@smithy/node-config-provider': 4.2.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/types': 4.4.0
+ '@smithy/util-config-provider': 4.1.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-expect-continue@3.873.0':
+ dependencies:
+ '@aws-sdk/types': 3.862.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-flexible-checksums@3.882.0':
+ dependencies:
+ '@aws-crypto/crc32': 5.2.0
+ '@aws-crypto/crc32c': 5.2.0
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/core': 3.882.0
+ '@aws-sdk/types': 3.862.0
+ '@smithy/is-array-buffer': 4.1.0
+ '@smithy/node-config-provider': 4.2.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/types': 4.4.0
+ '@smithy/util-middleware': 4.1.0
+ '@smithy/util-stream': 4.3.0
+ '@smithy/util-utf8': 4.1.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-host-header@3.873.0':
+ dependencies:
+ '@aws-sdk/types': 3.862.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-location-constraint@3.873.0':
+ dependencies:
+ '@aws-sdk/types': 3.862.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-logger@3.876.0':
+ dependencies:
+ '@aws-sdk/types': 3.862.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-recursion-detection@3.873.0':
+ dependencies:
+ '@aws-sdk/types': 3.862.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-sdk-s3@3.882.0':
+ dependencies:
+ '@aws-sdk/core': 3.882.0
+ '@aws-sdk/types': 3.862.0
+ '@aws-sdk/util-arn-parser': 3.873.0
+ '@smithy/core': 3.10.0
+ '@smithy/node-config-provider': 4.2.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/signature-v4': 5.2.0
+ '@smithy/smithy-client': 4.6.0
+ '@smithy/types': 4.4.0
+ '@smithy/util-config-provider': 4.1.0
+ '@smithy/util-middleware': 4.1.0
+ '@smithy/util-stream': 4.3.0
+ '@smithy/util-utf8': 4.1.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-ssec@3.873.0':
+ dependencies:
+ '@aws-sdk/types': 3.862.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-user-agent@3.882.0':
+ dependencies:
+ '@aws-sdk/core': 3.882.0
+ '@aws-sdk/types': 3.862.0
+ '@aws-sdk/util-endpoints': 3.879.0
+ '@smithy/core': 3.10.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@aws-sdk/nested-clients@3.882.0':
+ dependencies:
+ '@aws-crypto/sha256-browser': 5.2.0
+ '@aws-crypto/sha256-js': 5.2.0
+ '@aws-sdk/core': 3.882.0
+ '@aws-sdk/middleware-host-header': 3.873.0
+ '@aws-sdk/middleware-logger': 3.876.0
+ '@aws-sdk/middleware-recursion-detection': 3.873.0
+ '@aws-sdk/middleware-user-agent': 3.882.0
+ '@aws-sdk/region-config-resolver': 3.873.0
+ '@aws-sdk/types': 3.862.0
+ '@aws-sdk/util-endpoints': 3.879.0
+ '@aws-sdk/util-user-agent-browser': 3.873.0
+ '@aws-sdk/util-user-agent-node': 3.882.0
+ '@smithy/config-resolver': 4.2.0
+ '@smithy/core': 3.10.0
+ '@smithy/fetch-http-handler': 5.2.0
+ '@smithy/hash-node': 4.1.0
+ '@smithy/invalid-dependency': 4.1.0
+ '@smithy/middleware-content-length': 4.1.0
+ '@smithy/middleware-endpoint': 4.2.0
+ '@smithy/middleware-retry': 4.2.0
+ '@smithy/middleware-serde': 4.1.0
+ '@smithy/middleware-stack': 4.1.0
+ '@smithy/node-config-provider': 4.2.0
+ '@smithy/node-http-handler': 4.2.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/smithy-client': 4.6.0
+ '@smithy/types': 4.4.0
+ '@smithy/url-parser': 4.1.0
+ '@smithy/util-base64': 4.1.0
+ '@smithy/util-body-length-browser': 4.1.0
+ '@smithy/util-body-length-node': 4.1.0
+ '@smithy/util-defaults-mode-browser': 4.1.0
+ '@smithy/util-defaults-mode-node': 4.1.0
+ '@smithy/util-endpoints': 3.1.0
+ '@smithy/util-middleware': 4.1.0
+ '@smithy/util-retry': 4.1.0
+ '@smithy/util-utf8': 4.1.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/region-config-resolver@3.873.0':
+ dependencies:
+ '@aws-sdk/types': 3.862.0
+ '@smithy/node-config-provider': 4.2.0
+ '@smithy/types': 4.4.0
+ '@smithy/util-config-provider': 4.1.0
+ '@smithy/util-middleware': 4.1.0
+ tslib: 2.8.1
+
+ '@aws-sdk/signature-v4-multi-region@3.882.0':
+ dependencies:
+ '@aws-sdk/middleware-sdk-s3': 3.882.0
+ '@aws-sdk/types': 3.862.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/signature-v4': 5.2.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@aws-sdk/token-providers@3.882.0':
+ dependencies:
+ '@aws-sdk/core': 3.882.0
+ '@aws-sdk/nested-clients': 3.882.0
+ '@aws-sdk/types': 3.862.0
+ '@smithy/property-provider': 4.1.0
+ '@smithy/shared-ini-file-loader': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - aws-crt
+
+ '@aws-sdk/types@3.862.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@aws-sdk/util-arn-parser@3.873.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@aws-sdk/util-endpoints@3.879.0':
+ dependencies:
+ '@aws-sdk/types': 3.862.0
+ '@smithy/types': 4.4.0
+ '@smithy/url-parser': 4.1.0
+ '@smithy/util-endpoints': 3.1.0
+ tslib: 2.8.1
+
+ '@aws-sdk/util-locate-window@3.873.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@aws-sdk/util-user-agent-browser@3.873.0':
+ dependencies:
+ '@aws-sdk/types': 3.862.0
+ '@smithy/types': 4.4.0
+ bowser: 2.12.1
+ tslib: 2.8.1
+
+ '@aws-sdk/util-user-agent-node@3.882.0':
+ dependencies:
+ '@aws-sdk/middleware-user-agent': 3.882.0
+ '@aws-sdk/types': 3.862.0
+ '@smithy/node-config-provider': 4.2.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@aws-sdk/xml-builder@3.873.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
'@babel/runtime@7.28.3': {}
'@bugsnag/cuid@3.2.1': {}
@@ -3021,6 +3866,342 @@ snapshots:
'@rollup/rollup-darwin-arm64@4.50.0':
optional: true
+ '@smithy/abort-controller@4.1.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/chunked-blob-reader-native@4.1.0':
+ dependencies:
+ '@smithy/util-base64': 4.1.0
+ tslib: 2.8.1
+
+ '@smithy/chunked-blob-reader@5.1.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/config-resolver@4.2.0':
+ dependencies:
+ '@smithy/node-config-provider': 4.2.0
+ '@smithy/types': 4.4.0
+ '@smithy/util-config-provider': 4.1.0
+ '@smithy/util-middleware': 4.1.0
+ tslib: 2.8.1
+
+ '@smithy/core@3.10.0':
+ dependencies:
+ '@smithy/middleware-serde': 4.1.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/types': 4.4.0
+ '@smithy/util-base64': 4.1.0
+ '@smithy/util-body-length-browser': 4.1.0
+ '@smithy/util-middleware': 4.1.0
+ '@smithy/util-stream': 4.3.0
+ '@smithy/util-utf8': 4.1.0
+ '@types/uuid': 9.0.8
+ tslib: 2.8.1
+ uuid: 9.0.1
+
+ '@smithy/credential-provider-imds@4.1.0':
+ dependencies:
+ '@smithy/node-config-provider': 4.2.0
+ '@smithy/property-provider': 4.1.0
+ '@smithy/types': 4.4.0
+ '@smithy/url-parser': 4.1.0
+ tslib: 2.8.1
+
+ '@smithy/eventstream-codec@4.1.0':
+ dependencies:
+ '@aws-crypto/crc32': 5.2.0
+ '@smithy/types': 4.4.0
+ '@smithy/util-hex-encoding': 4.1.0
+ tslib: 2.8.1
+
+ '@smithy/eventstream-serde-browser@4.1.0':
+ dependencies:
+ '@smithy/eventstream-serde-universal': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/eventstream-serde-config-resolver@4.2.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/eventstream-serde-node@4.1.0':
+ dependencies:
+ '@smithy/eventstream-serde-universal': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/eventstream-serde-universal@4.1.0':
+ dependencies:
+ '@smithy/eventstream-codec': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/fetch-http-handler@5.2.0':
+ dependencies:
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/querystring-builder': 4.1.0
+ '@smithy/types': 4.4.0
+ '@smithy/util-base64': 4.1.0
+ tslib: 2.8.1
+
+ '@smithy/hash-blob-browser@4.1.0':
+ dependencies:
+ '@smithy/chunked-blob-reader': 5.1.0
+ '@smithy/chunked-blob-reader-native': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/hash-node@4.1.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+ '@smithy/util-buffer-from': 4.1.0
+ '@smithy/util-utf8': 4.1.0
+ tslib: 2.8.1
+
+ '@smithy/hash-stream-node@4.1.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+ '@smithy/util-utf8': 4.1.0
+ tslib: 2.8.1
+
+ '@smithy/invalid-dependency@4.1.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/is-array-buffer@2.2.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/is-array-buffer@4.1.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/md5-js@4.1.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+ '@smithy/util-utf8': 4.1.0
+ tslib: 2.8.1
+
+ '@smithy/middleware-content-length@4.1.0':
+ dependencies:
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/middleware-endpoint@4.2.0':
+ dependencies:
+ '@smithy/core': 3.10.0
+ '@smithy/middleware-serde': 4.1.0
+ '@smithy/node-config-provider': 4.2.0
+ '@smithy/shared-ini-file-loader': 4.1.0
+ '@smithy/types': 4.4.0
+ '@smithy/url-parser': 4.1.0
+ '@smithy/util-middleware': 4.1.0
+ tslib: 2.8.1
+
+ '@smithy/middleware-retry@4.2.0':
+ dependencies:
+ '@smithy/node-config-provider': 4.2.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/service-error-classification': 4.1.0
+ '@smithy/smithy-client': 4.6.0
+ '@smithy/types': 4.4.0
+ '@smithy/util-middleware': 4.1.0
+ '@smithy/util-retry': 4.1.0
+ '@types/uuid': 9.0.8
+ tslib: 2.8.1
+ uuid: 9.0.1
+
+ '@smithy/middleware-serde@4.1.0':
+ dependencies:
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/middleware-stack@4.1.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/node-config-provider@4.2.0':
+ dependencies:
+ '@smithy/property-provider': 4.1.0
+ '@smithy/shared-ini-file-loader': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/node-http-handler@4.2.0':
+ dependencies:
+ '@smithy/abort-controller': 4.1.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/querystring-builder': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/property-provider@4.1.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/protocol-http@5.2.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/querystring-builder@4.1.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+ '@smithy/util-uri-escape': 4.1.0
+ tslib: 2.8.1
+
+ '@smithy/querystring-parser@4.1.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/service-error-classification@4.1.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+
+ '@smithy/shared-ini-file-loader@4.1.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/signature-v4@5.2.0':
+ dependencies:
+ '@smithy/is-array-buffer': 4.1.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/types': 4.4.0
+ '@smithy/util-hex-encoding': 4.1.0
+ '@smithy/util-middleware': 4.1.0
+ '@smithy/util-uri-escape': 4.1.0
+ '@smithy/util-utf8': 4.1.0
+ tslib: 2.8.1
+
+ '@smithy/smithy-client@4.6.0':
+ dependencies:
+ '@smithy/core': 3.10.0
+ '@smithy/middleware-endpoint': 4.2.0
+ '@smithy/middleware-stack': 4.1.0
+ '@smithy/protocol-http': 5.2.0
+ '@smithy/types': 4.4.0
+ '@smithy/util-stream': 4.3.0
+ tslib: 2.8.1
+
+ '@smithy/types@4.4.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/url-parser@4.1.0':
+ dependencies:
+ '@smithy/querystring-parser': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/util-base64@4.1.0':
+ dependencies:
+ '@smithy/util-buffer-from': 4.1.0
+ '@smithy/util-utf8': 4.1.0
+ tslib: 2.8.1
+
+ '@smithy/util-body-length-browser@4.1.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/util-body-length-node@4.1.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/util-buffer-from@2.2.0':
+ dependencies:
+ '@smithy/is-array-buffer': 2.2.0
+ tslib: 2.8.1
+
+ '@smithy/util-buffer-from@4.1.0':
+ dependencies:
+ '@smithy/is-array-buffer': 4.1.0
+ tslib: 2.8.1
+
+ '@smithy/util-config-provider@4.1.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/util-defaults-mode-browser@4.1.0':
+ dependencies:
+ '@smithy/property-provider': 4.1.0
+ '@smithy/smithy-client': 4.6.0
+ '@smithy/types': 4.4.0
+ bowser: 2.12.1
+ tslib: 2.8.1
+
+ '@smithy/util-defaults-mode-node@4.1.0':
+ dependencies:
+ '@smithy/config-resolver': 4.2.0
+ '@smithy/credential-provider-imds': 4.1.0
+ '@smithy/node-config-provider': 4.2.0
+ '@smithy/property-provider': 4.1.0
+ '@smithy/smithy-client': 4.6.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/util-endpoints@3.1.0':
+ dependencies:
+ '@smithy/node-config-provider': 4.2.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/util-hex-encoding@4.1.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/util-middleware@4.1.0':
+ dependencies:
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/util-retry@4.1.0':
+ dependencies:
+ '@smithy/service-error-classification': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
+ '@smithy/util-stream@4.3.0':
+ dependencies:
+ '@smithy/fetch-http-handler': 5.2.0
+ '@smithy/node-http-handler': 4.2.0
+ '@smithy/types': 4.4.0
+ '@smithy/util-base64': 4.1.0
+ '@smithy/util-buffer-from': 4.1.0
+ '@smithy/util-hex-encoding': 4.1.0
+ '@smithy/util-utf8': 4.1.0
+ tslib: 2.8.1
+
+ '@smithy/util-uri-escape@4.1.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/util-utf8@2.3.0':
+ dependencies:
+ '@smithy/util-buffer-from': 2.2.0
+ tslib: 2.8.1
+
+ '@smithy/util-utf8@4.1.0':
+ dependencies:
+ '@smithy/util-buffer-from': 4.1.0
+ tslib: 2.8.1
+
+ '@smithy/util-waiter@4.1.0':
+ dependencies:
+ '@smithy/abort-controller': 4.1.0
+ '@smithy/types': 4.4.0
+ tslib: 2.8.1
+
'@socket.io/component-emitter@3.1.2': {}
'@swc/counter@0.1.3': {}
@@ -3217,6 +4398,8 @@ snapshots:
'@types/prop-types': 15.7.15
csstype: 3.1.3
+ '@types/uuid@9.0.8': {}
+
'@vercel/analytics@1.3.1(next@14.2.25(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)':
dependencies:
server-only: 0.0.1
@@ -3253,6 +4436,8 @@ snapshots:
bintrees@1.0.2: {}
+ bowser@2.12.1: {}
+
browserslist@4.25.4:
dependencies:
caniuse-lite: 1.0.30001739
@@ -3456,6 +4641,10 @@ snapshots:
fast-equals@5.2.2: {}
+ fast-xml-parser@5.2.5:
+ dependencies:
+ strnum: 2.1.1
+
fraction.js@4.3.7: {}
function-bind@1.1.2: {}
@@ -3866,6 +5055,8 @@ snapshots:
strip-final-newline@3.0.0: {}
+ strnum@2.1.1: {}
+
styled-jsx@5.1.1(react@19.1.0):
dependencies:
client-only: 0.0.1
diff --git a/product-image-generator/src/trigger/example.ts b/product-image-generator/src/trigger/example.ts
deleted file mode 100644
index 871d8c0..0000000
--- a/product-image-generator/src/trigger/example.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { logger, task, wait } from "@trigger.dev/sdk/v3";
-
-export const helloWorldTask = task({
- id: "hello-world",
- // Set an optional maxDuration to prevent tasks from running indefinitely
- maxDuration: 300, // Stop executing after 300 secs (5 mins) of compute
- run: async (payload: any, { ctx }) => {
- logger.log("Hello, world!", { payload, ctx });
-
- await wait.for({ seconds: 5 });
-
- return {
- message: "Hello, world!",
- };
- },
-});
diff --git a/product-image-generator/src/trigger/image-upload.ts b/product-image-generator/src/trigger/image-upload.ts
new file mode 100644
index 0000000..6e585dc
--- /dev/null
+++ b/product-image-generator/src/trigger/image-upload.ts
@@ -0,0 +1,122 @@
+import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
+import { logger, metadata, task } from "@trigger.dev/sdk";
+import { Buffer } from "buffer";
+
+// Initialize S3 client for R2
+const s3Client = new S3Client({
+ region: "auto",
+ endpoint: process.env.R2_ENDPOINT,
+ credentials: {
+ accessKeyId: process.env.R2_ACCESS_KEY_ID ?? "",
+ secretAccessKey: process.env.R2_SECRET_ACCESS_KEY ?? "",
+ },
+});
+
+export const uploadImageToR2 = task({
+ id: "upload-image-to-r2",
+ maxDuration: 300, // 5 minutes max
+ run: async (payload: {
+ imageBuffer: string; // base64 encoded image
+ fileName: string;
+ contentType: string;
+ }) => {
+ const { imageBuffer, fileName, contentType } = payload;
+
+ // Set initial metadata
+ metadata.set("status", "starting");
+ metadata.set("progress", {
+ step: 1,
+ total: 4,
+ message: "Preparing upload...",
+ });
+
+ logger.log("Starting image upload to R2", { fileName, contentType });
+
+ // Convert base64 to buffer
+ metadata.set("progress", {
+ step: 2,
+ total: 4,
+ message: "Processing image data...",
+ });
+ const buffer = Buffer.from(imageBuffer, "base64");
+ const fileSize = buffer.length;
+
+ logger.log(`Image size: ${fileSize} bytes`);
+
+ // Generate unique key for R2
+ const timestamp = Date.now();
+ const sanitizedFileName = fileName.replace(/[^a-zA-Z0-9.-]/g, "_");
+ const r2Key = `uploaded-images/${timestamp}-${sanitizedFileName}`;
+
+ metadata.set("progress", {
+ step: 3,
+ total: 4,
+ message: "Uploading to storage...",
+ });
+
+ const uploadParams = {
+ Bucket: process.env.R2_BUCKET,
+ Key: r2Key,
+ Body: buffer,
+ ContentType: contentType,
+ // Add cache control for better performance
+ CacheControl: "public, max-age=31536000", // 1 year
+ };
+
+ try {
+ // Upload to R2
+ logger.log("About to upload to R2", {
+ bucket: process.env.R2_BUCKET,
+ endpoint: process.env.R2_ENDPOINT,
+ r2Key,
+ fileSize,
+ hasAccessKey: !!process.env.R2_ACCESS_KEY_ID,
+ hasSecretKey: !!process.env.R2_SECRET_ACCESS_KEY,
+ });
+
+ const result = await s3Client.send(new PutObjectCommand(uploadParams));
+ logger.log("S3 PutObject response:", result as any);
+ logger.log(`Image uploaded successfully to R2`, { r2Key });
+
+ // Update metadata for completion
+ metadata.set("progress", {
+ step: 4,
+ total: 4,
+ message: "Upload completed!",
+ });
+ metadata.set("status", "completed");
+
+ // Construct the public URL using the R2_PUBLIC_URL env var
+ const publicUrl = `${process.env.R2_PUBLIC_URL}/${r2Key}`;
+
+ // Set final metadata with result
+ metadata.set("result", {
+ publicUrl,
+ r2Key,
+ fileSize,
+ fileName: sanitizedFileName,
+ });
+
+ return {
+ success: true,
+ bucket: process.env.R2_BUCKET,
+ r2Key,
+ publicUrl,
+ fileSize,
+ contentType,
+ fileName: sanitizedFileName,
+ };
+ } catch (error) {
+ // Set error metadata
+ metadata.set("status", "failed");
+ metadata.set("progress", { step: 0, total: 4, message: "Upload failed" });
+ metadata.set(
+ "error",
+ error instanceof Error ? error.message : "Unknown error",
+ );
+
+ logger.error("Failed to upload image to R2", { error, r2Key });
+ throw error;
+ }
+ },
+});
diff --git a/product-image-generator/src/trigger/index.ts b/product-image-generator/src/trigger/index.ts
deleted file mode 100644
index a11d031..0000000
--- a/product-image-generator/src/trigger/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { helloWorldTask } from "./example";
diff --git a/product-image-generator/trigger.config.ts b/product-image-generator/trigger.config.ts
index 727e7e5..6fe60ff 100644
--- a/product-image-generator/trigger.config.ts
+++ b/product-image-generator/trigger.config.ts
@@ -11,7 +11,7 @@ export default defineConfig({
retries: {
enabledInDev: true,
default: {
- maxAttempts: 3,
+ maxAttempts: 1,
minTimeoutInMs: 1000,
maxTimeoutInMs: 10000,
factor: 2,
diff --git a/product-image-generator/tsconfig.json b/product-image-generator/tsconfig.json
index c133409..ceb6ecf 100644
--- a/product-image-generator/tsconfig.json
+++ b/product-image-generator/tsconfig.json
@@ -19,9 +19,14 @@
}
],
"paths": {
- "@/*": ["./src/*"]
+ "@/*": ["./app/*"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules"]
+ "include": [
+ "next-env.d.ts",
+ "app/**/*.ts",
+ "app/**/*.tsx",
+ ".next/types/**/*.ts"
+ ],
+ "exclude": ["node_modules", "src"]
}
From 22c1d27fbe525c4f204bedb768aa3fbcd36abd61 Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Fri, 5 Sep 2025 11:10:00 +0100
Subject: [PATCH 034/232] Added more tasks and updated actions
---
product-image-generator/README.md | 242 ++++++++++++++++--
product-image-generator/app/actions.ts | 97 +++++++
.../app/components/GeneratedCard.tsx | 194 ++++++++++++++
.../app/components/UploadCard.tsx | 13 +-
product-image-generator/app/layout.tsx | 9 +-
product-image-generator/app/page.tsx | 158 ++++++++++--
product-image-generator/package.json | 2 +
product-image-generator/pnpm-lock.yaml | 81 +++++-
.../src/trigger/batch-generate-and-upload.ts | 143 +++++++++++
.../src/trigger/batch-generate-images.ts | 143 +++++++++++
.../src/trigger/generate-and-upload-image.ts | 169 ++++++++++++
.../src/trigger/generate-image.ts | 147 +++++++++++
12 files changed, 1358 insertions(+), 40 deletions(-)
create mode 100644 product-image-generator/app/components/GeneratedCard.tsx
create mode 100644 product-image-generator/src/trigger/batch-generate-and-upload.ts
create mode 100644 product-image-generator/src/trigger/batch-generate-images.ts
create mode 100644 product-image-generator/src/trigger/generate-and-upload-image.ts
create mode 100644 product-image-generator/src/trigger/generate-image.ts
diff --git a/product-image-generator/README.md b/product-image-generator/README.md
index e215bc4..f514242 100644
--- a/product-image-generator/README.md
+++ b/product-image-generator/README.md
@@ -1,36 +1,240 @@
-This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
+# AI Product Image Generator
+
+Transform product photos into professional marketing materials using AI-powered image generation.
+
+Upload a product image and automatically generate three marketing variations: isolated table shots, lifestyle scenes, and hero presentations. Built with Next.js, Trigger.dev, and OpenAI's DALL-E 3.
+
+## Features
+
+- Real-time progress tracking with WebSocket subscriptions
+- Product-focused AI prompts that maintain identical product appearance
+- Responsive design with portrait-oriented image cards
+- Automatic upload to Cloudflare R2 with public URLs
+- Individual retry mechanisms for failed generations
+- One-click download functionality
+- Parallel processing for multiple image generations
+
+## Architecture
+
+### Task Structure
+
+```
+uploadImageToR2
+├── Handles user image uploads to R2 storage
+├── 4-step progress tracking
+
+generateAndUploadImage
+├── Generates AI image using DALL-E 3
+├── Uploads result to R2 storage
+├── 5-step progress tracking
+
+batchGenerateAndUploadImages
+├── Triggers 3x generateAndUploadImage tasks in parallel
+├── Returns individual run IDs for UI subscription
+```
+
+### Component Architecture
+
+```
+UploadCard (aspect-square)
+├── Drag & drop image upload
+├── Progress tracking via run subscription
+├── Triggers batch generation on completion
+
+GeneratedCard (aspect-[3/4])
+├── Individual task progress tracking
+├── Real-time image display via subscription
+├── Download and retry functionality
+
+Main Page
+├── Grid layout: 1 upload + 3 generated cards
+├── State management for run IDs and access tokens
+├── Individual generation triggering and retry handling
+```
## Getting Started
-First, run the development server:
+### Prerequisites
+
+- Node.js 18+ and pnpm
+- Trigger.dev account and project
+- OpenAI API key with DALL-E 3 access
+- Cloudflare R2 bucket for image storage
+
+### Environment Variables
+
+```env
+# Trigger.dev
+TRIGGER_SECRET_KEY=tr_dev_your_secret_key_here
+
+# OpenAI
+OPENAI_API_KEY=sk-your_openai_api_key_here
+
+# Cloudflare R2 Storage
+R2_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com
+R2_ACCESS_KEY_ID=your_r2_access_key
+R2_SECRET_ACCESS_KEY=your_r2_secret_key
+R2_BUCKET=your-bucket-name
+R2_PUBLIC_URL=https://your-public-domain.com
+```
+
+### Installation
```bash
-npm run dev
-# or
-yarn dev
-# or
+# Install dependencies
+pnpm install
+
+# Start development server
pnpm dev
-# or
-bun dev
+
+# Start Trigger.dev dev server (separate terminal)
+pnpm dlx trigger.dev@latest dev
```
-Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+## Usage
+
+1. Upload a product image via drag & drop or file selection
+2. Upload task runs with 4-step progress tracking
+3. On completion, batch task triggers 3 parallel generation tasks
+4. Each GeneratedCard subscribes to its individual task progress
+5. Generated images display with download functionality
+6. Failed generations can be retried individually
+
+## Technical Implementation
+
+### AI Prompt Engineering
+
+Each generation uses prompts that emphasize:
+
+- Product consistency: "EXACT same product from reference image"
+- Material preservation: "Identical colors, textures, materials, and design details"
+- Context adaptation: Only backgrounds and lighting change
+
+### Progress Tracking Implementation
+
+- Upload Task: 4 steps (prepare → process → upload → complete)
+- Generation Tasks: 5 steps (prepare → generate → prepare upload → upload → complete)
+- Real-time updates via `runs.subscribeToRun()` with public access tokens
+
+### Image Processing
+
+- Format: Portrait 1024x1792 for optimal display in 3:4 aspect ratio cards
+- Display: `object-contain` CSS to prevent cropping
+- Storage: Cloudflare R2 with automatic public URL generation
+- Fallback: Base64 blob URLs if storage unavailable
+
+## Project Structure
+
+```
+src/
+├── trigger/
+│ ├── image-upload.ts # User image upload to R2
+│ ├── generate-and-upload-image.ts # AI generation + upload (single task)
+│ └── batch-generate-and-upload.ts # Batch processing coordinator
+├── app/
+│ ├── actions.ts # Server actions for task triggering
+│ ├── components/
+│ │ ├── UploadCard.tsx # Image upload with progress
+│ │ ├── GeneratedCard.tsx # Generated image display with subscription
+│ │ └── ui/ # shadcn/ui components
+│ └── page.tsx # Main application interface
+└── trigger.config.ts # Trigger.dev configuration
+```
+
+## Task Flow Details
+
+### Upload Flow
+
+1. `UploadCard` triggers `uploadImageToR2` task
+2. Task converts file to base64, uploads to R2, returns public URL
+3. `UploadCard` subscribes to run progress, displays completion
+4. On completion, triggers `batchGenerateAndUploadImages`
+
+### Generation Flow
+
+1. `batchGenerateAndUploadImages` triggers 3x `generateAndUploadImage` tasks
+2. Each task: generates image → uploads to R2 → returns public URL
+3. `GeneratedCard` components subscribe to individual task progress
+4. Images display automatically when tasks complete
+
+### Error Handling
+
+- Individual task failures don't affect other generations
+- Retry functionality re-triggers specific failed tasks
+- Comprehensive error logging and user feedback
+
+## Customization
+
+### Adding Generation Styles
+
+Edit prompts in `src/trigger/batch-generate-and-upload.ts`:
+
+```typescript
+const prompts = [
+ {
+ id: "your-style-id",
+ prompt: "Your detailed prompt here...",
+ },
+];
+```
+
+### Modifying Image Dimensions
+
+Change size parameter in actions:
+
+```typescript
+size: "1024x1792", // Portrait
+size: "1792x1024", // Landscape
+size: "1024x1024", // Square
+```
+
+### Custom Progress Messages
+
+Update metadata in task files:
+
+```typescript
+metadata.set("progress", {
+ step: 2,
+ total: 5,
+ message: "Custom progress message",
+});
+```
+
+## Deployment
+
+### Production Deployment
+
+```bash
+# Deploy tasks to Trigger.dev
+pnpm dlx trigger.dev@latest deploy
+
+# Deploy frontend (Vercel recommended)
+vercel deploy
+```
-You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+### Environment Configuration
-This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
+- Add all environment variables to deployment platform
+- Configure R2 bucket CORS for production domain
+- Update `R2_PUBLIC_URL` for production storage access
+- Ensure OpenAI API key has sufficient credits and rate limits
-## Learn More
+## Development Notes
-To learn more about Next.js, take a look at the following resources:
+### Key Implementation Details
-- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
-- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+- Uses `triggerAndWait()` for sequential task dependencies
+- Public access tokens enable client-side run subscriptions
+- `aspect-[3/4]` Tailwind class for portrait card layout
+- Error boundaries and timeout handling for stuck tasks
-You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
+### Performance Considerations
-## Deploy on Vercel
+- Parallel task execution for multiple image generations
+- Efficient base64 to blob conversion for image display
+- Proper cleanup of blob URLs and timeouts
+- Rate limiting considerations for OpenAI API calls
-The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+## License
-Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
+MIT License - see LICENSE file for details.
diff --git a/product-image-generator/app/actions.ts b/product-image-generator/app/actions.ts
index 98611d5..bd52367 100644
--- a/product-image-generator/app/actions.ts
+++ b/product-image-generator/app/actions.ts
@@ -2,6 +2,8 @@
import { auth, tasks } from "@trigger.dev/sdk/v3";
import type { uploadImageToR2 } from "../src/trigger/image-upload";
+import type { batchGenerateAndUploadImages } from "../src/trigger/batch-generate-and-upload";
+import type { generateAndUploadImage } from "../src/trigger/generate-and-upload-image";
export async function createPublicAccessToken(runId: string) {
try {
@@ -73,3 +75,98 @@ export async function uploadImageToR2Action(formData: FormData) {
};
}
}
+
+export async function batchGenerateImagesAction(
+ baseImageUrl: string,
+ productDescription?: string,
+) {
+ try {
+ const handle = await tasks.trigger
(
+ "batch-generate-and-upload-images",
+ {
+ baseImageUrl,
+ productDescription,
+ },
+ );
+
+ // Create a public access token for this specific run
+ const tokenResult = await createPublicAccessToken(handle.id);
+
+ if (!tokenResult.success) {
+ return {
+ success: false as const,
+ error: tokenResult.error,
+ };
+ }
+
+ return {
+ success: true as const,
+ runId: handle.id,
+ accessToken: tokenResult.token,
+ };
+ } catch (error) {
+ console.error("Failed to trigger batch generation:", error);
+ return {
+ success: false as const,
+ error: "Failed to trigger batch generation",
+ };
+ }
+}
+
+export async function generateSingleImageAction(
+ baseImageUrl: string,
+ promptId: string,
+ productDescription: string = "product",
+) {
+ try {
+ // Define the prompts - emphasizing exact product replication
+ const prompts = {
+ "isolated-table":
+ `Create a professional product photography shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Place this identical product isolated on a clean white table surface with studio lighting, minimalist background, commercial photography style, high resolution, sharp focus. The product must look exactly the same as in the reference image.`,
+ "lifestyle-scene":
+ `Create a lifestyle product photography shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Place this identical product in a modern home setting with natural lighting, styled environment, aspirational lifestyle, professional commercial photography. The product must look exactly the same as in the reference image, only the background and setting should change.`,
+ "hero-shot":
+ `Create a hero product shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Present this identical product with dramatic lighting, premium presentation, luxury commercial photography style, perfect for marketing materials, high-end aesthetic. The product must look exactly the same as in the reference image, only the lighting and presentation should be enhanced.`,
+ };
+
+ const prompt = prompts[promptId as keyof typeof prompts];
+ if (!prompt) {
+ return {
+ success: false as const,
+ error: "Invalid prompt ID",
+ };
+ }
+
+ const handle = await tasks.trigger(
+ "generate-and-upload-image",
+ {
+ prompt,
+ baseImageUrl,
+ model: "dall-e-3",
+ size: "1024x1792", // Portrait format for better slot fitting
+ },
+ );
+
+ // Create a public access token for this specific run
+ const tokenResult = await createPublicAccessToken(handle.id);
+
+ if (!tokenResult.success) {
+ return {
+ success: false as const,
+ error: tokenResult.error,
+ };
+ }
+
+ return {
+ success: true as const,
+ runId: handle.id,
+ accessToken: tokenResult.token,
+ };
+ } catch (error) {
+ console.error("Failed to trigger single image generation:", error);
+ return {
+ success: false as const,
+ error: "Failed to trigger image generation",
+ };
+ }
+}
diff --git a/product-image-generator/app/components/GeneratedCard.tsx b/product-image-generator/app/components/GeneratedCard.tsx
new file mode 100644
index 0000000..dfa13a9
--- /dev/null
+++ b/product-image-generator/app/components/GeneratedCard.tsx
@@ -0,0 +1,194 @@
+"use client";
+
+import { Button } from "./ui/button";
+import { Card } from "./ui/card";
+import { Download, RefreshCw } from "lucide-react";
+import { useState, useEffect } from "react";
+import { runs, configure } from "@trigger.dev/sdk/v3";
+
+interface GeneratedCardProps {
+ runId: string | null;
+ accessToken: string | null;
+ promptId: string;
+ promptTitle: string;
+ onRetry?: () => void;
+}
+
+export default function GeneratedCard({
+ runId,
+ accessToken,
+ promptId,
+ promptTitle,
+ onRetry,
+}: GeneratedCardProps) {
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [generatedImageUrl, setGeneratedImageUrl] = useState(
+ null
+ );
+ const [generationProgress, setGenerationProgress] = useState("idle");
+ const [progressMessage, setProgressMessage] = useState("");
+ const [progressStep, setProgressStep] = useState<{
+ step: number;
+ total: number;
+ } | null>(null);
+
+ // Subscribe to run updates when runId and accessToken are available
+ useEffect(() => {
+ if (!runId || !accessToken) return;
+
+ const subscribeToRun = async () => {
+ setIsLoading(true);
+ setGenerationProgress("generating");
+ setError(null);
+
+ try {
+ configure({
+ secretKey: accessToken,
+ });
+
+ for await (const run of runs.subscribeToRun(runId)) {
+ // Update progress from metadata
+ if (run.metadata?.progress) {
+ const progress = run.metadata.progress as {
+ step: number;
+ total: number;
+ message: string;
+ };
+ setProgressMessage(progress.message);
+ setProgressStep({ step: progress.step, total: progress.total });
+ }
+
+ // Handle completion
+ if (run.status === "COMPLETED" && run.output) {
+ setGeneratedImageUrl(run.output.publicUrl);
+ setGenerationProgress("completed");
+ setProgressMessage("Generation completed!");
+ setIsLoading(false);
+ break;
+ } else if (run.status === "FAILED") {
+ const errorMsg = run.metadata?.error || "Generation failed";
+ setError(
+ typeof errorMsg === "string" ? errorMsg : "Generation failed"
+ );
+ setGenerationProgress("failed");
+ setProgressMessage("");
+ setIsLoading(false);
+ break;
+ }
+ }
+ } catch (err) {
+ setError("Failed to get task updates");
+ setGenerationProgress("failed");
+ setIsLoading(false);
+ }
+ };
+
+ subscribeToRun();
+ }, [runId, accessToken]);
+
+ const handleDownload = () => {
+ if (generatedImageUrl) {
+ const link = document.createElement("a");
+ link.href = generatedImageUrl;
+ link.download = `generated-${promptId}-${Date.now()}.png`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ }
+ };
+
+ const handleRetry = () => {
+ if (onRetry) {
+ setError(null);
+ setGeneratedImageUrl(null);
+ setGenerationProgress("idle");
+ setProgressMessage("");
+ setProgressStep(null);
+ onRetry();
+ }
+ };
+
+ return (
+
+ {generatedImageUrl ? (
+ // Show generated image
+
+
+ {/* Download button */}
+
+
+
+
+
+ {/* Title overlay */}
+
+
+ ) : generationProgress === "failed" ? (
+ // Show error state
+
+
+
+
+
+ {promptTitle}
+
+
{error}
+ {onRetry && (
+
+
+ Retry
+
+ )}
+
+ ) : (
+ // Show loading/waiting state
+
+
+ {isLoading && generationProgress === "generating" ? (
+
+ ) : (
+
+ )}
+
+
+ {promptTitle}
+
+
+ {generationProgress === "generating"
+ ? progressMessage || "Generating..."
+ : generationProgress === "idle"
+ ? "Waiting to start..."
+ : "Ready"}
+
+ {progressStep && generationProgress === "generating" && (
+ <>
+
+
+ Step {progressStep.step} of {progressStep.total}
+
+ >
+ )}
+
+ )}
+
+ );
+}
diff --git a/product-image-generator/app/components/UploadCard.tsx b/product-image-generator/app/components/UploadCard.tsx
index 1c4857e..d49b5d8 100644
--- a/product-image-generator/app/components/UploadCard.tsx
+++ b/product-image-generator/app/components/UploadCard.tsx
@@ -7,7 +7,11 @@ import { useRef, useState, useEffect } from "react";
import { uploadImageToR2Action } from "../actions";
import { runs, configure } from "@trigger.dev/sdk/v3";
-export default function UploadCard() {
+interface UploadCardProps {
+ onUploadComplete?: (imageUrl: string) => void;
+}
+
+export default function UploadCard({ onUploadComplete }: UploadCardProps) {
const [isDragOver, setIsDragOver] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [runId, setRunId] = useState(null);
@@ -51,6 +55,11 @@ export default function UploadCard() {
setUploadProgress("completed");
setProgressMessage("Upload completed!");
setIsLoading(false);
+
+ // Notify parent component
+ if (onUploadComplete && run.output.publicUrl) {
+ onUploadComplete(run.output.publicUrl);
+ }
break;
} else if (run.status === "FAILED") {
const errorMsg = run.metadata?.error || "Upload failed";
@@ -145,7 +154,7 @@ export default function UploadCard() {
return (
(null);
+ const [generationRunIds, setGenerationRunIds] = useState<{
+ [key: string]: string | null;
+ }>({
+ "isolated-table": null,
+ "lifestyle-scene": null,
+ "hero-shot": null,
+ });
+ const [generationAccessTokens, setGenerationAccessTokens] = useState<{
+ [key: string]: string | null;
+ }>({
+ "isolated-table": null,
+ "lifestyle-scene": null,
+ "hero-shot": null,
+ });
+
+ const promptTitles = {
+ "isolated-table": "Table Shot",
+ "lifestyle-scene": "Lifestyle Scene",
+ "hero-shot": "Hero Shot",
+ };
+
+ const handleUploadComplete = async (imageUrl: string) => {
+ try {
+ // Store the uploaded image URL for retries
+ setUploadedImageUrl(imageUrl);
+
+ // Trigger all 3 generations individually for better progress tracking
+ const promptIds = ["isolated-table", "lifestyle-scene", "hero-shot"];
+
+ for (const promptId of promptIds) {
+ const result = await generateSingleImageAction(
+ imageUrl,
+ promptId,
+ "product"
+ );
+
+ if (result.success) {
+ console.log(`Successfully triggered generation for ${promptId}:`, {
+ runId: result.runId,
+ hasAccessToken: !!result.accessToken,
+ });
+ setGenerationRunIds((prev) => ({
+ ...prev,
+ [promptId]: result.runId,
+ }));
+ setGenerationAccessTokens((prev) => ({
+ ...prev,
+ [promptId]: result.accessToken,
+ }));
+ } else {
+ console.error(
+ `Failed to start generation for ${promptId}:`,
+ result.error
+ );
+ }
+ }
+ } catch (error) {
+ console.error("Failed to start image generations:", error);
+ }
+ };
+
+ const handleRetryGeneration = async (promptId: string) => {
+ if (!uploadedImageUrl) {
+ console.error("No base image URL available for retry");
+ return;
+ }
+
+ try {
+ // Reset the specific generation
+ setGenerationRunIds((prev) => ({ ...prev, [promptId]: null }));
+ setGenerationAccessTokens((prev) => ({ ...prev, [promptId]: null }));
+
+ // Trigger the specific generation again
+ const result = await generateSingleImageAction(
+ uploadedImageUrl,
+ promptId,
+ "product"
+ );
+
+ if (result.success) {
+ setGenerationRunIds((prev) => ({ ...prev, [promptId]: result.runId }));
+ setGenerationAccessTokens((prev) => ({
+ ...prev,
+ [promptId]: result.accessToken,
+ }));
+ } else {
+ console.error(
+ `Failed to retry generation for ${promptId}:`,
+ result.error
+ );
+ }
+ } catch (error) {
+ console.error(`Failed to retry generation for ${promptId}:`, error);
+ }
+ };
+
return (
{/* Navigation Header */}
@@ -39,28 +141,54 @@ export default function ImageManagementApp() {
- Image Gallery
+ Product Image Generator
- Upload and organize your images with our intuitive drag-and-drop
- interface
+ Upload a product image and automatically generate professional
+ marketing shots
- {/* Image Grid */}
-
- {/* First Slot - Upload Card */}
-
+ {/* Top Row - Upload + 3 Generated Images */}
+
+ {/* Upload card stays square */}
+
+ handleRetryGeneration("isolated-table")}
+ />
+ handleRetryGeneration("lifestyle-scene")}
+ />
+ handleRetryGeneration("hero-shot")}
+ />
+
- {/* Remaining 7 Slots - Blank States */}
- {Array.from({ length: 7 }).map((_, index) => (
+ {/* Bottom Row - 4 More Cards (Future Implementation) */}
+
+ {Array.from({ length: 4 }).map((_, index) => (
-
@@ -71,11 +199,11 @@ export default function ImageManagementApp() {
- Upload Images
+ Upload New Image
- Organize
+ Settings
diff --git a/product-image-generator/package.json b/product-image-generator/package.json
index c0fb4f3..07decbe 100644
--- a/product-image-generator/package.json
+++ b/product-image-generator/package.json
@@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
+ "@ai-sdk/openai": "^2.0.24",
"@aws-sdk/client-s3": "^3.882.0",
"@hookform/resolvers": "^3.10.0",
"@radix-ui/react-accordion": "1.2.2",
@@ -41,6 +42,7 @@
"@trigger.dev/react-hooks": "^4.0.2",
"@trigger.dev/sdk": "^4.0.2",
"@vercel/analytics": "1.3.1",
+ "ai": "^5.0.33",
"autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
diff --git a/product-image-generator/pnpm-lock.yaml b/product-image-generator/pnpm-lock.yaml
index 90e65b5..bf6f9ab 100644
--- a/product-image-generator/pnpm-lock.yaml
+++ b/product-image-generator/pnpm-lock.yaml
@@ -8,6 +8,9 @@ importers:
.:
dependencies:
+ '@ai-sdk/openai':
+ specifier: ^2.0.24
+ version: 2.0.24(zod@3.25.67)
'@aws-sdk/client-s3':
specifier: ^3.882.0
version: 3.882.0
@@ -100,10 +103,13 @@ importers:
version: 4.0.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@trigger.dev/sdk':
specifier: ^4.0.2
- version: 4.0.2(zod@3.25.67)
+ version: 4.0.2(ai@5.0.33(zod@3.25.67))(zod@3.25.67)
'@vercel/analytics':
specifier: 1.3.1
version: 1.3.1(next@14.2.25(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
+ ai:
+ specifier: ^5.0.33
+ version: 5.0.33(zod@3.25.67)
autoprefixer:
specifier: ^10.4.20
version: 10.4.21(postcss@8.5.6)
@@ -198,6 +204,28 @@ importers:
packages:
+ '@ai-sdk/gateway@1.0.18':
+ resolution: {integrity: sha512-tpUF9nwTVFJGH+u9LHccf1TTRMeUrfJPzYJVpHH1tc1vclO695SQUTIR9jnTCuvn1XFYtkiXUALYpQhBYWf3Pg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4
+
+ '@ai-sdk/openai@2.0.24':
+ resolution: {integrity: sha512-8fPFvlb6PpDjy6JtJBP3Hqs4THKFNYOw6+j7nG7iJivNp+uvHlrHwnU6wQgMAesxEDjZRmVB6ntXWxGPCbBeJw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4
+
+ '@ai-sdk/provider-utils@3.0.8':
+ resolution: {integrity: sha512-cDj1iigu7MW2tgAQeBzOiLhjHOUM9vENsgh4oAVitek0d//WdgfPCsKO3euP7m7LyO/j9a1vr/So+BGNdpFXYw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4
+
+ '@ai-sdk/provider@2.0.0':
+ resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==}
+ engines: {node: '>=18'}
+
'@alloc/quick-lru@5.2.0':
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
@@ -1460,6 +1488,9 @@ packages:
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
+ '@standard-schema/spec@1.0.0':
+ resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
+
'@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
@@ -1650,6 +1681,12 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
+ ai@5.0.33:
+ resolution: {integrity: sha512-qmYQTb+K0204mawkjhCMGYPutDqPgmCeh/tQ9I3FpZfxvUe8R462D/MQUgLMFnMQ0z2kpUMoOJBKX6dSKb0OwA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4
+
aria-hidden@1.2.6:
resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
engines: {node: '>=10'}
@@ -1950,6 +1987,9 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+ json-schema@0.4.0:
+ resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
+
lightningcss-darwin-arm64@1.30.1:
resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==}
engines: {node: '>= 12.0.0'}
@@ -2499,6 +2539,29 @@ packages:
snapshots:
+ '@ai-sdk/gateway@1.0.18(zod@3.25.67)':
+ dependencies:
+ '@ai-sdk/provider': 2.0.0
+ '@ai-sdk/provider-utils': 3.0.8(zod@3.25.67)
+ zod: 3.25.67
+
+ '@ai-sdk/openai@2.0.24(zod@3.25.67)':
+ dependencies:
+ '@ai-sdk/provider': 2.0.0
+ '@ai-sdk/provider-utils': 3.0.8(zod@3.25.67)
+ zod: 3.25.67
+
+ '@ai-sdk/provider-utils@3.0.8(zod@3.25.67)':
+ dependencies:
+ '@ai-sdk/provider': 2.0.0
+ '@standard-schema/spec': 1.0.0
+ eventsource-parser: 3.0.6
+ zod: 3.25.67
+
+ '@ai-sdk/provider@2.0.0':
+ dependencies:
+ json-schema: 0.4.0
+
'@alloc/quick-lru@5.2.0': {}
'@aws-crypto/crc32@5.2.0':
@@ -4204,6 +4267,8 @@ snapshots:
'@socket.io/component-emitter@3.1.2': {}
+ '@standard-schema/spec@1.0.0': {}
+
'@swc/counter@0.1.3': {}
'@swc/helpers@0.5.5':
@@ -4333,7 +4398,7 @@ snapshots:
- supports-color
- utf-8-validate
- '@trigger.dev/sdk@4.0.2(zod@3.25.67)':
+ '@trigger.dev/sdk@4.0.2(ai@5.0.33(zod@3.25.67))(zod@3.25.67)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.36.0
@@ -4348,6 +4413,8 @@ snapshots:
uuid: 9.0.1
ws: 8.18.3
zod: 3.25.67
+ optionalDependencies:
+ ai: 5.0.33(zod@3.25.67)
transitivePeerDependencies:
- bufferutil
- supports-color
@@ -4418,6 +4485,14 @@ snapshots:
acorn@8.15.0: {}
+ ai@5.0.33(zod@3.25.67):
+ dependencies:
+ '@ai-sdk/gateway': 1.0.18(zod@3.25.67)
+ '@ai-sdk/provider': 2.0.0
+ '@ai-sdk/provider-utils': 3.0.8(zod@3.25.67)
+ '@opentelemetry/api': 1.9.0
+ zod: 3.25.67
+
aria-hidden@1.2.6:
dependencies:
tslib: 2.8.1
@@ -4697,6 +4772,8 @@ snapshots:
js-tokens@4.0.0: {}
+ json-schema@0.4.0: {}
+
lightningcss-darwin-arm64@1.30.1:
optional: true
diff --git a/product-image-generator/src/trigger/batch-generate-and-upload.ts b/product-image-generator/src/trigger/batch-generate-and-upload.ts
new file mode 100644
index 0000000..ec8e502
--- /dev/null
+++ b/product-image-generator/src/trigger/batch-generate-and-upload.ts
@@ -0,0 +1,143 @@
+import { logger, metadata, task } from "@trigger.dev/sdk";
+import { generateAndUploadImage } from "./generate-and-upload-image";
+
+export const batchGenerateAndUploadImages = task({
+ id: "batch-generate-and-upload-images",
+ maxDuration: 900, // 15 minutes max for all 3 images
+ run: async (payload: {
+ baseImageUrl: string;
+ productDescription?: string;
+ }) => {
+ const { baseImageUrl, productDescription = "product" } = payload;
+
+ // Set initial metadata
+ metadata.set("status", "starting");
+ metadata.set("progress", {
+ step: 1,
+ total: 4,
+ message: "Preparing batch generation...",
+ });
+
+ logger.log("Starting batch image generation and upload", {
+ baseImageUrl,
+ productDescription,
+ });
+
+ // Define the 3 marketing prompts - emphasizing exact product replication
+ const prompts = [
+ {
+ id: "isolated-table",
+ prompt:
+ `Create a professional product photography shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Place this identical product isolated on a clean white table surface with studio lighting, minimalist background, commercial photography style, high resolution, sharp focus. The product must look exactly the same as in the reference image.`,
+ },
+ {
+ id: "lifestyle-scene",
+ prompt:
+ `Create a lifestyle product photography shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Place this identical product in a modern home setting with natural lighting, styled environment, aspirational lifestyle, professional commercial photography. The product must look exactly the same as in the reference image, only the background and setting should change.`,
+ },
+ {
+ id: "hero-shot",
+ prompt:
+ `Create a hero product shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Present this identical product with dramatic lighting, premium presentation, luxury commercial photography style, perfect for marketing materials, high-end aesthetic. The product must look exactly the same as in the reference image, only the lighting and presentation should be enhanced.`,
+ },
+ ];
+
+ try {
+ // Update progress
+ metadata.set("progress", {
+ step: 2,
+ total: 4,
+ message: "Triggering individual generation tasks...",
+ });
+
+ // Trigger all 3 generation tasks in parallel
+ const generationPromises = prompts.map(async ({ id, prompt }) => {
+ logger.log(`Triggering generation for ${id}`, { prompt });
+
+ try {
+ const result = await generateAndUploadImage.trigger({
+ prompt,
+ baseImageUrl,
+ model: "dall-e-3",
+ size: "1024x1792",
+ });
+
+ return {
+ id,
+ success: true,
+ runId: result.id,
+ prompt,
+ };
+ } catch (error) {
+ logger.error(`Failed to trigger generation for ${id}`, { error });
+ return {
+ id,
+ success: false,
+ error: error instanceof Error ? error.message : "Unknown error",
+ prompt,
+ };
+ }
+ });
+
+ // Wait for all triggers to complete
+ metadata.set("progress", {
+ step: 3,
+ total: 4,
+ message: "All generation tasks triggered, monitoring progress...",
+ });
+
+ const results = await Promise.all(generationPromises);
+
+ // Update progress
+ metadata.set("progress", {
+ step: 4,
+ total: 4,
+ message: "Batch generation initiated successfully!",
+ });
+ metadata.set("status", "completed");
+
+ // Set final metadata with results
+ metadata.set("result", {
+ baseImageUrl,
+ productDescription,
+ generations: results,
+ totalTasks: results.length,
+ successfulTasks: results.filter((r) => r.success).length,
+ failedTasks: results.filter((r) => !r.success).length,
+ });
+
+ logger.log("Batch generation completed", {
+ successful: results.filter((r) => r.success).length,
+ failed: results.filter((r) => !r.success).length,
+ });
+
+ return {
+ success: true,
+ baseImageUrl,
+ productDescription,
+ generations: results,
+ totalTasks: results.length,
+ successfulTasks: results.filter((r) => r.success).length,
+ failedTasks: results.filter((r) => !r.success).length,
+ };
+ } catch (error) {
+ // Set error metadata
+ metadata.set("status", "failed");
+ metadata.set("progress", {
+ step: 0,
+ total: 4,
+ message: "Batch generation failed",
+ });
+ metadata.set(
+ "error",
+ error instanceof Error ? error.message : "Unknown error",
+ );
+
+ logger.error("Failed to complete batch generation", {
+ error,
+ baseImageUrl,
+ });
+ throw error;
+ }
+ },
+});
diff --git a/product-image-generator/src/trigger/batch-generate-images.ts b/product-image-generator/src/trigger/batch-generate-images.ts
new file mode 100644
index 0000000..6f3f52c
--- /dev/null
+++ b/product-image-generator/src/trigger/batch-generate-images.ts
@@ -0,0 +1,143 @@
+import { logger, metadata, task } from "@trigger.dev/sdk";
+import { generateProductImage } from "./generate-image";
+
+export const batchGenerateProductImages = task({
+ id: "batch-generate-product-images",
+ maxDuration: 900, // 15 minutes max for all 3 images
+ run: async (payload: {
+ baseImageUrl: string;
+ productDescription?: string;
+ }) => {
+ const { baseImageUrl, productDescription = "product" } = payload;
+
+ // Set initial metadata
+ metadata.set("status", "starting");
+ metadata.set("progress", {
+ step: 1,
+ total: 4,
+ message: "Preparing batch image generation...",
+ });
+
+ logger.log("Starting batch image generation", {
+ baseImageUrl,
+ productDescription,
+ });
+
+ // Define the 3 marketing prompts - emphasizing exact product replication
+ const prompts = [
+ {
+ id: "isolated-table",
+ prompt:
+ `Create a professional product photography shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Place this identical product isolated on a clean white table surface with studio lighting, minimalist background, commercial photography style, high resolution, sharp focus. The product must look exactly the same as in the reference image.`,
+ },
+ {
+ id: "lifestyle-scene",
+ prompt:
+ `Create a lifestyle product photography shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Place this identical product in a modern home setting with natural lighting, styled environment, aspirational lifestyle, professional commercial photography. The product must look exactly the same as in the reference image, only the background and setting should change.`,
+ },
+ {
+ id: "hero-shot",
+ prompt:
+ `Create a hero product shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Present this identical product with dramatic lighting, premium presentation, luxury commercial photography style, perfect for marketing materials, high-end aesthetic. The product must look exactly the same as in the reference image, only the lighting and presentation should be enhanced.`,
+ },
+ ];
+
+ try {
+ // Update progress
+ metadata.set("progress", {
+ step: 2,
+ total: 4,
+ message: "Triggering image generation tasks...",
+ });
+
+ // Trigger all 3 image generation tasks in parallel
+ const generationPromises = prompts.map(async ({ id, prompt }) => {
+ logger.log(`Triggering generation for ${id}`, { prompt });
+
+ try {
+ const result = await generateProductImage.trigger({
+ prompt,
+ baseImageUrl,
+ model: "dall-e-3",
+ size: "1024x1792", // Portrait format for better slot fitting
+ });
+
+ return {
+ id,
+ success: true,
+ runId: result.id,
+ prompt,
+ };
+ } catch (error) {
+ logger.error(`Failed to trigger generation for ${id}`, { error });
+ return {
+ id,
+ success: false,
+ error: error instanceof Error ? error.message : "Unknown error",
+ prompt,
+ };
+ }
+ });
+
+ // Wait for all triggers to complete
+ metadata.set("progress", {
+ step: 3,
+ total: 4,
+ message: "All generation tasks triggered, monitoring progress...",
+ });
+
+ const results = await Promise.all(generationPromises);
+
+ // Update progress
+ metadata.set("progress", {
+ step: 4,
+ total: 4,
+ message: "Batch generation initiated successfully!",
+ });
+ metadata.set("status", "completed");
+
+ // Set final metadata with results
+ metadata.set("result", {
+ baseImageUrl,
+ productDescription,
+ generations: results,
+ totalTasks: results.length,
+ successfulTasks: results.filter((r) => r.success).length,
+ failedTasks: results.filter((r) => !r.success).length,
+ });
+
+ logger.log("Batch generation completed", {
+ successful: results.filter((r) => r.success).length,
+ failed: results.filter((r) => !r.success).length,
+ });
+
+ return {
+ success: true,
+ baseImageUrl,
+ productDescription,
+ generations: results,
+ totalTasks: results.length,
+ successfulTasks: results.filter((r) => r.success).length,
+ failedTasks: results.filter((r) => !r.success).length,
+ };
+ } catch (error) {
+ // Set error metadata
+ metadata.set("status", "failed");
+ metadata.set("progress", {
+ step: 0,
+ total: 4,
+ message: "Batch generation failed",
+ });
+ metadata.set(
+ "error",
+ error instanceof Error ? error.message : "Unknown error",
+ );
+
+ logger.error("Failed to complete batch generation", {
+ error,
+ baseImageUrl,
+ });
+ throw error;
+ }
+ },
+});
diff --git a/product-image-generator/src/trigger/generate-and-upload-image.ts b/product-image-generator/src/trigger/generate-and-upload-image.ts
new file mode 100644
index 0000000..355e31e
--- /dev/null
+++ b/product-image-generator/src/trigger/generate-and-upload-image.ts
@@ -0,0 +1,169 @@
+import { experimental_generateImage } from "ai";
+import { logger, metadata, task } from "@trigger.dev/sdk";
+import { openai } from "@ai-sdk/openai";
+import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
+import { Buffer } from "buffer";
+
+// Initialize S3 client for R2
+const s3Client = new S3Client({
+ region: "auto",
+ endpoint: process.env.R2_ENDPOINT,
+ credentials: {
+ accessKeyId: process.env.R2_ACCESS_KEY_ID ?? "",
+ secretAccessKey: process.env.R2_SECRET_ACCESS_KEY ?? "",
+ },
+});
+
+export const generateAndUploadImage = task({
+ id: "generate-and-upload-image",
+ maxDuration: 600, // 10 minutes max
+ run: async (payload: {
+ prompt: string;
+ baseImageUrl: string;
+ model?: "dall-e-2" | "dall-e-3";
+ size?: "1024x1024" | "1792x1024" | "1024x1792";
+ }) => {
+ const {
+ prompt,
+ baseImageUrl,
+ model = "dall-e-3",
+ size = "1024x1792",
+ } = payload;
+
+ // Set initial metadata with 5 steps total
+ metadata.set("status", "starting");
+ metadata.set("progress", {
+ step: 1,
+ total: 5,
+ message: "Preparing image generation...",
+ });
+
+ logger.log("Starting image generation and upload", {
+ prompt,
+ baseImageUrl,
+ model,
+ size,
+ });
+
+ try {
+ // Step 2: Generate image
+ metadata.set("progress", {
+ step: 2,
+ total: 5,
+ message: "Generating image with AI...",
+ });
+
+ const generateParams: any = {
+ model: openai.image(model),
+ prompt:
+ `Create a professional marketing image: ${prompt}. Use the product from this reference image: ${baseImageUrl}. Maintain the product's key features and characteristics while adapting it to the new scenario.`,
+ size,
+ };
+
+ const { image } = await experimental_generateImage(generateParams);
+ logger.log("Image generated successfully");
+
+ // Step 3: Prepare for upload
+ metadata.set("progress", {
+ step: 3,
+ total: 5,
+ message: "Preparing image for upload...",
+ });
+
+ const imageBuffer = Buffer.from(image.uint8Array);
+ const base64Image = imageBuffer.toString("base64");
+
+ const timestamp = Date.now();
+ const filename = `generated-${timestamp}.png`;
+
+ // Step 4: Upload to storage
+ metadata.set("progress", {
+ step: 4,
+ total: 5,
+ message: "Uploading to storage...",
+ });
+
+ // Generate unique key for R2
+ const sanitizedFileName = filename.replace(/[^a-zA-Z0-9.-]/g, "_");
+ const r2Key = `uploaded-images/${timestamp}-${sanitizedFileName}`;
+
+ const uploadParams = {
+ Bucket: process.env.R2_BUCKET,
+ Key: r2Key,
+ Body: imageBuffer,
+ ContentType: "image/png",
+ // Add cache control for better performance
+ CacheControl: "public, max-age=31536000", // 1 year
+ };
+
+ // Upload to R2
+ logger.log("About to upload to R2", {
+ bucket: process.env.R2_BUCKET,
+ endpoint: process.env.R2_ENDPOINT,
+ r2Key,
+ fileSize: imageBuffer.length,
+ hasAccessKey: !!process.env.R2_ACCESS_KEY_ID,
+ hasSecretKey: !!process.env.R2_SECRET_ACCESS_KEY,
+ });
+
+ const result = await s3Client.send(new PutObjectCommand(uploadParams));
+ logger.log("S3 PutObject response:", result as any);
+ logger.log(`Image uploaded successfully to R2`, { r2Key });
+
+ // Construct the public URL using the R2_PUBLIC_URL env var
+ const publicUrl = `${process.env.R2_PUBLIC_URL}/${r2Key}`;
+
+ const uploadOutput = {
+ publicUrl,
+ r2Key,
+ fileSize: imageBuffer.length,
+ fileName: sanitizedFileName,
+ };
+
+ // Step 5: Complete
+ metadata.set("progress", {
+ step: 5,
+ total: 5,
+ message: "Generation and upload completed!",
+ });
+ metadata.set("status", "completed");
+
+ // Set final metadata with result
+ metadata.set("result", {
+ prompt,
+ model,
+ size,
+ imageSize: imageBuffer.length,
+ publicUrl: uploadOutput.publicUrl,
+ r2Key: uploadOutput.r2Key,
+ });
+
+ return {
+ success: true,
+ publicUrl: uploadOutput.publicUrl,
+ r2Key: uploadOutput.r2Key,
+ imageSize: imageBuffer.length,
+ contentType: "image/png",
+ model,
+ size,
+ prompt,
+ baseImageUrl,
+ };
+ } catch (error) {
+ // Set error metadata
+ metadata.set("status", "failed");
+ metadata.set("progress", {
+ step: 0,
+ total: 5,
+ message: "Generation and upload failed",
+ });
+ metadata.set(
+ "error",
+ error instanceof Error ? error.message : "Unknown error",
+ );
+
+ logger.error("Failed to generate and upload image", { error, prompt });
+ throw error;
+ }
+ },
+});
diff --git a/product-image-generator/src/trigger/generate-image.ts b/product-image-generator/src/trigger/generate-image.ts
new file mode 100644
index 0000000..207537c
--- /dev/null
+++ b/product-image-generator/src/trigger/generate-image.ts
@@ -0,0 +1,147 @@
+import { experimental_generateImage } from "ai";
+import { logger, metadata, task } from "@trigger.dev/sdk";
+import { openai } from "@ai-sdk/openai";
+import { uploadImageToR2 } from "./image-upload";
+
+export const generateProductImage = task({
+ id: "generate-product-image",
+ maxDuration: 600, // 10 minutes max (includes upload time)
+ run: async (payload: {
+ prompt: string;
+ model?: "dall-e-2" | "dall-e-3";
+ size?: "1024x1024" | "1792x1024" | "1024x1792";
+ baseImageUrl?: string; // For creating derivatives
+ }) => {
+ const {
+ prompt,
+ model = "dall-e-3",
+ size = "1024x1024",
+ baseImageUrl,
+ } = payload;
+
+ // Set initial metadata
+ metadata.set("status", "starting");
+ metadata.set("progress", {
+ step: 1,
+ total: 4,
+ message: "Preparing image generation...",
+ });
+
+ logger.log("Starting image generation", {
+ prompt,
+ model,
+ size,
+ baseImageUrl,
+ isDerivative: !!baseImageUrl,
+ });
+
+ try {
+ // Update progress
+ metadata.set("progress", {
+ step: 2,
+ total: 4,
+ message: "Generating image with AI...",
+ });
+
+ // Generate the image using AI SDK
+ const generateParams: any = {
+ model: openai.image(model),
+ prompt: baseImageUrl
+ ? `${prompt}. Base this on the product shown in this reference image: ${baseImageUrl}`
+ : prompt,
+ size,
+ };
+
+ // Add image reference if provided (for derivatives)
+ if (baseImageUrl) {
+ generateParams.prompt =
+ `Create a professional marketing image: ${prompt}. Use the product from this reference image: ${baseImageUrl}. Maintain the product's key features and characteristics while adapting it to the new scenario.`;
+ }
+
+ const { image } = await experimental_generateImage(generateParams);
+
+ logger.log("Image generated successfully");
+
+ // Update progress
+ metadata.set("progress", {
+ step: 3,
+ total: 4,
+ message: "Uploading generated image...",
+ });
+
+ // Convert the image to base64 and upload to R2
+ const imageBuffer = Buffer.from(image.uint8Array);
+ const base64Image = imageBuffer.toString("base64");
+
+ // Generate a filename for the generated image
+ const timestamp = Date.now();
+ const filename = baseImageUrl
+ ? `generated-derivative-${timestamp}.png`
+ : `generated-${timestamp}.png`;
+
+ // Upload the generated image to R2 and wait for completion
+ const uploadResult = await uploadImageToR2.triggerAndWait({
+ imageBuffer: base64Image,
+ fileName: filename,
+ contentType: "image/png",
+ });
+
+ if (!uploadResult.ok) {
+ throw new Error(`Image upload failed: ${uploadResult.error}`);
+ }
+
+ const uploadOutput = uploadResult.output;
+
+ // Log the upload completion
+ logger.log("Upload task completed successfully");
+
+ // Update final progress
+ metadata.set("progress", {
+ step: 4,
+ total: 4,
+ message: "Generation and upload completed!",
+ });
+ metadata.set("status", "completed");
+
+ // Set final metadata with result
+ metadata.set("result", {
+ prompt,
+ model,
+ size,
+ imageSize: imageBuffer.length,
+ publicUrl: uploadOutput.publicUrl,
+ r2Key: uploadOutput.r2Key,
+ });
+
+ return {
+ success: true,
+ imageBuffer: base64Image,
+ imageSize: imageBuffer.length,
+ contentType: "image/png",
+ publicUrl: uploadOutput.publicUrl,
+ r2Key: uploadOutput.r2Key,
+ // Note: We don't include uploadRunId since we're using triggerAndWait
+ model,
+ size,
+ prompt,
+ baseImageUrl,
+ isDerivative: !!baseImageUrl,
+ };
+ } catch (error) {
+ // Set error metadata
+ metadata.set("status", "failed");
+ metadata.set("progress", {
+ step: 0,
+ total: 4,
+ message: "Generation failed",
+ });
+ metadata.set(
+ "error",
+ error instanceof Error ? error.message : "Unknown error",
+ );
+
+ logger.error("Failed to generate image", { error, prompt });
+ throw error;
+ }
+ },
+});
From 68c220070435f4c581af3355673f5db76f9b46f1 Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Fri, 5 Sep 2025 11:28:47 +0100
Subject: [PATCH 035/232] Images rendering in the first 4 slots
---
.../app/components/GeneratedCard.tsx | 120 +++++++-----------
.../app/components/UploadCard.tsx | 4 +-
.../src/trigger/generate-and-upload-image.ts | 13 +-
.../src/trigger/generate-image.ts | 76 ++++++++---
4 files changed, 114 insertions(+), 99 deletions(-)
diff --git a/product-image-generator/app/components/GeneratedCard.tsx b/product-image-generator/app/components/GeneratedCard.tsx
index dfa13a9..c6c1d19 100644
--- a/product-image-generator/app/components/GeneratedCard.tsx
+++ b/product-image-generator/app/components/GeneratedCard.tsx
@@ -3,8 +3,8 @@
import { Button } from "./ui/button";
import { Card } from "./ui/card";
import { Download, RefreshCw } from "lucide-react";
-import { useState, useEffect } from "react";
-import { runs, configure } from "@trigger.dev/sdk/v3";
+import { useState } from "react";
+import { useRealtimeRun } from "@trigger.dev/react-hooks";
interface GeneratedCardProps {
runId: string | null;
@@ -21,71 +21,50 @@ export default function GeneratedCard({
promptTitle,
onRetry,
}: GeneratedCardProps) {
- const [isLoading, setIsLoading] = useState(false);
- const [error, setError] = useState
(null);
const [generatedImageUrl, setGeneratedImageUrl] = useState(
null
);
- const [generationProgress, setGenerationProgress] = useState("idle");
- const [progressMessage, setProgressMessage] = useState("");
- const [progressStep, setProgressStep] = useState<{
- step: number;
- total: number;
- } | null>(null);
- // Subscribe to run updates when runId and accessToken are available
- useEffect(() => {
- if (!runId || !accessToken) return;
+ // Use the React hook for realtime run subscription
+ const { run, error } = useRealtimeRun(runId || undefined, {
+ accessToken: accessToken || undefined,
+ enabled: !!(runId && accessToken), // Only subscribe if we have both
+ });
- const subscribeToRun = async () => {
- setIsLoading(true);
- setGenerationProgress("generating");
- setError(null);
+ // Extract progress information from run metadata
+ const progressData = run?.metadata?.progress as
+ | {
+ step: number;
+ total: number;
+ message: string;
+ }
+ | undefined;
- try {
- configure({
- secretKey: accessToken,
- });
+ const isLoading = run?.status === "EXECUTING" || run?.status === "QUEUED";
+ const generationProgress =
+ run?.status === "COMPLETED"
+ ? "completed"
+ : run?.status === "FAILED"
+ ? "failed"
+ : run?.status === "EXECUTING"
+ ? "generating"
+ : "idle";
- for await (const run of runs.subscribeToRun(runId)) {
- // Update progress from metadata
- if (run.metadata?.progress) {
- const progress = run.metadata.progress as {
- step: number;
- total: number;
- message: string;
- };
- setProgressMessage(progress.message);
- setProgressStep({ step: progress.step, total: progress.total });
- }
+ // Update generated image URL when run completes
+ if (run?.status === "COMPLETED" && !generatedImageUrl) {
+ // First try to get publicUrl from output
+ let publicUrl = run.output?.publicUrl;
- // Handle completion
- if (run.status === "COMPLETED" && run.output) {
- setGeneratedImageUrl(run.output.publicUrl);
- setGenerationProgress("completed");
- setProgressMessage("Generation completed!");
- setIsLoading(false);
- break;
- } else if (run.status === "FAILED") {
- const errorMsg = run.metadata?.error || "Generation failed";
- setError(
- typeof errorMsg === "string" ? errorMsg : "Generation failed"
- );
- setGenerationProgress("failed");
- setProgressMessage("");
- setIsLoading(false);
- break;
- }
- }
- } catch (err) {
- setError("Failed to get task updates");
- setGenerationProgress("failed");
- setIsLoading(false);
- }
- };
+ // If not in output, try metadata.result (our new pattern)
+ if (!publicUrl && run.metadata?.result) {
+ const result = run.metadata.result as any;
+ publicUrl = result.publicUrl;
+ }
- subscribeToRun();
- }, [runId, accessToken]);
+ if (publicUrl) {
+ setGeneratedImageUrl(publicUrl);
+ }
+ }
const handleDownload = () => {
if (generatedImageUrl) {
@@ -100,11 +79,7 @@ export default function GeneratedCard({
const handleRetry = () => {
if (onRetry) {
- setError(null);
setGeneratedImageUrl(null);
- setGenerationProgress("idle");
- setProgressMessage("");
- setProgressStep(null);
onRetry();
}
};
@@ -144,7 +119,9 @@ export default function GeneratedCard({
{promptTitle}
- {error}
+
+ {error?.message || "Generation failed"}
+
{onRetry && (
@@ -166,24 +143,25 @@ export default function GeneratedCard({
{promptTitle}
- {generationProgress === "generating"
- ? progressMessage || "Generating..."
- : generationProgress === "idle"
- ? "Waiting to start..."
- : "Ready"}
+ {progressData?.message ||
+ (generationProgress === "generating"
+ ? "Generating..."
+ : generationProgress === "idle"
+ ? "Waiting to start..."
+ : "Ready")}
- {progressStep && generationProgress === "generating" && (
+ {progressData && generationProgress === "generating" && (
<>
- Step {progressStep.step} of {progressStep.total}
+ Step {progressData.step} of {progressData.total}
>
)}
diff --git a/product-image-generator/app/components/UploadCard.tsx b/product-image-generator/app/components/UploadCard.tsx
index d49b5d8..b9be662 100644
--- a/product-image-generator/app/components/UploadCard.tsx
+++ b/product-image-generator/app/components/UploadCard.tsx
@@ -32,9 +32,9 @@ export default function UploadCard({ onUploadComplete }: UploadCardProps) {
const subscribeToRun = async () => {
try {
- // Configure the SDK with the access token for client-side authentication
+ // Configure the SDK with the public access token for client-side authentication
configure({
- secretKey: accessToken,
+ accessToken: accessToken,
});
for await (const run of runs.subscribeToRun(runId)) {
diff --git a/product-image-generator/src/trigger/generate-and-upload-image.ts b/product-image-generator/src/trigger/generate-and-upload-image.ts
index 355e31e..1c5dc67 100644
--- a/product-image-generator/src/trigger/generate-and-upload-image.ts
+++ b/product-image-generator/src/trigger/generate-and-upload-image.ts
@@ -128,14 +128,17 @@ export const generateAndUploadImage = task({
});
metadata.set("status", "completed");
- // Set final metadata with result
+ // Set final metadata with result - this is the best practice for Trigger.dev
metadata.set("result", {
- prompt,
- model,
- size,
- imageSize: imageBuffer.length,
+ success: true,
publicUrl: uploadOutput.publicUrl,
r2Key: uploadOutput.r2Key,
+ imageSize: imageBuffer.length,
+ contentType: "image/png",
+ model,
+ size,
+ prompt,
+ baseImageUrl,
});
return {
diff --git a/product-image-generator/src/trigger/generate-image.ts b/product-image-generator/src/trigger/generate-image.ts
index 207537c..018c241 100644
--- a/product-image-generator/src/trigger/generate-image.ts
+++ b/product-image-generator/src/trigger/generate-image.ts
@@ -1,7 +1,18 @@
import { experimental_generateImage } from "ai";
import { logger, metadata, task } from "@trigger.dev/sdk";
import { openai } from "@ai-sdk/openai";
-import { uploadImageToR2 } from "./image-upload";
+import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
+import { Buffer } from "buffer";
+
+// Initialize S3 client for R2
+const s3Client = new S3Client({
+ region: "auto",
+ endpoint: process.env.R2_ENDPOINT,
+ credentials: {
+ accessKeyId: process.env.R2_ACCESS_KEY_ID ?? "",
+ secretAccessKey: process.env.R2_SECRET_ACCESS_KEY ?? "",
+ },
+});
export const generateProductImage = task({
id: "generate-product-image",
@@ -79,21 +90,42 @@ export const generateProductImage = task({
? `generated-derivative-${timestamp}.png`
: `generated-${timestamp}.png`;
- // Upload the generated image to R2 and wait for completion
- const uploadResult = await uploadImageToR2.triggerAndWait({
- imageBuffer: base64Image,
- fileName: filename,
- contentType: "image/png",
+ // Generate unique key for R2
+ const sanitizedFileName = filename.replace(/[^a-zA-Z0-9.-]/g, "_");
+ const r2Key = `uploaded-images/${timestamp}-${sanitizedFileName}`;
+
+ const uploadParams = {
+ Bucket: process.env.R2_BUCKET,
+ Key: r2Key,
+ Body: imageBuffer,
+ ContentType: "image/png",
+ // Add cache control for better performance
+ CacheControl: "public, max-age=31536000", // 1 year
+ };
+
+ // Upload to R2
+ logger.log("About to upload to R2", {
+ bucket: process.env.R2_BUCKET,
+ endpoint: process.env.R2_ENDPOINT,
+ r2Key,
+ fileSize: imageBuffer.length,
+ hasAccessKey: !!process.env.R2_ACCESS_KEY_ID,
+ hasSecretKey: !!process.env.R2_SECRET_ACCESS_KEY,
});
- if (!uploadResult.ok) {
- throw new Error(`Image upload failed: ${uploadResult.error}`);
- }
+ const result = await s3Client.send(new PutObjectCommand(uploadParams));
+ logger.log("S3 PutObject response:", result as any);
+ logger.log(`Image uploaded successfully to R2`, { r2Key });
- const uploadOutput = uploadResult.output;
+ // Construct the public URL using the R2_PUBLIC_URL env var
+ const publicUrl = `${process.env.R2_PUBLIC_URL}/${r2Key}`;
- // Log the upload completion
- logger.log("Upload task completed successfully");
+ const uploadOutput = {
+ publicUrl,
+ r2Key,
+ fileSize: imageBuffer.length,
+ fileName: sanitizedFileName,
+ };
// Update final progress
metadata.set("progress", {
@@ -103,24 +135,26 @@ export const generateProductImage = task({
});
metadata.set("status", "completed");
- // Set final metadata with result
+ // Set final metadata with result - this is the best practice for Trigger.dev
metadata.set("result", {
- prompt,
- model,
- size,
- imageSize: imageBuffer.length,
+ success: true,
publicUrl: uploadOutput.publicUrl,
r2Key: uploadOutput.r2Key,
+ imageSize: imageBuffer.length,
+ contentType: "image/png",
+ model: model as string,
+ size: size as string,
+ prompt,
+ baseImageUrl: baseImageUrl || null,
+ isDerivative: !!baseImageUrl,
});
return {
success: true,
- imageBuffer: base64Image,
- imageSize: imageBuffer.length,
- contentType: "image/png",
publicUrl: uploadOutput.publicUrl,
r2Key: uploadOutput.r2Key,
- // Note: We don't include uploadRunId since we're using triggerAndWait
+ imageSize: imageBuffer.length,
+ contentType: "image/png",
model,
size,
prompt,
From 5e552daeb75a4a759c863c2f3c7d359539c70fb9 Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Fri, 5 Sep 2025 12:16:04 +0100
Subject: [PATCH 036/232] Use flux plus other improvements
---
product-image-generator/app/actions.ts | 41 +---
.../app/components/GeneratedCard.tsx | 34 +++-
product-image-generator/package.json | 1 +
product-image-generator/pnpm-lock.yaml | 15 ++
.../src/trigger/batch-generate-and-upload.ts | 143 --------------
.../src/trigger/batch-generate-images.ts | 143 --------------
.../src/trigger/generate-and-upload-image.ts | 30 ++-
.../src/trigger/generate-image.ts | 181 ------------------
8 files changed, 66 insertions(+), 522 deletions(-)
delete mode 100644 product-image-generator/src/trigger/batch-generate-and-upload.ts
delete mode 100644 product-image-generator/src/trigger/batch-generate-images.ts
delete mode 100644 product-image-generator/src/trigger/generate-image.ts
diff --git a/product-image-generator/app/actions.ts b/product-image-generator/app/actions.ts
index bd52367..f632a07 100644
--- a/product-image-generator/app/actions.ts
+++ b/product-image-generator/app/actions.ts
@@ -2,7 +2,6 @@
import { auth, tasks } from "@trigger.dev/sdk/v3";
import type { uploadImageToR2 } from "../src/trigger/image-upload";
-import type { batchGenerateAndUploadImages } from "../src/trigger/batch-generate-and-upload";
import type { generateAndUploadImage } from "../src/trigger/generate-and-upload-image";
export async function createPublicAccessToken(runId: string) {
@@ -76,42 +75,6 @@ export async function uploadImageToR2Action(formData: FormData) {
}
}
-export async function batchGenerateImagesAction(
- baseImageUrl: string,
- productDescription?: string,
-) {
- try {
- const handle = await tasks.trigger(
- "batch-generate-and-upload-images",
- {
- baseImageUrl,
- productDescription,
- },
- );
-
- // Create a public access token for this specific run
- const tokenResult = await createPublicAccessToken(handle.id);
-
- if (!tokenResult.success) {
- return {
- success: false as const,
- error: tokenResult.error,
- };
- }
-
- return {
- success: true as const,
- runId: handle.id,
- accessToken: tokenResult.token,
- };
- } catch (error) {
- console.error("Failed to trigger batch generation:", error);
- return {
- success: false as const,
- error: "Failed to trigger batch generation",
- };
- }
-}
export async function generateSingleImageAction(
baseImageUrl: string,
@@ -142,8 +105,8 @@ export async function generateSingleImageAction(
{
prompt,
baseImageUrl,
- model: "dall-e-3",
- size: "1024x1792", // Portrait format for better slot fitting
+ model: "flux", // Use Flux with your settings
+ size: "1024x1024", // Square format from your settings
},
);
diff --git a/product-image-generator/app/components/GeneratedCard.tsx b/product-image-generator/app/components/GeneratedCard.tsx
index c6c1d19..0a0a342 100644
--- a/product-image-generator/app/components/GeneratedCard.tsx
+++ b/product-image-generator/app/components/GeneratedCard.tsx
@@ -2,7 +2,7 @@
import { Button } from "./ui/button";
import { Card } from "./ui/card";
-import { Download, RefreshCw } from "lucide-react";
+import { Download, RefreshCw, Expand } from "lucide-react";
import { useState } from "react";
import { useRealtimeRun } from "@trigger.dev/react-hooks";
@@ -77,6 +77,12 @@ export default function GeneratedCard({
}
};
+ const handleExpand = () => {
+ if (generatedImageUrl) {
+ window.open(generatedImageUrl, "_blank");
+ }
+ };
+
const handleRetry = () => {
if (onRetry) {
setGeneratedImageUrl(null);
@@ -85,29 +91,39 @@ export default function GeneratedCard({
};
return (
-
+
{generatedImageUrl ? (
// Show generated image
-
+
- {/* Download button */}
-
+ {/* Action buttons */}
+
+
+
+
{/* Title overlay */}
-
) : generationProgress === "failed" ? (
diff --git a/product-image-generator/package.json b/product-image-generator/package.json
index 07decbe..d2b2edb 100644
--- a/product-image-generator/package.json
+++ b/product-image-generator/package.json
@@ -10,6 +10,7 @@
},
"dependencies": {
"@ai-sdk/openai": "^2.0.24",
+ "@ai-sdk/replicate": "^1.0.8",
"@aws-sdk/client-s3": "^3.882.0",
"@hookform/resolvers": "^3.10.0",
"@radix-ui/react-accordion": "1.2.2",
diff --git a/product-image-generator/pnpm-lock.yaml b/product-image-generator/pnpm-lock.yaml
index bf6f9ab..9e318ab 100644
--- a/product-image-generator/pnpm-lock.yaml
+++ b/product-image-generator/pnpm-lock.yaml
@@ -11,6 +11,9 @@ importers:
'@ai-sdk/openai':
specifier: ^2.0.24
version: 2.0.24(zod@3.25.67)
+ '@ai-sdk/replicate':
+ specifier: ^1.0.8
+ version: 1.0.8(zod@3.25.67)
'@aws-sdk/client-s3':
specifier: ^3.882.0
version: 3.882.0
@@ -226,6 +229,12 @@ packages:
resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==}
engines: {node: '>=18'}
+ '@ai-sdk/replicate@1.0.8':
+ resolution: {integrity: sha512-lCisqVAZt/x1HmVhVbKlLa73f69+yC+P9QmRIKbXaZb1kqnL27urTehEpSLsT+iungmWOsMY3Xr4PXhNf/8FwQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4
+
'@alloc/quick-lru@5.2.0':
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
@@ -2562,6 +2571,12 @@ snapshots:
dependencies:
json-schema: 0.4.0
+ '@ai-sdk/replicate@1.0.8(zod@3.25.67)':
+ dependencies:
+ '@ai-sdk/provider': 2.0.0
+ '@ai-sdk/provider-utils': 3.0.8(zod@3.25.67)
+ zod: 3.25.67
+
'@alloc/quick-lru@5.2.0': {}
'@aws-crypto/crc32@5.2.0':
diff --git a/product-image-generator/src/trigger/batch-generate-and-upload.ts b/product-image-generator/src/trigger/batch-generate-and-upload.ts
deleted file mode 100644
index ec8e502..0000000
--- a/product-image-generator/src/trigger/batch-generate-and-upload.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-import { logger, metadata, task } from "@trigger.dev/sdk";
-import { generateAndUploadImage } from "./generate-and-upload-image";
-
-export const batchGenerateAndUploadImages = task({
- id: "batch-generate-and-upload-images",
- maxDuration: 900, // 15 minutes max for all 3 images
- run: async (payload: {
- baseImageUrl: string;
- productDescription?: string;
- }) => {
- const { baseImageUrl, productDescription = "product" } = payload;
-
- // Set initial metadata
- metadata.set("status", "starting");
- metadata.set("progress", {
- step: 1,
- total: 4,
- message: "Preparing batch generation...",
- });
-
- logger.log("Starting batch image generation and upload", {
- baseImageUrl,
- productDescription,
- });
-
- // Define the 3 marketing prompts - emphasizing exact product replication
- const prompts = [
- {
- id: "isolated-table",
- prompt:
- `Create a professional product photography shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Place this identical product isolated on a clean white table surface with studio lighting, minimalist background, commercial photography style, high resolution, sharp focus. The product must look exactly the same as in the reference image.`,
- },
- {
- id: "lifestyle-scene",
- prompt:
- `Create a lifestyle product photography shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Place this identical product in a modern home setting with natural lighting, styled environment, aspirational lifestyle, professional commercial photography. The product must look exactly the same as in the reference image, only the background and setting should change.`,
- },
- {
- id: "hero-shot",
- prompt:
- `Create a hero product shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Present this identical product with dramatic lighting, premium presentation, luxury commercial photography style, perfect for marketing materials, high-end aesthetic. The product must look exactly the same as in the reference image, only the lighting and presentation should be enhanced.`,
- },
- ];
-
- try {
- // Update progress
- metadata.set("progress", {
- step: 2,
- total: 4,
- message: "Triggering individual generation tasks...",
- });
-
- // Trigger all 3 generation tasks in parallel
- const generationPromises = prompts.map(async ({ id, prompt }) => {
- logger.log(`Triggering generation for ${id}`, { prompt });
-
- try {
- const result = await generateAndUploadImage.trigger({
- prompt,
- baseImageUrl,
- model: "dall-e-3",
- size: "1024x1792",
- });
-
- return {
- id,
- success: true,
- runId: result.id,
- prompt,
- };
- } catch (error) {
- logger.error(`Failed to trigger generation for ${id}`, { error });
- return {
- id,
- success: false,
- error: error instanceof Error ? error.message : "Unknown error",
- prompt,
- };
- }
- });
-
- // Wait for all triggers to complete
- metadata.set("progress", {
- step: 3,
- total: 4,
- message: "All generation tasks triggered, monitoring progress...",
- });
-
- const results = await Promise.all(generationPromises);
-
- // Update progress
- metadata.set("progress", {
- step: 4,
- total: 4,
- message: "Batch generation initiated successfully!",
- });
- metadata.set("status", "completed");
-
- // Set final metadata with results
- metadata.set("result", {
- baseImageUrl,
- productDescription,
- generations: results,
- totalTasks: results.length,
- successfulTasks: results.filter((r) => r.success).length,
- failedTasks: results.filter((r) => !r.success).length,
- });
-
- logger.log("Batch generation completed", {
- successful: results.filter((r) => r.success).length,
- failed: results.filter((r) => !r.success).length,
- });
-
- return {
- success: true,
- baseImageUrl,
- productDescription,
- generations: results,
- totalTasks: results.length,
- successfulTasks: results.filter((r) => r.success).length,
- failedTasks: results.filter((r) => !r.success).length,
- };
- } catch (error) {
- // Set error metadata
- metadata.set("status", "failed");
- metadata.set("progress", {
- step: 0,
- total: 4,
- message: "Batch generation failed",
- });
- metadata.set(
- "error",
- error instanceof Error ? error.message : "Unknown error",
- );
-
- logger.error("Failed to complete batch generation", {
- error,
- baseImageUrl,
- });
- throw error;
- }
- },
-});
diff --git a/product-image-generator/src/trigger/batch-generate-images.ts b/product-image-generator/src/trigger/batch-generate-images.ts
deleted file mode 100644
index 6f3f52c..0000000
--- a/product-image-generator/src/trigger/batch-generate-images.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-import { logger, metadata, task } from "@trigger.dev/sdk";
-import { generateProductImage } from "./generate-image";
-
-export const batchGenerateProductImages = task({
- id: "batch-generate-product-images",
- maxDuration: 900, // 15 minutes max for all 3 images
- run: async (payload: {
- baseImageUrl: string;
- productDescription?: string;
- }) => {
- const { baseImageUrl, productDescription = "product" } = payload;
-
- // Set initial metadata
- metadata.set("status", "starting");
- metadata.set("progress", {
- step: 1,
- total: 4,
- message: "Preparing batch image generation...",
- });
-
- logger.log("Starting batch image generation", {
- baseImageUrl,
- productDescription,
- });
-
- // Define the 3 marketing prompts - emphasizing exact product replication
- const prompts = [
- {
- id: "isolated-table",
- prompt:
- `Create a professional product photography shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Place this identical product isolated on a clean white table surface with studio lighting, minimalist background, commercial photography style, high resolution, sharp focus. The product must look exactly the same as in the reference image.`,
- },
- {
- id: "lifestyle-scene",
- prompt:
- `Create a lifestyle product photography shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Place this identical product in a modern home setting with natural lighting, styled environment, aspirational lifestyle, professional commercial photography. The product must look exactly the same as in the reference image, only the background and setting should change.`,
- },
- {
- id: "hero-shot",
- prompt:
- `Create a hero product shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Present this identical product with dramatic lighting, premium presentation, luxury commercial photography style, perfect for marketing materials, high-end aesthetic. The product must look exactly the same as in the reference image, only the lighting and presentation should be enhanced.`,
- },
- ];
-
- try {
- // Update progress
- metadata.set("progress", {
- step: 2,
- total: 4,
- message: "Triggering image generation tasks...",
- });
-
- // Trigger all 3 image generation tasks in parallel
- const generationPromises = prompts.map(async ({ id, prompt }) => {
- logger.log(`Triggering generation for ${id}`, { prompt });
-
- try {
- const result = await generateProductImage.trigger({
- prompt,
- baseImageUrl,
- model: "dall-e-3",
- size: "1024x1792", // Portrait format for better slot fitting
- });
-
- return {
- id,
- success: true,
- runId: result.id,
- prompt,
- };
- } catch (error) {
- logger.error(`Failed to trigger generation for ${id}`, { error });
- return {
- id,
- success: false,
- error: error instanceof Error ? error.message : "Unknown error",
- prompt,
- };
- }
- });
-
- // Wait for all triggers to complete
- metadata.set("progress", {
- step: 3,
- total: 4,
- message: "All generation tasks triggered, monitoring progress...",
- });
-
- const results = await Promise.all(generationPromises);
-
- // Update progress
- metadata.set("progress", {
- step: 4,
- total: 4,
- message: "Batch generation initiated successfully!",
- });
- metadata.set("status", "completed");
-
- // Set final metadata with results
- metadata.set("result", {
- baseImageUrl,
- productDescription,
- generations: results,
- totalTasks: results.length,
- successfulTasks: results.filter((r) => r.success).length,
- failedTasks: results.filter((r) => !r.success).length,
- });
-
- logger.log("Batch generation completed", {
- successful: results.filter((r) => r.success).length,
- failed: results.filter((r) => !r.success).length,
- });
-
- return {
- success: true,
- baseImageUrl,
- productDescription,
- generations: results,
- totalTasks: results.length,
- successfulTasks: results.filter((r) => r.success).length,
- failedTasks: results.filter((r) => !r.success).length,
- };
- } catch (error) {
- // Set error metadata
- metadata.set("status", "failed");
- metadata.set("progress", {
- step: 0,
- total: 4,
- message: "Batch generation failed",
- });
- metadata.set(
- "error",
- error instanceof Error ? error.message : "Unknown error",
- );
-
- logger.error("Failed to complete batch generation", {
- error,
- baseImageUrl,
- });
- throw error;
- }
- },
-});
diff --git a/product-image-generator/src/trigger/generate-and-upload-image.ts b/product-image-generator/src/trigger/generate-and-upload-image.ts
index 1c5dc67..9f6323c 100644
--- a/product-image-generator/src/trigger/generate-and-upload-image.ts
+++ b/product-image-generator/src/trigger/generate-and-upload-image.ts
@@ -1,6 +1,7 @@
import { experimental_generateImage } from "ai";
import { logger, metadata, task } from "@trigger.dev/sdk";
import { openai } from "@ai-sdk/openai";
+import { replicate } from "@ai-sdk/replicate";
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { Buffer } from "buffer";
@@ -20,14 +21,22 @@ export const generateAndUploadImage = task({
run: async (payload: {
prompt: string;
baseImageUrl: string;
- model?: "dall-e-2" | "dall-e-3";
+ model?: "flux" | "dall-e-3";
size?: "1024x1024" | "1792x1024" | "1024x1792";
+ strength?: number;
+ guidance?: number;
+ steps?: number;
+ seed?: number;
}) => {
const {
prompt,
baseImageUrl,
- model = "dall-e-3",
- size = "1024x1792",
+ model = "flux",
+ size = "1024x1024", // Match your settings
+ strength = 0.7,
+ guidance = 7, // From your settings
+ steps = 30, // From your settings
+ seed = 1601, // From your settings
} = payload;
// Set initial metadata with 5 steps total
@@ -53,11 +62,18 @@ export const generateAndUploadImage = task({
message: "Generating image with AI...",
});
+ // Use Flux with your exact settings from the screenshot
const generateParams: any = {
- model: openai.image(model),
- prompt:
- `Create a professional marketing image: ${prompt}. Use the product from this reference image: ${baseImageUrl}. Maintain the product's key features and characteristics while adapting it to the new scenario.`,
- size,
+ model: replicate.image("black-forest-labs/flux-dev"),
+ prompt: prompt,
+ image: baseImageUrl, // Reference image for img2img
+ width: 1024, // From your settings
+ height: 1024, // From your settings
+ guidance_scale: 7, // From your settings
+ num_inference_steps: 30, // From your settings
+ strength: 0.7, // Good balance for product preservation
+ seed: 1601, // From your settings
+ num_outputs: 1,
};
const { image } = await experimental_generateImage(generateParams);
diff --git a/product-image-generator/src/trigger/generate-image.ts b/product-image-generator/src/trigger/generate-image.ts
deleted file mode 100644
index 018c241..0000000
--- a/product-image-generator/src/trigger/generate-image.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-import { experimental_generateImage } from "ai";
-import { logger, metadata, task } from "@trigger.dev/sdk";
-import { openai } from "@ai-sdk/openai";
-import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
-import { Buffer } from "buffer";
-
-// Initialize S3 client for R2
-const s3Client = new S3Client({
- region: "auto",
- endpoint: process.env.R2_ENDPOINT,
- credentials: {
- accessKeyId: process.env.R2_ACCESS_KEY_ID ?? "",
- secretAccessKey: process.env.R2_SECRET_ACCESS_KEY ?? "",
- },
-});
-
-export const generateProductImage = task({
- id: "generate-product-image",
- maxDuration: 600, // 10 minutes max (includes upload time)
- run: async (payload: {
- prompt: string;
- model?: "dall-e-2" | "dall-e-3";
- size?: "1024x1024" | "1792x1024" | "1024x1792";
- baseImageUrl?: string; // For creating derivatives
- }) => {
- const {
- prompt,
- model = "dall-e-3",
- size = "1024x1024",
- baseImageUrl,
- } = payload;
-
- // Set initial metadata
- metadata.set("status", "starting");
- metadata.set("progress", {
- step: 1,
- total: 4,
- message: "Preparing image generation...",
- });
-
- logger.log("Starting image generation", {
- prompt,
- model,
- size,
- baseImageUrl,
- isDerivative: !!baseImageUrl,
- });
-
- try {
- // Update progress
- metadata.set("progress", {
- step: 2,
- total: 4,
- message: "Generating image with AI...",
- });
-
- // Generate the image using AI SDK
- const generateParams: any = {
- model: openai.image(model),
- prompt: baseImageUrl
- ? `${prompt}. Base this on the product shown in this reference image: ${baseImageUrl}`
- : prompt,
- size,
- };
-
- // Add image reference if provided (for derivatives)
- if (baseImageUrl) {
- generateParams.prompt =
- `Create a professional marketing image: ${prompt}. Use the product from this reference image: ${baseImageUrl}. Maintain the product's key features and characteristics while adapting it to the new scenario.`;
- }
-
- const { image } = await experimental_generateImage(generateParams);
-
- logger.log("Image generated successfully");
-
- // Update progress
- metadata.set("progress", {
- step: 3,
- total: 4,
- message: "Uploading generated image...",
- });
-
- // Convert the image to base64 and upload to R2
- const imageBuffer = Buffer.from(image.uint8Array);
- const base64Image = imageBuffer.toString("base64");
-
- // Generate a filename for the generated image
- const timestamp = Date.now();
- const filename = baseImageUrl
- ? `generated-derivative-${timestamp}.png`
- : `generated-${timestamp}.png`;
-
- // Generate unique key for R2
- const sanitizedFileName = filename.replace(/[^a-zA-Z0-9.-]/g, "_");
- const r2Key = `uploaded-images/${timestamp}-${sanitizedFileName}`;
-
- const uploadParams = {
- Bucket: process.env.R2_BUCKET,
- Key: r2Key,
- Body: imageBuffer,
- ContentType: "image/png",
- // Add cache control for better performance
- CacheControl: "public, max-age=31536000", // 1 year
- };
-
- // Upload to R2
- logger.log("About to upload to R2", {
- bucket: process.env.R2_BUCKET,
- endpoint: process.env.R2_ENDPOINT,
- r2Key,
- fileSize: imageBuffer.length,
- hasAccessKey: !!process.env.R2_ACCESS_KEY_ID,
- hasSecretKey: !!process.env.R2_SECRET_ACCESS_KEY,
- });
-
- const result = await s3Client.send(new PutObjectCommand(uploadParams));
- logger.log("S3 PutObject response:", result as any);
- logger.log(`Image uploaded successfully to R2`, { r2Key });
-
- // Construct the public URL using the R2_PUBLIC_URL env var
- const publicUrl = `${process.env.R2_PUBLIC_URL}/${r2Key}`;
-
- const uploadOutput = {
- publicUrl,
- r2Key,
- fileSize: imageBuffer.length,
- fileName: sanitizedFileName,
- };
-
- // Update final progress
- metadata.set("progress", {
- step: 4,
- total: 4,
- message: "Generation and upload completed!",
- });
- metadata.set("status", "completed");
-
- // Set final metadata with result - this is the best practice for Trigger.dev
- metadata.set("result", {
- success: true,
- publicUrl: uploadOutput.publicUrl,
- r2Key: uploadOutput.r2Key,
- imageSize: imageBuffer.length,
- contentType: "image/png",
- model: model as string,
- size: size as string,
- prompt,
- baseImageUrl: baseImageUrl || null,
- isDerivative: !!baseImageUrl,
- });
-
- return {
- success: true,
- publicUrl: uploadOutput.publicUrl,
- r2Key: uploadOutput.r2Key,
- imageSize: imageBuffer.length,
- contentType: "image/png",
- model,
- size,
- prompt,
- baseImageUrl,
- isDerivative: !!baseImageUrl,
- };
- } catch (error) {
- // Set error metadata
- metadata.set("status", "failed");
- metadata.set("progress", {
- step: 0,
- total: 4,
- message: "Generation failed",
- });
- metadata.set(
- "error",
- error instanceof Error ? error.message : "Unknown error",
- );
-
- logger.error("Failed to generate image", { error, prompt });
- throw error;
- }
- },
-});
From 3e0906c7c308fa65aa01444b310231882f59ad34 Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Fri, 5 Sep 2025 13:33:53 +0100
Subject: [PATCH 037/232] Working nicely
---
product-image-generator/app/actions.ts | 28 +----
.../app/components/UploadCard.tsx | 11 +-
product-image-generator/app/page.tsx | 28 +++--
product-image-generator/package.json | 1 +
product-image-generator/pnpm-lock.yaml | 20 +++
.../src/trigger/generate-and-upload-image.ts | 119 ++++++++++++------
.../src/trigger/image-upload.ts | 112 ++++++++++++++---
7 files changed, 232 insertions(+), 87 deletions(-)
diff --git a/product-image-generator/app/actions.ts b/product-image-generator/app/actions.ts
index f632a07..bcba8f7 100644
--- a/product-image-generator/app/actions.ts
+++ b/product-image-generator/app/actions.ts
@@ -75,38 +75,20 @@ export async function uploadImageToR2Action(formData: FormData) {
}
}
-
export async function generateSingleImageAction(
baseImageUrl: string,
+ productAnalysis: any, // Structured analysis from upload task
promptId: string,
- productDescription: string = "product",
) {
try {
- // Define the prompts - emphasizing exact product replication
- const prompts = {
- "isolated-table":
- `Create a professional product photography shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Place this identical product isolated on a clean white table surface with studio lighting, minimalist background, commercial photography style, high resolution, sharp focus. The product must look exactly the same as in the reference image.`,
- "lifestyle-scene":
- `Create a lifestyle product photography shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Place this identical product in a modern home setting with natural lighting, styled environment, aspirational lifestyle, professional commercial photography. The product must look exactly the same as in the reference image, only the background and setting should change.`,
- "hero-shot":
- `Create a hero product shot showing the EXACT same product from the reference image, maintaining identical colors, textures, materials, and design details. Present this identical product with dramatic lighting, premium presentation, luxury commercial photography style, perfect for marketing materials, high-end aesthetic. The product must look exactly the same as in the reference image, only the lighting and presentation should be enhanced.`,
- };
-
- const prompt = prompts[promptId as keyof typeof prompts];
- if (!prompt) {
- return {
- success: false as const,
- error: "Invalid prompt ID",
- };
- }
-
const handle = await tasks.trigger
(
"generate-and-upload-image",
{
- prompt,
+ promptStyle: promptId, // isolated-table, lifestyle-scene, hero-shot
baseImageUrl,
- model: "flux", // Use Flux with your settings
- size: "1024x1024", // Square format from your settings
+ productAnalysis,
+ model: "flux",
+ size: "1024x1024",
},
);
diff --git a/product-image-generator/app/components/UploadCard.tsx b/product-image-generator/app/components/UploadCard.tsx
index b9be662..d0c2388 100644
--- a/product-image-generator/app/components/UploadCard.tsx
+++ b/product-image-generator/app/components/UploadCard.tsx
@@ -8,7 +8,7 @@ import { uploadImageToR2Action } from "../actions";
import { runs, configure } from "@trigger.dev/sdk/v3";
interface UploadCardProps {
- onUploadComplete?: (imageUrl: string) => void;
+ onUploadComplete?: (imageUrl: string, productAnalysis?: any) => void;
}
export default function UploadCard({ onUploadComplete }: UploadCardProps) {
@@ -56,9 +56,12 @@ export default function UploadCard({ onUploadComplete }: UploadCardProps) {
setProgressMessage("Upload completed!");
setIsLoading(false);
- // Notify parent component
+ // Notify parent component with URL and analysis
if (onUploadComplete && run.output.publicUrl) {
- onUploadComplete(run.output.publicUrl);
+ const productAnalysis =
+ run.output.productAnalysis ||
+ run.metadata?.result?.productAnalysis;
+ onUploadComplete(run.output.publicUrl, productAnalysis);
}
break;
} else if (run.status === "FAILED") {
@@ -170,7 +173,7 @@ export default function UploadCard({ onUploadComplete }: UploadCardProps) {
(null);
+ const [productAnalysis, setProductAnalysis] = useState(null);
const [generationRunIds, setGenerationRunIds] = useState<{
[key: string]: string | null;
}>({
@@ -31,10 +32,17 @@ export default function ImageManagementApp() {
"hero-shot": "Hero Shot",
};
- const handleUploadComplete = async (imageUrl: string) => {
+ const handleUploadComplete = async (imageUrl: string, analysis?: any) => {
try {
- // Store the uploaded image URL for retries
+ // Store the uploaded image URL and analysis for retries
setUploadedImageUrl(imageUrl);
+ setProductAnalysis(analysis);
+
+ // Only trigger generations if we have product analysis
+ if (!analysis) {
+ console.error("No product analysis available");
+ return;
+ }
// Trigger all 3 generations individually for better progress tracking
const promptIds = ["isolated-table", "lifestyle-scene", "hero-shot"];
@@ -42,8 +50,8 @@ export default function ImageManagementApp() {
for (const promptId of promptIds) {
const result = await generateSingleImageAction(
imageUrl,
- promptId,
- "product"
+ analysis,
+ promptId
);
if (result.success) {
@@ -72,8 +80,10 @@ export default function ImageManagementApp() {
};
const handleRetryGeneration = async (promptId: string) => {
- if (!uploadedImageUrl) {
- console.error("No base image URL available for retry");
+ if (!uploadedImageUrl || !productAnalysis) {
+ console.error(
+ "No base image URL or product analysis available for retry"
+ );
return;
}
@@ -82,11 +92,11 @@ export default function ImageManagementApp() {
setGenerationRunIds((prev) => ({ ...prev, [promptId]: null }));
setGenerationAccessTokens((prev) => ({ ...prev, [promptId]: null }));
- // Trigger the specific generation again
+ // Trigger the specific generation again with product analysis
const result = await generateSingleImageAction(
uploadedImageUrl,
- promptId,
- "product"
+ productAnalysis,
+ promptId
);
if (result.success) {
diff --git a/product-image-generator/package.json b/product-image-generator/package.json
index d2b2edb..0504a89 100644
--- a/product-image-generator/package.json
+++ b/product-image-generator/package.json
@@ -55,6 +55,7 @@
"lucide-react": "^0.454.0",
"next": "14.2.25",
"next-themes": "^0.4.6",
+ "openai": "^5.19.1",
"react": "^19",
"react-day-picker": "9.8.0",
"react-dom": "^19",
diff --git a/product-image-generator/pnpm-lock.yaml b/product-image-generator/pnpm-lock.yaml
index 9e318ab..2f34c66 100644
--- a/product-image-generator/pnpm-lock.yaml
+++ b/product-image-generator/pnpm-lock.yaml
@@ -146,6 +146,9 @@ importers:
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ openai:
+ specifier: ^5.19.1
+ version: 5.19.1(ws@8.18.3)(zod@3.25.67)
react:
specifier: ^19
version: 19.1.0
@@ -2175,6 +2178,18 @@ packages:
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
engines: {node: '>=12'}
+ openai@5.19.1:
+ resolution: {integrity: sha512-zSqnUF7oR9ksmpusKkpUgkNrj8Sl57U+OyzO8jzc7LUjTMg4DRfR3uCm+EIMA6iw06sRPNp4t7ojp3sCpEUZRQ==}
+ hasBin: true
+ peerDependencies:
+ ws: ^8.18.0
+ zod: ^3.23.8
+ peerDependenciesMeta:
+ ws:
+ optional: true
+ zod:
+ optional: true
+
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
@@ -4925,6 +4940,11 @@ snapshots:
dependencies:
mimic-fn: 4.0.0
+ openai@5.19.1(ws@8.18.3)(zod@3.25.67):
+ optionalDependencies:
+ ws: 8.18.3
+ zod: 3.25.67
+
path-key@3.1.1: {}
path-key@4.0.0: {}
diff --git a/product-image-generator/src/trigger/generate-and-upload-image.ts b/product-image-generator/src/trigger/generate-and-upload-image.ts
index 9f6323c..870994e 100644
--- a/product-image-generator/src/trigger/generate-and-upload-image.ts
+++ b/product-image-generator/src/trigger/generate-and-upload-image.ts
@@ -19,9 +19,20 @@ export const generateAndUploadImage = task({
id: "generate-and-upload-image",
maxDuration: 600, // 10 minutes max
run: async (payload: {
- prompt: string;
+ promptStyle: string; // Style prompt (table-shot, lifestyle, hero)
baseImageUrl: string;
- model?: "flux" | "dall-e-3";
+ productAnalysis: {
+ material: string;
+ colors: string[];
+ shape: string;
+ size_proportions: string;
+ functional_elements: string[];
+ surface_finish: string;
+ text_branding: string;
+ unique_features: string[];
+ product_category: string;
+ };
+ model?: "flux";
size?: "1024x1024" | "1792x1024" | "1024x1792";
strength?: number;
guidance?: number;
@@ -29,61 +40,99 @@ export const generateAndUploadImage = task({
seed?: number;
}) => {
const {
- prompt,
+ promptStyle,
baseImageUrl,
+ productAnalysis,
model = "flux",
- size = "1024x1024", // Match your settings
+ size = "1024x1024",
strength = 0.7,
guidance = 7, // From your settings
steps = 30, // From your settings
- seed = 1601, // From your settings
+ seed = Math.floor(Math.random() * 1000000), // Random seed for variety
} = payload;
- // Set initial metadata with 5 steps total
+ // Set initial metadata with 4 steps (no analysis needed)
metadata.set("status", "starting");
metadata.set("progress", {
step: 1,
- total: 5,
+ total: 4,
message: "Preparing image generation...",
});
logger.log("Starting image generation and upload", {
- prompt,
+ promptStyle,
baseImageUrl,
+ productAnalysis,
model,
size,
});
try {
- // Step 2: Generate image
+ // Step 2: Create structured prompt
metadata.set("progress", {
step: 2,
- total: 5,
- message: "Generating image with AI...",
+ total: 4,
+ message: "Creating enhanced prompt...",
});
- // Use Flux with your exact settings from the screenshot
+ // Build detailed product description from analysis with fallbacks
+ const productDetails = [
+ `Material: ${productAnalysis?.material || "unknown"}`,
+ `Colors: ${productAnalysis?.colors?.join(", ") || "unknown"}`,
+ `Shape: ${productAnalysis?.shape || "unknown"}`,
+ `Proportions: ${productAnalysis?.size_proportions || "standard"}`,
+ `Functional elements: ${
+ productAnalysis?.functional_elements?.join(", ") || "standard"
+ }`,
+ `Surface finish: ${productAnalysis?.surface_finish || "standard"}`,
+ `Text/branding: ${productAnalysis?.text_branding || "none"}`,
+ `Unique features: ${
+ productAnalysis?.unique_features?.join(", ") || "standard"
+ }`,
+ ].join(". ");
+
+ // Style-specific prompts
+ const stylePrompts = {
+ "isolated-table":
+ `Professional product photography on clean white table with studio lighting, minimalist background, commercial style`,
+ "lifestyle-scene":
+ `Lifestyle product photography in modern home setting with natural lighting, styled environment, aspirational setting`,
+ "hero-shot":
+ `Premium hero shot with dramatic lighting, luxury commercial photography style, perfect for marketing materials`,
+ };
+
+ const baseStylePrompt =
+ stylePrompts[promptStyle as keyof typeof stylePrompts] ||
+ stylePrompts["isolated-table"];
+
+ // Combine everything into one unambiguous prompt
+ const enhancedPrompt =
+ `${baseStylePrompt}. Product specifications that MUST be preserved exactly: ${productDetails}. The product must maintain these exact characteristics while only the background and lighting change.`;
+
+ logger.log("Enhanced prompt created", { enhancedPrompt });
+
+ // Use Flux with structured prompt
const generateParams: any = {
model: replicate.image("black-forest-labs/flux-dev"),
- prompt: prompt,
+ prompt: enhancedPrompt,
image: baseImageUrl, // Reference image for img2img
- width: 1024, // From your settings
- height: 1024, // From your settings
- guidance_scale: 7, // From your settings
- num_inference_steps: 30, // From your settings
- strength: 0.7, // Good balance for product preservation
- seed: 1601, // From your settings
+ width: parseInt(size.split("x")[0]),
+ height: parseInt(size.split("x")[1]),
+ guidance_scale: guidance,
+ num_inference_steps: steps,
+ strength: strength,
+ seed: seed,
num_outputs: 1,
};
const { image } = await experimental_generateImage(generateParams);
logger.log("Image generated successfully");
- // Step 3: Prepare for upload
+ // Step 4: Upload to storage
metadata.set("progress", {
- step: 3,
- total: 5,
- message: "Preparing image for upload...",
+ step: 4,
+ total: 4,
+ message: "Uploading to storage...",
});
const imageBuffer = Buffer.from(image.uint8Array);
@@ -92,13 +141,6 @@ export const generateAndUploadImage = task({
const timestamp = Date.now();
const filename = `generated-${timestamp}.png`;
- // Step 4: Upload to storage
- metadata.set("progress", {
- step: 4,
- total: 5,
- message: "Uploading to storage...",
- });
-
// Generate unique key for R2
const sanitizedFileName = filename.replace(/[^a-zA-Z0-9.-]/g, "_");
const r2Key = `uploaded-images/${timestamp}-${sanitizedFileName}`;
@@ -136,10 +178,10 @@ export const generateAndUploadImage = task({
fileName: sanitizedFileName,
};
- // Step 5: Complete
+ // Complete
metadata.set("progress", {
- step: 5,
- total: 5,
+ step: 4,
+ total: 4,
message: "Generation and upload completed!",
});
metadata.set("status", "completed");
@@ -153,7 +195,7 @@ export const generateAndUploadImage = task({
contentType: "image/png",
model,
size,
- prompt,
+ promptStyle,
baseImageUrl,
});
@@ -165,7 +207,7 @@ export const generateAndUploadImage = task({
contentType: "image/png",
model,
size,
- prompt,
+ promptStyle,
baseImageUrl,
};
} catch (error) {
@@ -173,7 +215,7 @@ export const generateAndUploadImage = task({
metadata.set("status", "failed");
metadata.set("progress", {
step: 0,
- total: 5,
+ total: 4,
message: "Generation and upload failed",
});
metadata.set(
@@ -181,7 +223,10 @@ export const generateAndUploadImage = task({
error instanceof Error ? error.message : "Unknown error",
);
- logger.error("Failed to generate and upload image", { error, prompt });
+ logger.error("Failed to generate and upload image", {
+ error,
+ promptStyle,
+ });
throw error;
}
},
diff --git a/product-image-generator/src/trigger/image-upload.ts b/product-image-generator/src/trigger/image-upload.ts
index 6e585dc..a82b97e 100644
--- a/product-image-generator/src/trigger/image-upload.ts
+++ b/product-image-generator/src/trigger/image-upload.ts
@@ -1,6 +1,9 @@
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { logger, metadata, task } from "@trigger.dev/sdk";
+import { openai } from "@ai-sdk/openai";
+import { generateObject } from "ai";
import { Buffer } from "buffer";
+import { z } from "zod";
// Initialize S3 client for R2
const s3Client = new S3Client({
@@ -12,6 +15,71 @@ const s3Client = new S3Client({
},
});
+// Define the product analysis schema
+const productAnalysisSchema = z.object({
+ material: z.string().describe(
+ "Primary material type (e.g., glass, plastic, metal, fabric, wood)",
+ ),
+ colors: z.array(z.string()).describe(
+ "Array of colors present in the product",
+ ),
+ shape: z.string().describe("Detailed shape description"),
+ size_proportions: z.string().describe(
+ "Relative size and proportions description",
+ ),
+ functional_elements: z.array(z.string()).describe("List of functional parts"),
+ surface_finish: z.string().describe(
+ "Finish type (matte, glossy, textured, etc.)",
+ ),
+ text_branding: z.string().describe("Any visible text, logos, or branding"),
+ unique_features: z.array(z.string()).describe(
+ "Distinctive identifying characteristics",
+ ),
+ product_category: z.string().describe("What type of product this is"),
+});
+
+// Structured product analysis using AI SDK
+async function analyzeProductStructured(imageUrl: string) {
+ try {
+ const result = await generateObject({
+ model: openai("gpt-4o"),
+ schema: productAnalysisSchema,
+ messages: [
+ {
+ role: "user",
+ content: [
+ {
+ type: "text",
+ text:
+ "Analyze this product image and extract detailed properties. Be extremely specific and detailed about materials, colors, shape, functional elements, and unique characteristics. This will be used to preserve exact product appearance in AI generation.",
+ },
+ {
+ type: "image",
+ image: imageUrl,
+ },
+ ],
+ },
+ ],
+ });
+
+ return result.object;
+ } catch (error) {
+ logger.warn("Failed to analyze product", { error });
+ // Return fallback structure
+ return {
+ material: "unknown",
+ colors: ["unknown"],
+ shape: "product shape",
+ size_proportions: "standard proportions",
+ functional_elements: ["main body"],
+ surface_finish: "standard finish",
+ text_branding: "none visible",
+ unique_features: ["distinctive product"],
+ product_category: "general product",
+ };
+ }
+}
+
export const uploadImageToR2 = task({
id: "upload-image-to-r2",
maxDuration: 300, // 5 minutes max
@@ -22,20 +90,20 @@ export const uploadImageToR2 = task({
}) => {
const { imageBuffer, fileName, contentType } = payload;
- // Set initial metadata
+ // Set initial metadata with 5 steps (added analysis)
metadata.set("status", "starting");
metadata.set("progress", {
step: 1,
- total: 4,
- message: "Preparing upload...",
+ total: 5,
+ message: "Preparing upload and analysis...",
});
- logger.log("Starting image upload to R2", { fileName, contentType });
+ logger.log("Starting image upload and analysis", { fileName, contentType });
// Convert base64 to buffer
metadata.set("progress", {
step: 2,
- total: 4,
+ total: 5,
message: "Processing image data...",
});
const buffer = Buffer.from(imageBuffer, "base64");
@@ -50,7 +118,7 @@ export const uploadImageToR2 = task({
metadata.set("progress", {
step: 3,
- total: 4,
+ total: 5,
message: "Uploading to storage...",
});
@@ -78,23 +146,34 @@ export const uploadImageToR2 = task({
logger.log("S3 PutObject response:", result as any);
logger.log(`Image uploaded successfully to R2`, { r2Key });
- // Update metadata for completion
+ // Construct the public URL using the R2_PUBLIC_URL env var
+ const publicUrl = `${process.env.R2_PUBLIC_URL}/${r2Key}`;
+
+ // Step 4: Analyze product properties
metadata.set("progress", {
step: 4,
- total: 4,
- message: "Upload completed!",
+ total: 5,
+ message: "Analyzing product properties...",
});
- metadata.set("status", "completed");
- // Construct the public URL using the R2_PUBLIC_URL env var
- const publicUrl = `${process.env.R2_PUBLIC_URL}/${r2Key}`;
+ const productAnalysis = await analyzeProductStructured(publicUrl);
+ logger.log("Product analysis completed", { productAnalysis });
- // Set final metadata with result
+ // Step 5: Complete
+ metadata.set("progress", {
+ step: 5,
+ total: 5,
+ message: "Upload and analysis completed!",
+ });
+ metadata.set("status", "completed");
+
+ // Set final metadata with result including analysis
metadata.set("result", {
publicUrl,
r2Key,
fileSize,
fileName: sanitizedFileName,
+ productAnalysis,
});
return {
@@ -105,11 +184,16 @@ export const uploadImageToR2 = task({
fileSize,
contentType,
fileName: sanitizedFileName,
+ productAnalysis,
};
} catch (error) {
// Set error metadata
metadata.set("status", "failed");
- metadata.set("progress", { step: 0, total: 4, message: "Upload failed" });
+ metadata.set("progress", {
+ step: 0,
+ total: 5,
+ message: "Upload and analysis failed",
+ });
metadata.set(
"error",
error instanceof Error ? error.message : "Unknown error",
From 208653c206b2c85a2796a88a12aa9d59ee102c1c Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Fri, 5 Sep 2025 16:50:27 +0100
Subject: [PATCH 038/232] Added prompt card and improved prompts
---
product-image-generator/app/actions.ts | 42 +++++
.../app/components/CustomPromptCard.tsx | 160 ++++++++++++++++++
.../app/components/GeneratedCard.tsx | 6 +-
.../app/components/UploadCard.tsx | 5 +-
.../app/components/ui/input.tsx | 25 +++
product-image-generator/app/page.tsx | 128 ++++++++++++--
.../src/trigger/generate-and-upload-image.ts | 31 +++-
.../src/trigger/image-upload.ts | 11 +-
8 files changed, 381 insertions(+), 27 deletions(-)
create mode 100644 product-image-generator/app/components/CustomPromptCard.tsx
create mode 100644 product-image-generator/app/components/ui/input.tsx
diff --git a/product-image-generator/app/actions.ts b/product-image-generator/app/actions.ts
index bcba8f7..8d4d2b4 100644
--- a/product-image-generator/app/actions.ts
+++ b/product-image-generator/app/actions.ts
@@ -115,3 +115,45 @@ export async function generateSingleImageAction(
};
}
}
+
+export async function generateCustomImageAction(
+ baseImageUrl: string,
+ productAnalysis: any, // Structured analysis from upload task
+ customPrompt: string,
+) {
+ try {
+ const handle = await tasks.trigger(
+ "generate-and-upload-image",
+ {
+ promptStyle: "custom", // Custom prompt style
+ baseImageUrl,
+ productAnalysis,
+ customPrompt, // User's custom prompt
+ model: "flux",
+ size: "1024x1024",
+ },
+ );
+
+ // Create a public access token for this specific run
+ const tokenResult = await createPublicAccessToken(handle.id);
+
+ if (!tokenResult.success) {
+ return {
+ success: false as const,
+ error: tokenResult.error,
+ };
+ }
+
+ return {
+ success: true as const,
+ runId: handle.id,
+ accessToken: tokenResult.token,
+ };
+ } catch (error) {
+ console.error("Failed to trigger custom image generation:", error);
+ return {
+ success: false as const,
+ error: "Failed to trigger image generation",
+ };
+ }
+}
diff --git a/product-image-generator/app/components/CustomPromptCard.tsx b/product-image-generator/app/components/CustomPromptCard.tsx
new file mode 100644
index 0000000..d3a2de5
--- /dev/null
+++ b/product-image-generator/app/components/CustomPromptCard.tsx
@@ -0,0 +1,160 @@
+"use client";
+
+import { Button } from "./ui/button";
+import { Card } from "./ui/card";
+import { Input } from "./ui/input";
+import { Send, RefreshCw } from "lucide-react";
+import { useState } from "react";
+import { generateCustomImageAction } from "../actions";
+
+interface CustomPromptCardProps {
+ baseImageUrl: string | null;
+ productAnalysis: any | null;
+ onGenerationComplete?: (
+ runId: string,
+ accessToken: string,
+ prompt: string
+ ) => void;
+}
+
+export default function CustomPromptCard({
+ baseImageUrl,
+ productAnalysis,
+ onGenerationComplete,
+}: CustomPromptCardProps) {
+ const [customPrompt, setCustomPrompt] = useState("");
+ const [isGenerating, setIsGenerating] = useState(false);
+ const [runId, setRunId] = useState(null);
+ const [accessToken, setAccessToken] = useState(null);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ if (!customPrompt.trim() || !baseImageUrl || !productAnalysis) {
+ return;
+ }
+
+ setIsGenerating(true);
+
+ try {
+ const result = await generateCustomImageAction(
+ baseImageUrl,
+ productAnalysis,
+ customPrompt.trim()
+ );
+
+ if (result.success) {
+ setRunId(result.runId);
+ setAccessToken(result.accessToken);
+ onGenerationComplete?.(
+ result.runId,
+ result.accessToken,
+ customPrompt.trim()
+ );
+ }
+ } catch (error) {
+ console.error("Failed to generate custom image:", error);
+ } finally {
+ setIsGenerating(false);
+ }
+ };
+
+ const handleRegenerate = async () => {
+ if (!customPrompt.trim() || !baseImageUrl || !productAnalysis) {
+ return;
+ }
+
+ setIsGenerating(true);
+
+ try {
+ const result = await generateCustomImageAction(
+ baseImageUrl,
+ productAnalysis,
+ customPrompt.trim()
+ );
+
+ if (result.success) {
+ setRunId(result.runId);
+ setAccessToken(result.accessToken);
+ onGenerationComplete?.(
+ result.runId,
+ result.accessToken,
+ customPrompt.trim()
+ );
+ }
+ } catch (error) {
+ console.error("Failed to regenerate custom image:", error);
+ } finally {
+ setIsGenerating(false);
+ }
+ };
+
+ const isDisabled = !baseImageUrl || !productAnalysis || !customPrompt.trim();
+
+ return (
+
+
+
+
+ {!baseImageUrl && (
+
+ Upload an image first to enable custom prompts
+
+ )}
+
+
+ );
+}
diff --git a/product-image-generator/app/components/GeneratedCard.tsx b/product-image-generator/app/components/GeneratedCard.tsx
index 0a0a342..be3cfaf 100644
--- a/product-image-generator/app/components/GeneratedCard.tsx
+++ b/product-image-generator/app/components/GeneratedCard.tsx
@@ -2,7 +2,7 @@
import { Button } from "./ui/button";
import { Card } from "./ui/card";
-import { Download, RefreshCw, Expand } from "lucide-react";
+import { Download, RefreshCw, Expand, ImageIcon } from "lucide-react";
import { useState } from "react";
import { useRealtimeRun } from "@trigger.dev/react-hooks";
@@ -148,11 +148,11 @@ export default function GeneratedCard({
) : (
// Show loading/waiting state
-
+
{isLoading && generationProgress === "generating" ? (
) : (
-
+
)}
diff --git a/product-image-generator/app/components/UploadCard.tsx b/product-image-generator/app/components/UploadCard.tsx
index d0c2388..e64af1c 100644
--- a/product-image-generator/app/components/UploadCard.tsx
+++ b/product-image-generator/app/components/UploadCard.tsx
@@ -58,9 +58,10 @@ export default function UploadCard({ onUploadComplete }: UploadCardProps) {
// Notify parent component with URL and analysis
if (onUploadComplete && run.output.publicUrl) {
+ const output = run.output as any;
+ const metadataResult = run.metadata?.result as any;
const productAnalysis =
- run.output.productAnalysis ||
- run.metadata?.result?.productAnalysis;
+ output.productAnalysis || metadataResult?.productAnalysis;
onUploadComplete(run.output.publicUrl, productAnalysis);
}
break;
diff --git a/product-image-generator/app/components/ui/input.tsx b/product-image-generator/app/components/ui/input.tsx
new file mode 100644
index 0000000..c168325
--- /dev/null
+++ b/product-image-generator/app/components/ui/input.tsx
@@ -0,0 +1,25 @@
+import * as React from "react";
+
+import { cn } from "@/lib/utils";
+
+export interface InputProps
+ extends React.InputHTMLAttributes {}
+
+const Input = React.forwardRef(
+ ({ className, type, ...props }, ref) => {
+ return (
+
+ );
+ }
+);
+Input.displayName = "Input";
+
+export { Input };
diff --git a/product-image-generator/app/page.tsx b/product-image-generator/app/page.tsx
index a0a49b4..41da128 100644
--- a/product-image-generator/app/page.tsx
+++ b/product-image-generator/app/page.tsx
@@ -6,6 +6,7 @@ import { Button } from "./components/ui/button";
import { Card } from "./components/ui/card";
import UploadCard from "./components/UploadCard";
import GeneratedCard from "./components/GeneratedCard";
+import CustomPromptCard from "./components/CustomPromptCard";
import { generateSingleImageAction } from "./actions";
export default function ImageManagementApp() {
@@ -24,12 +25,25 @@ export default function ImageManagementApp() {
"isolated-table": null,
"lifestyle-scene": null,
"hero-shot": null,
+ custom: null,
+ });
+
+ // Track custom generations for bottom row
+ const [customGenerations, setCustomGenerations] = useState<{
+ runIds: (string | null)[];
+ accessTokens: (string | null)[];
+ prompts: (string | null)[];
+ }>({
+ runIds: [null, null, null],
+ accessTokens: [null, null, null],
+ prompts: [null, null, null],
});
const promptTitles = {
"isolated-table": "Table Shot",
"lifestyle-scene": "Lifestyle Scene",
"hero-shot": "Hero Shot",
+ custom: "Custom Prompt",
};
const handleUploadComplete = async (imageUrl: string, analysis?: any) => {
@@ -116,6 +130,35 @@ export default function ImageManagementApp() {
}
};
+ const handleCustomGenerationComplete = (
+ runId: string,
+ accessToken: string,
+ prompt: string,
+ cardIndex: number
+ ) => {
+ setCustomGenerations((prev) => ({
+ runIds: prev.runIds.map((id, i) => (i === cardIndex ? runId : id)),
+ accessTokens: prev.accessTokens.map((token, i) =>
+ i === cardIndex ? accessToken : token
+ ),
+ prompts: prev.prompts.map((p, i) => (i === cardIndex ? prompt : p)),
+ }));
+ };
+
+ // Check if top row generations are complete
+ const topRowGenerationsComplete =
+ generationRunIds["isolated-table"] &&
+ generationRunIds["lifestyle-scene"] &&
+ generationRunIds["hero-shot"];
+
+ // Find next available custom card slot
+ const getNextCustomCardIndex = () => {
+ return customGenerations.runIds.findIndex((id) => id === null);
+ };
+
+ const nextCustomCardIndex = getNextCustomCardIndex();
+ const hasAvailableCustomSlot = nextCustomCardIndex !== -1;
+
return (
{/* Navigation Header */}
@@ -186,23 +229,80 @@ export default function ImageManagementApp() {
/>
- {/* Bottom Row - 4 More Cards (Future Implementation) */}
+ {/* Bottom Row - Sequential Custom Cards */}
- {Array.from({ length: 4 }).map((_, index) => (
-
-
-
-
-
+ {Array.from({ length: 4 }).map((_, index) => {
+ const runId = customGenerations.runIds[index];
+ const accessToken = customGenerations.accessTokens[index];
+ const prompt = customGenerations.prompts[index];
+
+ // If this slot has a generation, show the generated card
+ if (runId && accessToken) {
+ return (
+
{
+ // Reset this specific custom generation
+ setCustomGenerations((prev) => ({
+ runIds: prev.runIds.map((id, i) =>
+ i === index ? null : id
+ ),
+ accessTokens: prev.accessTokens.map((token, i) =>
+ i === index ? null : token
+ ),
+ prompts: prev.prompts.map((p, i) =>
+ i === index ? null : p
+ ),
+ }));
+ }}
+ />
+ );
+ }
+
+ // If this is the next available slot and top row is complete, show custom prompt card
+ if (index === nextCustomCardIndex && topRowGenerationsComplete) {
+ return (
+
+ handleCustomGenerationComplete(
+ runId,
+ accessToken,
+ prompt,
+ index
+ )
+ }
+ />
+ );
+ }
+
+ // Otherwise show placeholder
+ return (
+
+
+
+
+
+
+
+ {topRowGenerationsComplete
+ ? "Create custom scenario"
+ : ""}
+
-
Coming Soon
-
-
- ))}
+
+ );
+ })}
{/* Action Buttons */}
diff --git a/product-image-generator/src/trigger/generate-and-upload-image.ts b/product-image-generator/src/trigger/generate-and-upload-image.ts
index 870994e..4386597 100644
--- a/product-image-generator/src/trigger/generate-and-upload-image.ts
+++ b/product-image-generator/src/trigger/generate-and-upload-image.ts
@@ -19,9 +19,11 @@ export const generateAndUploadImage = task({
id: "generate-and-upload-image",
maxDuration: 600, // 10 minutes max
run: async (payload: {
- promptStyle: string; // Style prompt (table-shot, lifestyle, hero)
+ promptStyle: string; // Style prompt (table-shot, lifestyle, hero, custom)
baseImageUrl: string;
productAnalysis: {
+ exact_product_name: string;
+ model_number: string;
material: string;
colors: string[];
shape: string;
@@ -32,6 +34,7 @@ export const generateAndUploadImage = task({
unique_features: string[];
product_category: string;
};
+ customPrompt?: string; // User's custom prompt for "custom" style
model?: "flux";
size?: "1024x1024" | "1792x1024" | "1024x1792";
strength?: number;
@@ -43,11 +46,12 @@ export const generateAndUploadImage = task({
promptStyle,
baseImageUrl,
productAnalysis,
+ customPrompt,
model = "flux",
size = "1024x1024",
- strength = 0.7,
+ strength = 0.2,
guidance = 7, // From your settings
- steps = 30, // From your settings
+ steps = 20, // From your settings
seed = Math.floor(Math.random() * 1000000), // Random seed for variety
} = payload;
@@ -77,6 +81,10 @@ export const generateAndUploadImage = task({
// Build detailed product description from analysis with fallbacks
const productDetails = [
+ `Exact product: ${
+ productAnalysis?.exact_product_name || "unknown product"
+ }`,
+ `Model number: ${productAnalysis?.model_number || "unknown model"}`,
`Material: ${productAnalysis?.material || "unknown"}`,
`Colors: ${productAnalysis?.colors?.join(", ") || "unknown"}`,
`Shape: ${productAnalysis?.shape || "unknown"}`,
@@ -96,9 +104,10 @@ export const generateAndUploadImage = task({
"isolated-table":
`Professional product photography on clean white table with studio lighting, minimalist background, commercial style`,
"lifestyle-scene":
- `Lifestyle product photography in modern home setting with natural lighting, styled environment, aspirational setting`,
+ `Lifestyle product photography of fashionable person of any gender or ethnicity in the sunshine holding the product in their hand with a big smile on their face - they should be pointing to the product. This should be a very sophisticated lifestyle shot`,
"hero-shot":
- `Premium hero shot with dramatic lighting, luxury commercial photography style, perfect for marketing materials`,
+ `Professional lifestyle shot of elegant hands holding and presenting the product, dramatic lighting, luxury commercial photography style, perfect for marketing materials, human interaction with product`,
+ "custom": customPrompt || "Professional product photography",
};
const baseStylePrompt =
@@ -107,9 +116,14 @@ export const generateAndUploadImage = task({
// Combine everything into one unambiguous prompt
const enhancedPrompt =
- `${baseStylePrompt}. Product specifications that MUST be preserved exactly: ${productDetails}. The product must maintain these exact characteristics while only the background and lighting change.`;
+ `${baseStylePrompt}. MANDATORY PRODUCT PRESERVATION: You MUST recreate the EXACT product from the reference image. Product specifications that are ABSOLUTELY REQUIRED: ${productDetails}. The product must be IDENTICAL to the reference image - same brand name, same exact model number, same exact colors and color combinations, same shape, same proportions, same text, same logos, same design elements, same materials, same finish. DO NOT change any colors, DO NOT substitute different models or color variants, DO NOT modify the product itself in any way. The product must be pixel-perfect identical. Only change the background, lighting, and camera angle. If you cannot preserve the exact product, do not generate the image.`;
- logger.log("Enhanced prompt created", { enhancedPrompt });
+ logger.log("Enhanced prompt created", {
+ enhancedPrompt,
+ promptStyle,
+ customPrompt: customPrompt || "none provided",
+ baseStylePrompt,
+ });
// Use Flux with structured prompt
const generateParams: any = {
@@ -123,6 +137,9 @@ export const generateAndUploadImage = task({
strength: strength,
seed: seed,
num_outputs: 1,
+ // Add negative prompt to prevent unwanted changes
+ negative_prompt:
+ "different product, wrong brand, different model, different colors, color variants, different shape, modified product, altered design, wrong text, different logo, fake product, generic product",
};
const { image } = await experimental_generateImage(generateParams);
diff --git a/product-image-generator/src/trigger/image-upload.ts b/product-image-generator/src/trigger/image-upload.ts
index a82b97e..6801c86 100644
--- a/product-image-generator/src/trigger/image-upload.ts
+++ b/product-image-generator/src/trigger/image-upload.ts
@@ -17,6 +17,12 @@ const s3Client = new S3Client({
// Define the product analysis schema
const productAnalysisSchema = z.object({
+ exact_product_name: z.string().describe(
+ "The exact brand name and product model/name as it appears on the packaging",
+ ),
+ model_number: z.string().describe(
+ "The specific model number or identifier (e.g., Z fc, iPhone 15, etc.)",
+ ),
material: z.string().describe(
"Primary material type (e.g., glass, plastic, metal, fabric, wood)",
),
@@ -51,7 +57,7 @@ async function analyzeProductStructured(imageUrl: string) {
{
type: "text",
text:
- "Analyze this product image and extract detailed properties. Be extremely specific and detailed about materials, colors, shape, functional elements, and unique characteristics. This will be used to preserve exact product appearance in AI generation.",
+ "Analyze this product image and extract detailed properties. Be extremely specific and detailed about materials, colors, shape, functional elements, and unique characteristics. This will be used to preserve exact product appearance in AI generation. Focus on: exact brand name and model number, specific colors and color combinations (including any two-tone or multi-color designs), precise shape and proportions, functional details, label design elements, text content, and any distinctive visual features that make this product unique. Pay special attention to color accuracy - note exact color names and combinations. Be extremely precise about every visual detail that makes this product identifiable.",
},
{
type: "image",
@@ -62,11 +68,14 @@ async function analyzeProductStructured(imageUrl: string) {
],
});
+ logger.log("Product analysis completed", { analysis: result.object });
return result.object;
} catch (error) {
logger.warn("Failed to analyze product", { error });
// Return fallback structure
return {
+ exact_product_name: "unknown product",
+ model_number: "unknown model",
material: "unknown",
colors: ["unknown"],
shape: "product shape",
From 505d93da8a5f3d52cbe545e442419d877508d005 Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Mon, 8 Sep 2025 17:42:58 +0100
Subject: [PATCH 039/232] More updates
---
product-image-generator/app/layout.tsx | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/product-image-generator/app/layout.tsx b/product-image-generator/app/layout.tsx
index 432080f..57a50b6 100644
--- a/product-image-generator/app/layout.tsx
+++ b/product-image-generator/app/layout.tsx
@@ -1,14 +1,11 @@
-import type React from "react";
-import type { Metadata } from "next";
-import { GeistSans } from "geist/font/sans";
+import { Analytics } from "@vercel/analytics/next";
import { GeistMono } from "geist/font/mono";
+import { GeistSans } from "geist/font/sans";
+import type { Metadata } from "next";
import {
- Playfair_Display,
- Roboto,
- Roboto_Condensed,
- Source_Sans_3,
+ Roboto
} from "next/font/google";
-import { Analytics } from "@vercel/analytics/next";
+import type React from "react";
import { Suspense } from "react";
import "./globals.css";
From 6b20c47c74de7c043debdb46cce5f8901b95b81a Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Wed, 10 Sep 2025 10:17:10 +0100
Subject: [PATCH 040/232] Some updates
---
product-image-generator/README.md | 153 +++++------
.../app/components/CustomPromptCard.tsx | 7 +-
.../app/components/GeneratedCard.tsx | 2 +-
.../app/components/UploadCard.tsx | 6 +-
product-image-generator/app/page.tsx | 256 +++++++++---------
5 files changed, 209 insertions(+), 215 deletions(-)
diff --git a/product-image-generator/README.md b/product-image-generator/README.md
index f514242..de3673a 100644
--- a/product-image-generator/README.md
+++ b/product-image-generator/README.md
@@ -3,6 +3,7 @@
Transform product photos into professional marketing materials using AI-powered image generation.
Upload a product image and automatically generate three marketing variations: isolated table shots, lifestyle scenes, and hero presentations. Built with Next.js, Trigger.dev, and OpenAI's DALL-E 3.
+Upload a product image and automatically generate three marketing variations: isolated table shots, lifestyle scenes, and hero presentations. Built with Next.js, Trigger.dev, and the AI SDK (Replicate Flux for image generation, OpenAI for analysis).
## Features
@@ -20,36 +21,37 @@ Upload a product image and automatically generate three marketing variations: is
```
uploadImageToR2
-├── Handles user image uploads to R2 storage
-├── 4-step progress tracking
+├── Handles user image uploads to Cloudflare R2 storage
+├── Analyzes the product with OpenAI (structured JSON)
+├── 5-step progress tracking via metadata
generateAndUploadImage
-├── Generates AI image using DALL-E 3
+├── Generates an image using Replicate Flux (img2img)
├── Uploads result to R2 storage
-├── 5-step progress tracking
-
-batchGenerateAndUploadImages
-├── Triggers 3x generateAndUploadImage tasks in parallel
-├── Returns individual run IDs for UI subscription
+├── 4-step progress tracking via metadata
```
### Component Architecture
```
-UploadCard (aspect-square)
+UploadCard (aspect-[3/4])
├── Drag & drop image upload
├── Progress tracking via run subscription
-├── Triggers batch generation on completion
+├── On completion, parent triggers 3 individual generations
GeneratedCard (aspect-[3/4])
├── Individual task progress tracking
├── Real-time image display via subscription
├── Download and retry functionality
+CustomPromptCard (aspect-[3/4])
+├── Lets user enter a freeform scene prompt
+├── Triggers a single generation reusing the product analysis
+
Main Page
-├── Grid layout: 1 upload + 3 generated cards
-├── State management for run IDs and access tokens
-├── Individual generation triggering and retry handling
+├── Grid layout: 1 upload + 3 generated cards (top) + 4 custom slots (bottom)
+├── State for run IDs/access tokens for each card
+├── Triggers generations and handles retries
```
## Getting Started
@@ -58,7 +60,8 @@ Main Page
- Node.js 18+ and pnpm
- Trigger.dev account and project
-- OpenAI API key with DALL-E 3 access
+- OpenAI API key (for product analysis)
+- Replicate API token (for Flux image generation)
- Cloudflare R2 bucket for image storage
### Environment Variables
@@ -70,6 +73,9 @@ TRIGGER_SECRET_KEY=tr_dev_your_secret_key_here
# OpenAI
OPENAI_API_KEY=sk-your_openai_api_key_here
+# Replicate
+REPLICATE_API_TOKEN=your_replicate_api_token
+
# Cloudflare R2 Storage
R2_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com
R2_ACCESS_KEY_ID=your_r2_access_key
@@ -91,114 +97,101 @@ pnpm dev
pnpm dlx trigger.dev@latest dev
```
+### Quickstart (happy path)
+
+```bash
+cp .env.example .env # if provided, otherwise create .env with the vars above
+pnpm install
+pnpm dev &
+pnpm dlx trigger.dev@latest dev
+```
+
## Usage
1. Upload a product image via drag & drop or file selection
-2. Upload task runs with 4-step progress tracking
-3. On completion, batch task triggers 3 parallel generation tasks
-4. Each GeneratedCard subscribes to its individual task progress
-5. Generated images display with download functionality
-6. Failed generations can be retried individually
+2. The upload task runs with 5-step progress and returns a public URL + analysis
+3. The page triggers three `generateAndUploadImage` tasks (table, lifestyle, hero)
+4. Each `GeneratedCard` subscribes to its run and shows progress
+5. When a run completes, the image auto-appears, with expand/download actions
+6. You can retry any failed generation individually
+7. After the top row completes, use custom cards to generate extra scenes
## Technical Implementation
### AI Prompt Engineering
-Each generation uses prompts that emphasize:
+Each generation builds an enhanced prompt from the structured product analysis. It enforces:
-- Product consistency: "EXACT same product from reference image"
-- Material preservation: "Identical colors, textures, materials, and design details"
-- Context adaptation: Only backgrounds and lighting change
+- Product consistency: EXACT same product as the reference (brand, model, colors, text)
+- Preservation: shape/proportions/materials/logos must be unchanged
+- Variation: only background, lighting, and camera angle may change
### Progress Tracking Implementation
-- Upload Task: 4 steps (prepare → process → upload → complete)
-- Generation Tasks: 5 steps (prepare → generate → prepare upload → upload → complete)
-- Real-time updates via `runs.subscribeToRun()` with public access tokens
+- Upload Task: 5 steps (prepare → process → upload → analyze → complete)
+- Generation Task: 4 steps (prepare → prompt → generate → upload/complete)
+- Real-time updates via `useRealtimeRun()` or `runs.subscribeToRun()` using public access tokens
### Image Processing
-- Format: Portrait 1024x1792 for optimal display in 3:4 aspect ratio cards
-- Display: `object-contain` CSS to prevent cropping
+- Size: Defaults to 1024x1024 (configurable)
+- Display: `object-cover` in generated cards
- Storage: Cloudflare R2 with automatic public URL generation
-- Fallback: Base64 blob URLs if storage unavailable
## Project Structure
```
src/
├── trigger/
-│ ├── image-upload.ts # User image upload to R2
-│ ├── generate-and-upload-image.ts # AI generation + upload (single task)
-│ └── batch-generate-and-upload.ts # Batch processing coordinator
+│ ├── image-upload.ts # Upload to R2 + structured product analysis (OpenAI)
+│ └── generate-and-upload-image.ts # Flux generation (Replicate) + R2 upload
├── app/
-│ ├── actions.ts # Server actions for task triggering
+│ ├── actions.ts # Server actions: trigger tasks + public tokens
│ ├── components/
-│ │ ├── UploadCard.tsx # Image upload with progress
-│ │ ├── GeneratedCard.tsx # Generated image display with subscription
-│ │ └── ui/ # shadcn/ui components
-│ └── page.tsx # Main application interface
-└── trigger.config.ts # Trigger.dev configuration
+│ │ ├── UploadCard.tsx # Upload with realtime progress
+│ │ ├── GeneratedCard.tsx # Generated image display + progress
+│ │ └── CustomPromptCard.tsx # Freeform prompt generation (post-upload)
+│ └── page.tsx # Main application interface
+└── trigger.config.ts # Trigger.dev configuration
```
## Task Flow Details
### Upload Flow
-1. `UploadCard` triggers `uploadImageToR2` task
-2. Task converts file to base64, uploads to R2, returns public URL
-3. `UploadCard` subscribes to run progress, displays completion
-4. On completion, triggers `batchGenerateAndUploadImages`
+1. `UploadCard` calls `uploadImageToR2Action` (server action)
+2. Server action triggers `uploadImageToR2` task and creates a public token
+3. Client subscribes to the run using the token; task uploads to R2
+4. Task performs structured product analysis (OpenAI GPT-4o)
+5. On completion, the page receives `publicUrl` and `productAnalysis`
### Generation Flow
-1. `batchGenerateAndUploadImages` triggers 3x `generateAndUploadImage` tasks
-2. Each task: generates image → uploads to R2 → returns public URL
-3. `GeneratedCard` components subscribe to individual task progress
-4. Images display automatically when tasks complete
+1. The page calls `generateSingleImageAction` three times (table/lifestyle/hero)
+2. Each task builds an enhanced prompt from `productAnalysis`
+3. Flux (Replicate) generates an img2img output referencing the base image URL
+4. The task uploads the result to R2 and sets `metadata.result.publicUrl`
+5. `GeneratedCard` subscribes and renders the image on completion
### Error Handling
- Individual task failures don't affect other generations
-- Retry functionality re-triggers specific failed tasks
-- Comprehensive error logging and user feedback
+- Retry triggers only that specific style
+- Errors are surfaced via run metadata and UI messages
## Customization
### Adding Generation Styles
-Edit prompts in `src/trigger/batch-generate-and-upload.ts`:
-
-```typescript
-const prompts = [
- {
- id: "your-style-id",
- prompt: "Your detailed prompt here...",
- },
-];
-```
+Add a new style key in `generate-and-upload-image.ts` inside `stylePrompts` and wire a corresponding button/card in `app/page.tsx`.
### Modifying Image Dimensions
-Change size parameter in actions:
-
-```typescript
-size: "1024x1792", // Portrait
-size: "1792x1024", // Landscape
-size: "1024x1024", // Square
-```
+Pass a different `size` when triggering `generateAndUploadImage` via the server action (e.g., `"1792x1024"` or `"1024x1792"`).
### Custom Progress Messages
-Update metadata in task files:
-
-```typescript
-metadata.set("progress", {
- step: 2,
- total: 5,
- message: "Custom progress message",
-});
-```
+Update `metadata.set("progress", ...)` in the respective task to change UX copy.
## Deployment
@@ -208,7 +201,7 @@ metadata.set("progress", {
# Deploy tasks to Trigger.dev
pnpm dlx trigger.dev@latest deploy
-# Deploy frontend (Vercel recommended)
+# Deploy frontend (e.g., Vercel)
vercel deploy
```
@@ -223,17 +216,15 @@ vercel deploy
### Key Implementation Details
-- Uses `triggerAndWait()` for sequential task dependencies
-- Public access tokens enable client-side run subscriptions
+- Uses public access tokens to enable client-side run subscriptions
- `aspect-[3/4]` Tailwind class for portrait card layout
-- Error boundaries and timeout handling for stuck tasks
+- Metadata carries progress + final result for robust UI updates
### Performance Considerations
- Parallel task execution for multiple image generations
-- Efficient base64 to blob conversion for image display
-- Proper cleanup of blob URLs and timeouts
-- Rate limiting considerations for OpenAI API calls
+- Cloud storage with aggressive cache headers for assets
+- Consider API quotas for Replicate/OpenAI
## License
diff --git a/product-image-generator/app/components/CustomPromptCard.tsx b/product-image-generator/app/components/CustomPromptCard.tsx
index d3a2de5..004e387 100644
--- a/product-image-generator/app/components/CustomPromptCard.tsx
+++ b/product-image-generator/app/components/CustomPromptCard.tsx
@@ -1,11 +1,10 @@
"use client";
-import { Button } from "./ui/button";
-import { Card } from "./ui/card";
-import { Input } from "./ui/input";
-import { Send, RefreshCw } from "lucide-react";
+import { RefreshCw, Send } from "lucide-react";
import { useState } from "react";
import { generateCustomImageAction } from "../actions";
+import { Button } from "./ui/button";
+import { Card } from "./ui/card";
interface CustomPromptCardProps {
baseImageUrl: string | null;
diff --git a/product-image-generator/app/components/GeneratedCard.tsx b/product-image-generator/app/components/GeneratedCard.tsx
index be3cfaf..240fe48 100644
--- a/product-image-generator/app/components/GeneratedCard.tsx
+++ b/product-image-generator/app/components/GeneratedCard.tsx
@@ -148,7 +148,7 @@ export default function GeneratedCard({
) : (
// Show loading/waiting state
-
+
{isLoading && generationProgress === "generating" ? (
) : (
diff --git a/product-image-generator/app/components/UploadCard.tsx b/product-image-generator/app/components/UploadCard.tsx
index e64af1c..bd7fdbf 100644
--- a/product-image-generator/app/components/UploadCard.tsx
+++ b/product-image-generator/app/components/UploadCard.tsx
@@ -204,12 +204,12 @@ export default function UploadCard({ onUploadComplete }: UploadCardProps) {
{isLoading ? (
-
+
) : (
)}
diff --git a/product-image-generator/app/page.tsx b/product-image-generator/app/page.tsx
index 41da128..6f5f5f1 100644
--- a/product-image-generator/app/page.tsx
+++ b/product-image-generator/app/page.tsx
@@ -162,8 +162,8 @@ export default function ImageManagementApp() {
return (
{/* Navigation Header */}
-
-
+
{/* Main Content */}
-
-
-
- Product Image Generator
-
-
- Upload a product image and automatically generate professional
- marketing shots
-
-
+
+
+
+
+ Product Image Generator
+
+
+ Upload a product image and automatically generate professional
+ marketing shots
+
+
- {/* Top Row - Upload + 3 Generated Images */}
-
- {/* Upload card stays square */}
-
- handleRetryGeneration("isolated-table")}
- />
- handleRetryGeneration("lifestyle-scene")}
- />
- handleRetryGeneration("hero-shot")}
- />
-
+ {/* Top Row - Upload + 3 Generated Images */}
+
+ {/* Upload card stays square */}
+
+ handleRetryGeneration("isolated-table")}
+ />
+ handleRetryGeneration("lifestyle-scene")}
+ />
+ handleRetryGeneration("hero-shot")}
+ />
+
- {/* Bottom Row - Sequential Custom Cards */}
-
- {Array.from({ length: 4 }).map((_, index) => {
- const runId = customGenerations.runIds[index];
- const accessToken = customGenerations.accessTokens[index];
- const prompt = customGenerations.prompts[index];
+ {/* Bottom Row - Sequential Custom Cards */}
+
+ {Array.from({ length: 4 }).map((_, index) => {
+ const runId = customGenerations.runIds[index];
+ const accessToken = customGenerations.accessTokens[index];
+ const prompt = customGenerations.prompts[index];
- // If this slot has a generation, show the generated card
- if (runId && accessToken) {
- return (
-
{
- // Reset this specific custom generation
- setCustomGenerations((prev) => ({
- runIds: prev.runIds.map((id, i) =>
- i === index ? null : id
- ),
- accessTokens: prev.accessTokens.map((token, i) =>
- i === index ? null : token
- ),
- prompts: prev.prompts.map((p, i) =>
- i === index ? null : p
- ),
- }));
- }}
- />
- );
- }
+ // If this slot has a generation, show the generated card
+ if (runId && accessToken) {
+ return (
+ {
+ // Reset this specific custom generation
+ setCustomGenerations((prev) => ({
+ runIds: prev.runIds.map((id, i) =>
+ i === index ? null : id
+ ),
+ accessTokens: prev.accessTokens.map((token, i) =>
+ i === index ? null : token
+ ),
+ prompts: prev.prompts.map((p, i) =>
+ i === index ? null : p
+ ),
+ }));
+ }}
+ />
+ );
+ }
- // If this is the next available slot and top row is complete, show custom prompt card
- if (index === nextCustomCardIndex && topRowGenerationsComplete) {
- return (
-
- handleCustomGenerationComplete(
- runId,
- accessToken,
- prompt,
- index
- )
- }
- />
- );
- }
+ // If this is the next available slot and top row is complete, show custom prompt card
+ if (index === nextCustomCardIndex && topRowGenerationsComplete) {
+ return (
+
+ handleCustomGenerationComplete(
+ runId,
+ accessToken,
+ prompt,
+ index
+ )
+ }
+ />
+ );
+ }
- // Otherwise show placeholder
- return (
-
-
-
-
-
+ // Otherwise show placeholder
+ return (
+
+
+
+
+
+
+
+ {topRowGenerationsComplete
+ ? "Create custom scenario"
+ : ""}
+
-
- {topRowGenerationsComplete
- ? "Create custom scenario"
- : ""}
-
-
-
- );
- })}
-
+
+ );
+ })}
+
- {/* Action Buttons */}
-
-
-
- Upload New Image
-
-
-
- Settings
-
+ {/* Action Buttons */}
+
+
+
+ Upload New Image
+
+
+
+ Settings
+
+
From 66bed635b5dcb58dd6f947ceb6b9c38b5ada7475 Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Wed, 10 Sep 2025 10:18:44 +0100
Subject: [PATCH 041/232] Removed v3
---
product-image-generator/app/actions.ts | 2 +-
product-image-generator/app/components/UploadCard.tsx | 2 +-
product-image-generator/trigger.config.ts | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/product-image-generator/app/actions.ts b/product-image-generator/app/actions.ts
index 8d4d2b4..c83a004 100644
--- a/product-image-generator/app/actions.ts
+++ b/product-image-generator/app/actions.ts
@@ -1,6 +1,6 @@
"use server";
-import { auth, tasks } from "@trigger.dev/sdk/v3";
+import { auth, tasks } from "@trigger.dev/sdk";
import type { uploadImageToR2 } from "../src/trigger/image-upload";
import type { generateAndUploadImage } from "../src/trigger/generate-and-upload-image";
diff --git a/product-image-generator/app/components/UploadCard.tsx b/product-image-generator/app/components/UploadCard.tsx
index bd7fdbf..a148877 100644
--- a/product-image-generator/app/components/UploadCard.tsx
+++ b/product-image-generator/app/components/UploadCard.tsx
@@ -5,7 +5,7 @@ import { Card } from "./ui/card";
import { Upload } from "lucide-react";
import { useRef, useState, useEffect } from "react";
import { uploadImageToR2Action } from "../actions";
-import { runs, configure } from "@trigger.dev/sdk/v3";
+import { runs, configure } from "@trigger.dev/sdk";
interface UploadCardProps {
onUploadComplete?: (imageUrl: string, productAnalysis?: any) => void;
diff --git a/product-image-generator/trigger.config.ts b/product-image-generator/trigger.config.ts
index 6fe60ff..819c295 100644
--- a/product-image-generator/trigger.config.ts
+++ b/product-image-generator/trigger.config.ts
@@ -1,4 +1,4 @@
-import { defineConfig } from "@trigger.dev/sdk/v3";
+import { defineConfig } from "@trigger.dev/sdk";
export default defineConfig({
project: "proj_xjminyoyogxbrfipkrkt",
From 38137e1ffcb6ca07285043a048f0aa89fd5ed16c Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Wed, 10 Sep 2025 13:18:24 +0100
Subject: [PATCH 042/232] Mid refactor
---
.../app/ProductImageGenerator.tsx | 227 ++++++++++++
product-image-generator/app/actions.ts | 155 ++------
.../app/components/CustomPromptCard.tsx | 102 +++---
.../app/components/GeneratedCard.tsx | 124 +++++--
.../app/components/UploadCard.tsx | 264 +++++++-------
product-image-generator/app/layout.tsx | 5 +-
product-image-generator/app/page.tsx | 334 +-----------------
product-image-generator/app/types/trigger.ts | 60 ++++
8 files changed, 615 insertions(+), 656 deletions(-)
create mode 100644 product-image-generator/app/ProductImageGenerator.tsx
create mode 100644 product-image-generator/app/types/trigger.ts
diff --git a/product-image-generator/app/ProductImageGenerator.tsx b/product-image-generator/app/ProductImageGenerator.tsx
new file mode 100644
index 0000000..b20eef5
--- /dev/null
+++ b/product-image-generator/app/ProductImageGenerator.tsx
@@ -0,0 +1,227 @@
+"use client";
+
+import { useState } from "react";
+import { Home, ImageIcon, Settings, Upload, User } from "lucide-react";
+import { Button } from "./components/ui/button";
+import { Card } from "./components/ui/card";
+import UploadCard from "./components/UploadCard";
+import GeneratedCard from "./components/GeneratedCard";
+import CustomPromptCard from "./components/CustomPromptCard";
+import type { ProductAnalysis } from "./types/trigger";
+
+interface ProductImageGeneratorProps {
+ triggerToken: string;
+}
+
+export default function ProductImageGenerator({
+ triggerToken: accessToken,
+}: ProductImageGeneratorProps) {
+ const [uploadedImageUrl, setUploadedImageUrl] = useState
(null);
+ const [productAnalysis, setProductAnalysis] =
+ useState(null);
+
+ // Track custom generations for bottom row
+ const [customGenerations, setCustomGenerations] = useState<{
+ runIds: (string | null)[];
+ accessTokens: (string | null)[];
+ prompts: (string | null)[];
+ }>({
+ runIds: [null, null, null, null],
+ accessTokens: [null, null, null, null],
+ prompts: [null, null, null, null],
+ });
+
+ const handleUploadComplete = (
+ imageUrl: string,
+ productAnalysis?: ProductAnalysis
+ ) => {
+ setUploadedImageUrl(imageUrl);
+ if (productAnalysis) {
+ setProductAnalysis(productAnalysis);
+ }
+ };
+
+ const handleCustomGenerationComplete = (
+ runId: string,
+ accessToken: string,
+ prompt: string,
+ index: number
+ ) => {
+ setCustomGenerations((prev) => ({
+ runIds: prev.runIds.map((id, i) => (i === index ? runId : id)),
+ accessTokens: prev.accessTokens.map((token, i) =>
+ i === index ? accessToken : token
+ ),
+ prompts: prev.prompts.map((p, i) => (i === index ? prompt : p)),
+ }));
+ };
+
+ const promptTitles = {
+ "isolated-table": "Clean Product Shot",
+ "lifestyle-scene": "Lifestyle Scene",
+ "hero-shot": "Hero Shot",
+ };
+
+ // Determine which custom cards have completed generations
+ const completedCustomCards = customGenerations.runIds.filter(
+ (runId) => runId !== null
+ ).length;
+
+ // Find the next available custom card slot
+ const nextCustomCardIndex = customGenerations.runIds.findIndex(
+ (runId) => runId === null
+ );
+
+ // Check if top row is complete (upload + 3 generated images)
+ const topRowGenerationsComplete =
+ uploadedImageUrl && productAnalysis ? true : false;
+
+ return (
+
+
+
+ {/* Header */}
+
+
+
+
+
+
+
+
+ Product Image Generator
+
+
+ Upload a product image and generate professional marketing
+ shots
+
+
+
+
+
+
+
+ Settings
+
+
+
+ Account
+
+
+
+ Home
+
+
+
+
+
+ {/* Top Row - Upload + 3 Generated Images */}
+
+ {/* Upload card stays square */}
+
+
+
+
+
+
+ {/* Bottom Row - Sequential Custom Cards */}
+
+ {Array.from({ length: 4 }).map((_, index) => {
+ // If there's a completed custom generation for this slot, show it
+ const runId = customGenerations.runIds[index];
+ const customAccessToken = customGenerations.accessTokens[index];
+ const customPrompt = customGenerations.prompts[index];
+
+ if (runId && customAccessToken && customPrompt) {
+ return (
+
+
+
+
Custom Scene
+
+ {customPrompt}
+
+
+
+ );
+ }
+
+ // If this is the next available slot and top row is complete, show custom prompt card
+ if (index === nextCustomCardIndex && topRowGenerationsComplete) {
+ return (
+
+ handleCustomGenerationComplete(
+ runId,
+ accessToken,
+ prompt,
+ index
+ )
+ }
+ />
+ );
+ }
+
+ // Otherwise show placeholder
+ return (
+
+
+
+
+ {index + 1}
+
+
+
+ {!topRowGenerationsComplete
+ ? "Complete upload and generation first"
+ : index === 0
+ ? "Ready for custom prompt"
+ : `Slot ${index + 1}`}
+
+
+
+ );
+ })}
+
+
+ {/* Footer */}
+
+
+ Powered by{" "}
+ Trigger.dev and{" "}
+ Flux AI
+
+
+
+
+
+ );
+}
diff --git a/product-image-generator/app/actions.ts b/product-image-generator/app/actions.ts
index c83a004..54b8bda 100644
--- a/product-image-generator/app/actions.ts
+++ b/product-image-generator/app/actions.ts
@@ -1,159 +1,74 @@
"use server";
import { auth, tasks } from "@trigger.dev/sdk";
-import type { uploadImageToR2 } from "../src/trigger/image-upload";
import type { generateAndUploadImage } from "../src/trigger/generate-and-upload-image";
+import type { uploadImageToR2 } from "../src/trigger/image-upload";
+import type { ProductAnalysis } from "./types/trigger";
-export async function createPublicAccessToken(runId: string) {
+export async function triggerUploadTask(payload: {
+ imageBuffer: string;
+ fileName: string;
+ contentType: string;
+}) {
try {
+ const handle = await tasks.trigger(
+ "upload-image-to-r2",
+ payload,
+ );
+
+ // Create a public access token for this specific run
const publicAccessToken = await auth.createPublicToken({
scopes: {
read: {
- runs: [runId],
+ runs: [handle.id],
},
},
});
- return {
- success: true as const,
- token: publicAccessToken,
- };
- } catch (error) {
- console.error("Error creating public access token:", error);
- return {
- success: false as const,
- error: "Failed to create access token",
- };
- }
-}
-
-export async function uploadImageToR2Action(formData: FormData) {
- try {
- const file = formData.get("image") as File;
- if (!file) {
- return {
- success: false as const,
- error: "No file provided",
- };
- }
-
- // Convert file to base64
- const bytes = await file.arrayBuffer();
- const buffer = Buffer.from(bytes);
- const base64 = buffer.toString("base64");
-
- const handle = await tasks.trigger(
- "upload-image-to-r2",
- {
- imageBuffer: base64,
- fileName: file.name,
- contentType: file.type,
- },
- );
-
- // Create a public access token for this specific run
- const tokenResult = await createPublicAccessToken(handle.id);
-
- if (!tokenResult.success) {
- return {
- success: false as const,
- error: tokenResult.error,
- };
- }
-
return {
success: true as const,
runId: handle.id,
- accessToken: tokenResult.token,
+ publicAccessToken,
};
} catch (error) {
- console.error("Error triggering image upload task:", error);
- return {
- success: false as const,
- error: "Failed to upload image",
- };
+ console.error("Error triggering upload task:", error);
+ return { success: false as const, error: "Failed to trigger upload task" };
}
}
-export async function generateSingleImageAction(
- baseImageUrl: string,
- productAnalysis: any, // Structured analysis from upload task
- promptId: string,
-) {
+export async function triggerGenerationTask(payload: {
+ promptStyle: string;
+ baseImageUrl: string;
+ productAnalysis: ProductAnalysis;
+ customPrompt?: string;
+ model?: "flux";
+ size?: "1024x1792";
+}) {
try {
const handle = await tasks.trigger(
"generate-and-upload-image",
- {
- promptStyle: promptId, // isolated-table, lifestyle-scene, hero-shot
- baseImageUrl,
- productAnalysis,
- model: "flux",
- size: "1024x1024",
- },
+ payload,
);
// Create a public access token for this specific run
- const tokenResult = await createPublicAccessToken(handle.id);
-
- if (!tokenResult.success) {
- return {
- success: false as const,
- error: tokenResult.error,
- };
- }
-
- return {
- success: true as const,
- runId: handle.id,
- accessToken: tokenResult.token,
- };
- } catch (error) {
- console.error("Failed to trigger single image generation:", error);
- return {
- success: false as const,
- error: "Failed to trigger image generation",
- };
- }
-}
-
-export async function generateCustomImageAction(
- baseImageUrl: string,
- productAnalysis: any, // Structured analysis from upload task
- customPrompt: string,
-) {
- try {
- const handle = await tasks.trigger(
- "generate-and-upload-image",
- {
- promptStyle: "custom", // Custom prompt style
- baseImageUrl,
- productAnalysis,
- customPrompt, // User's custom prompt
- model: "flux",
- size: "1024x1024",
+ const publicAccessToken = await auth.createPublicToken({
+ scopes: {
+ read: {
+ runs: [handle.id],
+ },
},
- );
-
- // Create a public access token for this specific run
- const tokenResult = await createPublicAccessToken(handle.id);
-
- if (!tokenResult.success) {
- return {
- success: false as const,
- error: tokenResult.error,
- };
- }
+ });
return {
success: true as const,
runId: handle.id,
- accessToken: tokenResult.token,
+ publicAccessToken,
};
} catch (error) {
- console.error("Failed to trigger custom image generation:", error);
+ console.error("Error triggering generation task:", error);
return {
success: false as const,
- error: "Failed to trigger image generation",
+ error: "Failed to trigger generation task",
};
}
}
diff --git a/product-image-generator/app/components/CustomPromptCard.tsx b/product-image-generator/app/components/CustomPromptCard.tsx
index 004e387..f83da26 100644
--- a/product-image-generator/app/components/CustomPromptCard.tsx
+++ b/product-image-generator/app/components/CustomPromptCard.tsx
@@ -1,14 +1,24 @@
"use client";
+import { useTaskTrigger, useRealtimeRun } from "@trigger.dev/react-hooks";
import { RefreshCw, Send } from "lucide-react";
-import { useState } from "react";
-import { generateCustomImageAction } from "../actions";
+import { useState, useEffect } from "react";
+import type { generateAndUploadImage } from "../../src/trigger/generate-and-upload-image";
+import type { ProductAnalysis } from "../types/trigger";
+
+type TaskRun = {
+ id?: string;
+ status?: string;
+ output?: unknown;
+ metadata?: unknown;
+};
import { Button } from "./ui/button";
import { Card } from "./ui/card";
interface CustomPromptCardProps {
+ triggerToken: string;
baseImageUrl: string | null;
- productAnalysis: any | null;
+ productAnalysis: ProductAnalysis | null;
onGenerationComplete?: (
runId: string,
accessToken: string,
@@ -17,14 +27,32 @@ interface CustomPromptCardProps {
}
export default function CustomPromptCard({
+ triggerToken,
baseImageUrl,
productAnalysis,
onGenerationComplete,
}: CustomPromptCardProps) {
const [customPrompt, setCustomPrompt] = useState("");
- const [isGenerating, setIsGenerating] = useState(false);
- const [runId, setRunId] = useState(null);
- const [accessToken, setAccessToken] = useState(null);
+
+ // Use task trigger hook for generation
+ const { submit, handle, error, isLoading } = useTaskTrigger<
+ typeof generateAndUploadImage
+ >("generate-and-upload-image", {
+ accessToken: triggerToken,
+ });
+
+ // Subscribe to the run using the handle's public access token
+ const { run, error: realtimeError } = useRealtimeRun<
+ typeof generateAndUploadImage
+ >(handle?.id, {
+ accessToken: handle?.publicAccessToken,
+ enabled: !!handle,
+ });
+
+ // Notify parent when generation completes
+ if (run?.status === "COMPLETED" && run.id && onGenerationComplete) {
+ onGenerationComplete(run.id, triggerToken ?? "", customPrompt);
+ }
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -33,28 +61,22 @@ export default function CustomPromptCard({
return;
}
- setIsGenerating(true);
+ if (!triggerToken) {
+ console.error("Access token not available");
+ return;
+ }
try {
- const result = await generateCustomImageAction(
+ submit({
+ promptStyle: "custom",
baseImageUrl,
productAnalysis,
- customPrompt.trim()
- );
-
- if (result.success) {
- setRunId(result.runId);
- setAccessToken(result.accessToken);
- onGenerationComplete?.(
- result.runId,
- result.accessToken,
- customPrompt.trim()
- );
- }
+ customPrompt: customPrompt.trim(),
+ model: "flux",
+ size: "1024x1024",
+ });
} catch (error) {
console.error("Failed to generate custom image:", error);
- } finally {
- setIsGenerating(false);
}
};
@@ -63,28 +85,22 @@ export default function CustomPromptCard({
return;
}
- setIsGenerating(true);
+ if (!triggerToken) {
+ console.error("Access token not available");
+ return;
+ }
try {
- const result = await generateCustomImageAction(
+ submit({
+ promptStyle: "custom",
baseImageUrl,
productAnalysis,
- customPrompt.trim()
- );
-
- if (result.success) {
- setRunId(result.runId);
- setAccessToken(result.accessToken);
- onGenerationComplete?.(
- result.runId,
- result.accessToken,
- customPrompt.trim()
- );
- }
+ customPrompt: customPrompt.trim(),
+ model: "flux",
+ size: "1024x1024",
+ });
} catch (error) {
console.error("Failed to regenerate custom image:", error);
- } finally {
- setIsGenerating(false);
}
};
@@ -107,7 +123,7 @@ export default function CustomPromptCard({
placeholder="Describe a scene or setting for this product (e.g., 'product on a wooden table with natural lighting in a modern kitchen', 'product being used by someone outdoors', 'product in a luxury bathroom setting')"
value={customPrompt}
onChange={(e) => setCustomPrompt(e.target.value)}
- disabled={isGenerating}
+ disabled={isLoading}
className="w-full h-20 px-3 py-2 text-sm border border-input bg-transparent rounded-md shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 resize-none"
rows={3}
/>
@@ -116,10 +132,10 @@ export default function CustomPromptCard({
- {isGenerating ? (
+ {isLoading ? (
<>
Generating...
@@ -132,12 +148,12 @@ export default function CustomPromptCard({
)}
- {runId && (
+ {run?.id && (
diff --git a/product-image-generator/app/components/GeneratedCard.tsx b/product-image-generator/app/components/GeneratedCard.tsx
index 240fe48..76b4ce7 100644
--- a/product-image-generator/app/components/GeneratedCard.tsx
+++ b/product-image-generator/app/components/GeneratedCard.tsx
@@ -2,34 +2,93 @@
import { Button } from "./ui/button";
import { Card } from "./ui/card";
-import { Download, RefreshCw, Expand, ImageIcon } from "lucide-react";
-import { useState } from "react";
+import { Download, RefreshCw, Expand, ImageIcon, Sparkles } from "lucide-react";
+import { useState, useEffect, useCallback } from "react";
import { useRealtimeRun } from "@trigger.dev/react-hooks";
+import { triggerGenerationTask } from "../actions";
+import type { generateAndUploadImage } from "../../src/trigger/generate-and-upload-image";
+import type { ProductAnalysis } from "../types/trigger";
+
+type TaskRun = {
+ id?: string;
+ status?: string;
+ output?: unknown;
+ metadata?: unknown;
+};
interface GeneratedCardProps {
- runId: string | null;
- accessToken: string | null;
+ triggerToken: string;
+ baseImageUrl: string | null;
+ productAnalysis: ProductAnalysis | null;
promptId: string;
promptTitle: string;
- onRetry?: () => void;
}
export default function GeneratedCard({
- runId,
- accessToken,
+ triggerToken,
+ baseImageUrl,
+ productAnalysis,
promptId,
promptTitle,
- onRetry,
}: GeneratedCardProps) {
const [generatedImageUrl, setGeneratedImageUrl] = useState(
null
);
+ const [hasTriggered, setHasTriggered] = useState(false);
+
+ const [isGenerating, setIsGenerating] = useState(false);
+ const [runId, setRunId] = useState(null);
+ const [publicAccessToken, setPublicAccessToken] = useState(
+ null
+ );
+
+ // Subscribe to the run if we have a runId and token
+ const { run, error } = useRealtimeRun(
+ runId ?? undefined,
+ {
+ accessToken: publicAccessToken ?? "",
+ enabled: Boolean(runId && publicAccessToken),
+ }
+ );
+
+ const handleGenerate = useCallback(async () => {
+ if (!baseImageUrl || !productAnalysis || hasTriggered) {
+ return;
+ }
- // Use the React hook for realtime run subscription
- const { run, error } = useRealtimeRun(runId || undefined, {
- accessToken: accessToken || undefined,
- enabled: !!(runId && accessToken), // Only subscribe if we have both
- });
+ try {
+ setHasTriggered(true);
+ setIsGenerating(true);
+
+ const result = await triggerGenerationTask({
+ promptStyle: promptId,
+ baseImageUrl,
+ productAnalysis,
+ model: "flux",
+ size: "1024x1792",
+ });
+
+ if (result.success) {
+ setRunId(result.runId);
+ setPublicAccessToken(result.publicAccessToken);
+ } else {
+ console.error("Generation failed:", result.error);
+ setHasTriggered(false);
+ setIsGenerating(false);
+ }
+ } catch (error) {
+ console.error("Failed to generate image:", error);
+ setHasTriggered(false);
+ setIsGenerating(false);
+ }
+ }, [baseImageUrl, productAnalysis, hasTriggered, promptId]);
+
+ // Auto-trigger when data is available
+ useEffect(() => {
+ if (!hasTriggered && baseImageUrl && productAnalysis) {
+ handleGenerate();
+ }
+ }, [hasTriggered, baseImageUrl, productAnalysis, handleGenerate]);
// Extract progress information from run metadata
const progressData = run?.metadata?.progress as
@@ -40,11 +99,12 @@ export default function GeneratedCard({
}
| undefined;
- const isLoading = run?.status === "EXECUTING" || run?.status === "QUEUED";
+ const isTaskRunning =
+ isGenerating || run?.status === "EXECUTING" || run?.status === "QUEUED";
const generationProgress =
run?.status === "COMPLETED"
? "completed"
- : run?.status === "FAILED"
+ : run?.status === "FAILED" || error
? "failed"
: run?.status === "EXECUTING"
? "generating"
@@ -84,10 +144,12 @@ export default function GeneratedCard({
};
const handleRetry = () => {
- if (onRetry) {
- setGeneratedImageUrl(null);
- onRetry();
- }
+ setGeneratedImageUrl(null);
+ setHasTriggered(false);
+ setIsGenerating(false);
+ setRunId(null);
+ setPublicAccessToken(null);
+ handleGenerate();
};
return (
@@ -138,19 +200,25 @@ export default function GeneratedCard({
{error?.message || "Generation failed"}
- {onRetry && (
-
-
- Retry
-
- )}
+
+
+ Retry
+
) : (
// Show loading/waiting state
- {isLoading && generationProgress === "generating" ? (
+ {isTaskRunning && generationProgress === "generating" ? (
+ ) : baseImageUrl && productAnalysis && !hasTriggered ? (
+
+
+
) : (
)}
@@ -164,7 +232,9 @@ export default function GeneratedCard({
? "Generating..."
: generationProgress === "idle"
? "Waiting to start..."
- : "Ready")}
+ : baseImageUrl && productAnalysis && !hasTriggered
+ ? "Click to generate"
+ : "Waiting for upload...")}
{progressData && generationProgress === "generating" && (
<>
diff --git a/product-image-generator/app/components/UploadCard.tsx b/product-image-generator/app/components/UploadCard.tsx
index a148877..9065885 100644
--- a/product-image-generator/app/components/UploadCard.tsx
+++ b/product-image-generator/app/components/UploadCard.tsx
@@ -1,119 +1,106 @@
"use client";
-import { Button } from "./ui/button";
-import { Card } from "./ui/card";
import { Upload } from "lucide-react";
import { useRef, useState, useEffect } from "react";
-import { uploadImageToR2Action } from "../actions";
-import { runs, configure } from "@trigger.dev/sdk";
+import type {
+ ProductAnalysis,
+ UploadTaskMetadata,
+ UploadTaskOutput,
+} from "../types/trigger";
+import { Button } from "./ui/button";
+import { Card } from "./ui/card";
+import { useRealtimeRun } from "@trigger.dev/react-hooks";
+import { triggerUploadTask } from "../actions";
+import type { uploadImageToR2 } from "../../src/trigger/image-upload";
+
+type TaskRun = {
+ id?: string;
+ status?: string;
+ output?: unknown;
+ metadata?: unknown;
+};
interface UploadCardProps {
- onUploadComplete?: (imageUrl: string, productAnalysis?: any) => void;
+ onUploadComplete?: (
+ imageUrl: string,
+ productAnalysis?: ProductAnalysis
+ ) => void;
}
export default function UploadCard({ onUploadComplete }: UploadCardProps) {
const [isDragOver, setIsDragOver] = useState(false);
- const [isLoading, setIsLoading] = useState(false);
+ const [hasNotifiedComplete, setHasNotifiedComplete] = useState(false);
+ const [isUploading, setIsUploading] = useState(false);
const [runId, setRunId] = useState
(null);
- const [accessToken, setAccessToken] = useState(null);
- const [error, setError] = useState(null);
- const [uploadedImageUrl, setUploadedImageUrl] = useState(null);
- const [uploadProgress, setUploadProgress] = useState("idle");
- const [progressMessage, setProgressMessage] = useState("");
- const [progressStep, setProgressStep] = useState<{
- step: number;
- total: number;
- } | null>(null);
+ const [publicAccessToken, setPublicAccessToken] = useState(
+ null
+ );
const fileInputRef = useRef(null);
- // Subscribe to run updates when runId and accessToken are available
- useEffect(() => {
- if (!runId || !accessToken) return;
-
- const subscribeToRun = async () => {
- try {
- // Configure the SDK with the public access token for client-side authentication
- configure({
- accessToken: accessToken,
- });
-
- for await (const run of runs.subscribeToRun(runId)) {
- // Update progress from metadata
- if (run.metadata?.progress) {
- const progress = run.metadata.progress as {
- step: number;
- total: number;
- message: string;
- };
- setProgressMessage(progress.message);
- setProgressStep({ step: progress.step, total: progress.total });
- }
-
- // Handle completion
- if (run.status === "COMPLETED" && run.output) {
- setUploadedImageUrl(run.output.publicUrl);
- setUploadProgress("completed");
- setProgressMessage("Upload completed!");
- setIsLoading(false);
-
- // Notify parent component with URL and analysis
- if (onUploadComplete && run.output.publicUrl) {
- const output = run.output as any;
- const metadataResult = run.metadata?.result as any;
- const productAnalysis =
- output.productAnalysis || metadataResult?.productAnalysis;
- onUploadComplete(run.output.publicUrl, productAnalysis);
- }
- break;
- } else if (run.status === "FAILED") {
- const errorMsg = run.metadata?.error || "Upload failed";
- setError(typeof errorMsg === "string" ? errorMsg : "Upload failed");
- setUploadProgress("idle");
- setProgressMessage("");
- setIsLoading(false);
- break;
- }
- }
- } catch (err) {
- setError("Failed to get task updates");
- setUploadProgress("idle");
- setIsLoading(false);
- }
- };
+ // Subscribe to the run if we have a runId and token
+ const { run, error } = useRealtimeRun(
+ runId ?? undefined,
+ {
+ accessToken: publicAccessToken ?? "",
+ enabled: Boolean(runId && publicAccessToken),
+ }
+ );
- subscribeToRun();
- }, [runId, accessToken]);
+ // Derive UI state from run with proper types
+ const meta = run?.metadata as UploadTaskMetadata | undefined;
+ const progress = meta?.progress;
+ const output = run?.output as UploadTaskOutput | undefined;
+ const metadataResult = meta?.result;
+ const publicUrl = output?.publicUrl ?? metadataResult?.publicUrl;
+ const productAnalysis =
+ output?.productAnalysis ?? metadataResult?.productAnalysis;
- // Upload image with realtime subscription
+ // Notify parent when completed (only once)
+ useEffect(() => {
+ if (
+ run?.status === "COMPLETED" &&
+ publicUrl &&
+ onUploadComplete &&
+ !hasNotifiedComplete
+ ) {
+ onUploadComplete(publicUrl, productAnalysis);
+ setHasNotifiedComplete(true);
+ }
+ }, [
+ run?.status,
+ publicUrl,
+ productAnalysis,
+ onUploadComplete,
+ hasNotifiedComplete,
+ output,
+ metadataResult,
+ run,
+ ]);
+
+ // Upload image
const uploadImage = async (file: File) => {
- setIsLoading(true);
- setError(null);
- setUploadProgress("uploading");
- setUploadedImageUrl(null);
- setProgressMessage("");
- setProgressStep(null);
-
try {
- // Create FormData and upload
- const formData = new FormData();
- formData.append("image", file);
-
- const result = await uploadImageToR2Action(formData);
-
- if (result.success && result.runId && result.accessToken) {
+ // Convert file to base64
+ const bytes = await file.arrayBuffer();
+ const buffer = Buffer.from(bytes);
+ const base64 = buffer.toString("base64");
+
+ const result = await triggerUploadTask({
+ imageBuffer: base64,
+ fileName: file.name,
+ contentType: file.type,
+ });
+
+ if (result.success) {
setRunId(result.runId);
- setAccessToken(result.accessToken);
- setUploadProgress("processing");
- // The useEffect will handle the subscription
+ setPublicAccessToken(result.publicAccessToken);
} else {
- setError(result.error || "Failed to upload image");
- setUploadProgress("idle");
- setIsLoading(false);
+ console.error("Upload failed:", result.error);
+ setIsUploading(false);
}
} catch (err) {
- setError(err instanceof Error ? err.message : "Failed to upload image");
- setUploadProgress("idle");
- setIsLoading(false);
+ console.error("Upload failed:", err);
+ setIsUploading(false);
}
};
@@ -147,13 +134,7 @@ export default function UploadCard({ onUploadComplete }: UploadCardProps) {
};
const handleReset = () => {
- setUploadedImageUrl(null);
- setUploadProgress("idle");
- setRunId(null);
- setAccessToken(null);
- setError(null);
- setProgressMessage("");
- setProgressStep(null);
+ setHasNotifiedComplete(false);
};
return (
@@ -162,25 +143,31 @@ export default function UploadCard({ onUploadComplete }: UploadCardProps) {
isDragOver
? "border-primary bg-primary/5"
: "border-primary/30 bg-card hover:border-primary/50"
- } ${isLoading ? "opacity-50 pointer-events-none" : ""}`}
+ } ${
+ isUploading || run?.status === "EXECUTING" || run?.status === "QUEUED"
+ ? "opacity-50 pointer-events-none"
+ : ""
+ }`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
- onClick={() => !uploadedImageUrl && fileInputRef.current?.click()}
+ onClick={() => !publicUrl && fileInputRef.current?.click()}
>
- {uploadedImageUrl ? (
+ {publicUrl ? (
// Show uploaded image
- {uploadProgress === "processing" && (
+ {(isUploading ||
+ run?.status === "EXECUTING" ||
+ run?.status === "QUEUED") && (
@@ -198,50 +185,53 @@ export default function UploadCard({ onUploadComplete }: UploadCardProps) {
- ) : (
- // Show upload area
+ ) : isUploading || run?.id ? (
+ // Show progress state when loading or run exists
-
- {isLoading ? (
-
- ) : (
-
- )}
+
- {uploadProgress === "uploading"
- ? "Uploading..."
- : uploadProgress === "processing"
- ? progressMessage || "Processing..."
- : "Drag and drop an image here"}
+ {progress?.message || "Processing..."}
- {isLoading
- ? progressStep
- ? `Step ${progressStep.step} of ${progressStep.total}`
- : "Please wait"
- : "or click to browse"}
+ {progress
+ ? `Step ${progress.step} of ${progress.total}`
+ : "Please wait"}
- {progressStep && uploadProgress === "processing" && (
+ {progress && (
)}
- {runId && uploadProgress !== "idle" && (
-
Run ID: {runId}
+ {run?.id && (
+
Run ID: {run.id}
)}
- {error &&
Error: {error}
}
+ {error && (
+
Error: {error.message}
+ )}
+
+ ) : (
+ // Show upload area
+
+
+
+
+
+ Drag and drop an image here
+
+
or click to browse
)}
(null);
- const [productAnalysis, setProductAnalysis] = useState
(null);
- const [generationRunIds, setGenerationRunIds] = useState<{
- [key: string]: string | null;
- }>({
- "isolated-table": null,
- "lifestyle-scene": null,
- "hero-shot": null,
- });
- const [generationAccessTokens, setGenerationAccessTokens] = useState<{
- [key: string]: string | null;
- }>({
- "isolated-table": null,
- "lifestyle-scene": null,
- "hero-shot": null,
- custom: null,
- });
-
- // Track custom generations for bottom row
- const [customGenerations, setCustomGenerations] = useState<{
- runIds: (string | null)[];
- accessTokens: (string | null)[];
- prompts: (string | null)[];
- }>({
- runIds: [null, null, null],
- accessTokens: [null, null, null],
- prompts: [null, null, null],
- });
-
- const promptTitles = {
- "isolated-table": "Table Shot",
- "lifestyle-scene": "Lifestyle Scene",
- "hero-shot": "Hero Shot",
- custom: "Custom Prompt",
- };
-
- const handleUploadComplete = async (imageUrl: string, analysis?: any) => {
- try {
- // Store the uploaded image URL and analysis for retries
- setUploadedImageUrl(imageUrl);
- setProductAnalysis(analysis);
-
- // Only trigger generations if we have product analysis
- if (!analysis) {
- console.error("No product analysis available");
- return;
- }
-
- // Trigger all 3 generations individually for better progress tracking
- const promptIds = ["isolated-table", "lifestyle-scene", "hero-shot"];
-
- for (const promptId of promptIds) {
- const result = await generateSingleImageAction(
- imageUrl,
- analysis,
- promptId
- );
-
- if (result.success) {
- console.log(`Successfully triggered generation for ${promptId}:`, {
- runId: result.runId,
- hasAccessToken: !!result.accessToken,
- });
- setGenerationRunIds((prev) => ({
- ...prev,
- [promptId]: result.runId,
- }));
- setGenerationAccessTokens((prev) => ({
- ...prev,
- [promptId]: result.accessToken,
- }));
- } else {
- console.error(
- `Failed to start generation for ${promptId}:`,
- result.error
- );
- }
- }
- } catch (error) {
- console.error("Failed to start image generations:", error);
- }
- };
-
- const handleRetryGeneration = async (promptId: string) => {
- if (!uploadedImageUrl || !productAnalysis) {
- console.error(
- "No base image URL or product analysis available for retry"
- );
- return;
- }
-
- try {
- // Reset the specific generation
- setGenerationRunIds((prev) => ({ ...prev, [promptId]: null }));
- setGenerationAccessTokens((prev) => ({ ...prev, [promptId]: null }));
-
- // Trigger the specific generation again with product analysis
- const result = await generateSingleImageAction(
- uploadedImageUrl,
- productAnalysis,
- promptId
- );
-
- if (result.success) {
- setGenerationRunIds((prev) => ({ ...prev, [promptId]: result.runId }));
- setGenerationAccessTokens((prev) => ({
- ...prev,
- [promptId]: result.accessToken,
- }));
- } else {
- console.error(
- `Failed to retry generation for ${promptId}:`,
- result.error
- );
- }
- } catch (error) {
- console.error(`Failed to retry generation for ${promptId}:`, error);
- }
- };
-
- const handleCustomGenerationComplete = (
- runId: string,
- accessToken: string,
- prompt: string,
- cardIndex: number
- ) => {
- setCustomGenerations((prev) => ({
- runIds: prev.runIds.map((id, i) => (i === cardIndex ? runId : id)),
- accessTokens: prev.accessTokens.map((token, i) =>
- i === cardIndex ? accessToken : token
- ),
- prompts: prev.prompts.map((p, i) => (i === cardIndex ? prompt : p)),
- }));
- };
-
- // Check if top row generations are complete
- const topRowGenerationsComplete =
- generationRunIds["isolated-table"] &&
- generationRunIds["lifestyle-scene"] &&
- generationRunIds["hero-shot"];
-
- // Find next available custom card slot
- const getNextCustomCardIndex = () => {
- return customGenerations.runIds.findIndex((id) => id === null);
- };
-
- const nextCustomCardIndex = getNextCustomCardIndex();
- const hasAvailableCustomSlot = nextCustomCardIndex !== -1;
-
- return (
-
- {/* Navigation Header */}
-
-
- {/* Main Content */}
-
-
-
-
- Product Image Generator
-
-
- Upload a product image and automatically generate professional
- marketing shots
-
-
-
- {/* Top Row - Upload + 3 Generated Images */}
-
- {/* Upload card stays square */}
-
- handleRetryGeneration("isolated-table")}
- />
- handleRetryGeneration("lifestyle-scene")}
- />
- handleRetryGeneration("hero-shot")}
- />
-
-
- {/* Bottom Row - Sequential Custom Cards */}
-
- {Array.from({ length: 4 }).map((_, index) => {
- const runId = customGenerations.runIds[index];
- const accessToken = customGenerations.accessTokens[index];
- const prompt = customGenerations.prompts[index];
-
- // If this slot has a generation, show the generated card
- if (runId && accessToken) {
- return (
-
{
- // Reset this specific custom generation
- setCustomGenerations((prev) => ({
- runIds: prev.runIds.map((id, i) =>
- i === index ? null : id
- ),
- accessTokens: prev.accessTokens.map((token, i) =>
- i === index ? null : token
- ),
- prompts: prev.prompts.map((p, i) =>
- i === index ? null : p
- ),
- }));
- }}
- />
- );
- }
-
- // If this is the next available slot and top row is complete, show custom prompt card
- if (index === nextCustomCardIndex && topRowGenerationsComplete) {
- return (
-
- handleCustomGenerationComplete(
- runId,
- accessToken,
- prompt,
- index
- )
- }
- />
- );
- }
-
- // Otherwise show placeholder
- return (
-
-
-
-
-
-
-
- {topRowGenerationsComplete
- ? "Create custom scenario"
- : ""}
-
-
-
-
- );
- })}
-
-
- {/* Action Buttons */}
-
-
-
- Upload New Image
-
-
-
- Settings
-
-
-
-
-
- );
+import { auth } from "@trigger.dev/sdk";
+import ProductImageGenerator from "./ProductImageGenerator";
+
+export default async function Page() {
+ const triggerToken = await auth.createTriggerPublicToken([
+ "generate-and-upload-image",
+ "upload-image-to-r2",
+ ]);
+ return ;
}
diff --git a/product-image-generator/app/types/trigger.ts b/product-image-generator/app/types/trigger.ts
new file mode 100644
index 0000000..746c019
--- /dev/null
+++ b/product-image-generator/app/types/trigger.ts
@@ -0,0 +1,60 @@
+// Trigger.dev task types for proper TypeScript support
+
+export interface TaskProgress {
+ step: number;
+ total: number;
+ message: string;
+}
+
+export interface ProductAnalysis {
+ exact_product_name: string;
+ model_number: string;
+ material: string;
+ colors: string[];
+ shape: string;
+ size_proportions: string;
+ functional_elements: string[];
+ surface_finish: string;
+ text_branding: string;
+ unique_features: string[];
+ product_category: string;
+}
+
+export interface UploadTaskOutput {
+ success: boolean;
+ bucket?: string;
+ r2Key: string;
+ publicUrl: string;
+ fileSize: number;
+ contentType: string;
+ fileName: string;
+ productAnalysis: ProductAnalysis;
+}
+
+export interface UploadTaskMetadata {
+ status?: string;
+ progress?: TaskProgress;
+ result?: {
+ publicUrl: string;
+ r2Key: string;
+ fileSize: number;
+ fileName: string;
+ productAnalysis: ProductAnalysis;
+ };
+ error?: string;
+}
+
+export interface GenerationTaskOutput {
+ success: boolean;
+ imageUrl: string;
+ prompt: string;
+ model: string;
+ size: string;
+ publicUrl?: string;
+}
+
+export interface GenerationTaskMetadata {
+ status?: string;
+ progress?: TaskProgress;
+ error?: string;
+}
From cdf8192c4154ef9f472b36c2a20408134c3b4648 Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Wed, 10 Sep 2025 13:28:37 +0100
Subject: [PATCH 043/232] Simplified auth
---
.../app/ProductImageGenerator.tsx | 30 +-----
.../app/components/CustomPromptCard.tsx | 99 ++++++++++---------
.../app/components/GeneratedCard.tsx | 2 -
product-image-generator/app/page.tsx | 7 +-
4 files changed, 60 insertions(+), 78 deletions(-)
diff --git a/product-image-generator/app/ProductImageGenerator.tsx b/product-image-generator/app/ProductImageGenerator.tsx
index b20eef5..b90146c 100644
--- a/product-image-generator/app/ProductImageGenerator.tsx
+++ b/product-image-generator/app/ProductImageGenerator.tsx
@@ -9,13 +9,7 @@ import GeneratedCard from "./components/GeneratedCard";
import CustomPromptCard from "./components/CustomPromptCard";
import type { ProductAnalysis } from "./types/trigger";
-interface ProductImageGeneratorProps {
- triggerToken: string;
-}
-
-export default function ProductImageGenerator({
- triggerToken: accessToken,
-}: ProductImageGeneratorProps) {
+export default function ProductImageGenerator() {
const [uploadedImageUrl, setUploadedImageUrl] = useState(null);
const [productAnalysis, setProductAnalysis] =
useState(null);
@@ -23,11 +17,9 @@ export default function ProductImageGenerator({
// Track custom generations for bottom row
const [customGenerations, setCustomGenerations] = useState<{
runIds: (string | null)[];
- accessTokens: (string | null)[];
prompts: (string | null)[];
}>({
runIds: [null, null, null, null],
- accessTokens: [null, null, null, null],
prompts: [null, null, null, null],
});
@@ -43,15 +35,11 @@ export default function ProductImageGenerator({
const handleCustomGenerationComplete = (
runId: string,
- accessToken: string,
prompt: string,
index: number
) => {
setCustomGenerations((prev) => ({
runIds: prev.runIds.map((id, i) => (i === index ? runId : id)),
- accessTokens: prev.accessTokens.map((token, i) =>
- i === index ? accessToken : token
- ),
prompts: prev.prompts.map((p, i) => (i === index ? prompt : p)),
}));
};
@@ -120,21 +108,18 @@ export default function ProductImageGenerator({
{/* Upload card stays square */}
{
// If there's a completed custom generation for this slot, show it
const runId = customGenerations.runIds[index];
- const customAccessToken = customGenerations.accessTokens[index];
const customPrompt = customGenerations.prompts[index];
- if (runId && customAccessToken && customPrompt) {
+ if (runId && customPrompt) {
return (
- handleCustomGenerationComplete(
- runId,
- accessToken,
- prompt,
- index
- )
+ onGenerationComplete={(runId, prompt) =>
+ handleCustomGenerationComplete(runId, prompt, index)
}
/>
);
diff --git a/product-image-generator/app/components/CustomPromptCard.tsx b/product-image-generator/app/components/CustomPromptCard.tsx
index f83da26..13dfdb3 100644
--- a/product-image-generator/app/components/CustomPromptCard.tsx
+++ b/product-image-generator/app/components/CustomPromptCard.tsx
@@ -1,8 +1,9 @@
"use client";
-import { useTaskTrigger, useRealtimeRun } from "@trigger.dev/react-hooks";
+import { useRealtimeRun } from "@trigger.dev/react-hooks";
import { RefreshCw, Send } from "lucide-react";
import { useState, useEffect } from "react";
+import { triggerGenerationTask } from "../actions";
import type { generateAndUploadImage } from "../../src/trigger/generate-and-upload-image";
import type { ProductAnalysis } from "../types/trigger";
@@ -16,43 +17,38 @@ import { Button } from "./ui/button";
import { Card } from "./ui/card";
interface CustomPromptCardProps {
- triggerToken: string;
baseImageUrl: string | null;
productAnalysis: ProductAnalysis | null;
- onGenerationComplete?: (
- runId: string,
- accessToken: string,
- prompt: string
- ) => void;
+ onGenerationComplete?: (runId: string, prompt: string) => void;
}
export default function CustomPromptCard({
- triggerToken,
baseImageUrl,
productAnalysis,
onGenerationComplete,
}: CustomPromptCardProps) {
const [customPrompt, setCustomPrompt] = useState("");
+ const [isGenerating, setIsGenerating] = useState(false);
+ const [runId, setRunId] = useState(null);
+ const [publicAccessToken, setPublicAccessToken] = useState(
+ null
+ );
- // Use task trigger hook for generation
- const { submit, handle, error, isLoading } = useTaskTrigger<
- typeof generateAndUploadImage
- >("generate-and-upload-image", {
- accessToken: triggerToken,
- });
-
- // Subscribe to the run using the handle's public access token
- const { run, error: realtimeError } = useRealtimeRun<
- typeof generateAndUploadImage
- >(handle?.id, {
- accessToken: handle?.publicAccessToken,
- enabled: !!handle,
- });
+ // Subscribe to the run if we have a runId and token
+ const { run, error } = useRealtimeRun(
+ runId ?? undefined,
+ {
+ accessToken: publicAccessToken ?? "",
+ enabled: Boolean(runId && publicAccessToken),
+ }
+ );
// Notify parent when generation completes
- if (run?.status === "COMPLETED" && run.id && onGenerationComplete) {
- onGenerationComplete(run.id, triggerToken ?? "", customPrompt);
- }
+ useEffect(() => {
+ if (run?.status === "COMPLETED" && run?.id && onGenerationComplete) {
+ onGenerationComplete(run.id, customPrompt);
+ }
+ }, [run?.status, run?.id, onGenerationComplete, customPrompt]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -61,22 +57,28 @@ export default function CustomPromptCard({
return;
}
- if (!triggerToken) {
- console.error("Access token not available");
- return;
- }
-
try {
- submit({
+ setIsGenerating(true);
+
+ const result = await triggerGenerationTask({
promptStyle: "custom",
baseImageUrl,
productAnalysis,
customPrompt: customPrompt.trim(),
model: "flux",
- size: "1024x1024",
+ size: "1024x1792",
});
+
+ if (result.success) {
+ setRunId(result.runId);
+ setPublicAccessToken(result.publicAccessToken);
+ } else {
+ console.error("Generation failed:", result.error);
+ setIsGenerating(false);
+ }
} catch (error) {
console.error("Failed to generate custom image:", error);
+ setIsGenerating(false);
}
};
@@ -85,26 +87,35 @@ export default function CustomPromptCard({
return;
}
- if (!triggerToken) {
- console.error("Access token not available");
- return;
- }
-
try {
- submit({
+ setIsGenerating(true);
+ setRunId(null);
+ setPublicAccessToken(null);
+
+ const result = await triggerGenerationTask({
promptStyle: "custom",
baseImageUrl,
productAnalysis,
customPrompt: customPrompt.trim(),
model: "flux",
- size: "1024x1024",
+ size: "1024x1792",
});
+
+ if (result.success) {
+ setRunId(result.runId);
+ setPublicAccessToken(result.publicAccessToken);
+ } else {
+ console.error("Generation failed:", result.error);
+ setIsGenerating(false);
+ }
} catch (error) {
console.error("Failed to regenerate custom image:", error);
+ setIsGenerating(false);
}
};
- const isDisabled = !baseImageUrl || !productAnalysis || !customPrompt.trim();
+ const isDisabled =
+ !baseImageUrl || !productAnalysis || !customPrompt.trim() || isGenerating;
return (
@@ -123,7 +134,7 @@ export default function CustomPromptCard({
placeholder="Describe a scene or setting for this product (e.g., 'product on a wooden table with natural lighting in a modern kitchen', 'product being used by someone outdoors', 'product in a luxury bathroom setting')"
value={customPrompt}
onChange={(e) => setCustomPrompt(e.target.value)}
- disabled={isLoading}
+ disabled={isGenerating}
className="w-full h-20 px-3 py-2 text-sm border border-input bg-transparent rounded-md shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 resize-none"
rows={3}
/>
@@ -132,10 +143,10 @@ export default function CustomPromptCard({
- {isLoading ? (
+ {isGenerating ? (
<>
Generating...
@@ -153,7 +164,7 @@ export default function CustomPromptCard({
type="button"
variant="outline"
onClick={handleRegenerate}
- disabled={isDisabled || isLoading}
+ disabled={isDisabled || isGenerating}
className="gap-2"
>
diff --git a/product-image-generator/app/components/GeneratedCard.tsx b/product-image-generator/app/components/GeneratedCard.tsx
index 76b4ce7..b5588fc 100644
--- a/product-image-generator/app/components/GeneratedCard.tsx
+++ b/product-image-generator/app/components/GeneratedCard.tsx
@@ -17,7 +17,6 @@ type TaskRun = {
};
interface GeneratedCardProps {
- triggerToken: string;
baseImageUrl: string | null;
productAnalysis: ProductAnalysis | null;
promptId: string;
@@ -25,7 +24,6 @@ interface GeneratedCardProps {
}
export default function GeneratedCard({
- triggerToken,
baseImageUrl,
productAnalysis,
promptId,
diff --git a/product-image-generator/app/page.tsx b/product-image-generator/app/page.tsx
index fb0738f..bb8fdb3 100644
--- a/product-image-generator/app/page.tsx
+++ b/product-image-generator/app/page.tsx
@@ -1,10 +1,5 @@
-import { auth } from "@trigger.dev/sdk";
import ProductImageGenerator from "./ProductImageGenerator";
export default async function Page() {
- const triggerToken = await auth.createTriggerPublicToken([
- "generate-and-upload-image",
- "upload-image-to-r2",
- ]);
- return ;
+ return ;
}
From 9b1dd9fb98bf98a71658cab87de9ac3ba6d591af Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Wed, 10 Sep 2025 13:49:19 +0100
Subject: [PATCH 044/232] Restore nav and layout
---
.../app/ProductImageGenerator.tsx | 96 +++++++++++--------
.../app/components/CustomPromptCard.tsx | 7 --
.../app/components/UploadCard.tsx | 53 ++++------
product-image-generator/app/page.tsx | 6 +-
4 files changed, 78 insertions(+), 84 deletions(-)
diff --git a/product-image-generator/app/ProductImageGenerator.tsx b/product-image-generator/app/ProductImageGenerator.tsx
index b90146c..34058b1 100644
--- a/product-image-generator/app/ProductImageGenerator.tsx
+++ b/product-image-generator/app/ProductImageGenerator.tsx
@@ -1,15 +1,21 @@
"use client";
+import { Home, ImageIcon, Settings, User } from "lucide-react";
import { useState } from "react";
-import { Home, ImageIcon, Settings, Upload, User } from "lucide-react";
+import CustomPromptCard from "./components/CustomPromptCard";
+import GeneratedCard from "./components/GeneratedCard";
import { Button } from "./components/ui/button";
import { Card } from "./components/ui/card";
import UploadCard from "./components/UploadCard";
-import GeneratedCard from "./components/GeneratedCard";
-import CustomPromptCard from "./components/CustomPromptCard";
import type { ProductAnalysis } from "./types/trigger";
-export default function ProductImageGenerator() {
+interface ProductImageGeneratorProps {
+ triggerToken: string;
+}
+
+export default function ProductImageGenerator({
+ triggerToken,
+}: ProductImageGeneratorProps) {
const [uploadedImageUrl, setUploadedImageUrl] = useState(null);
const [productAnalysis, setProductAnalysis] =
useState(null);
@@ -66,47 +72,57 @@ export default function ProductImageGenerator() {
return (
-
-
- {/* Header */}
-
-
-
-
-
-
-
-
- Product Image Generator
-
-
- Upload a product image and generate professional marketing
- shots
-
-
-
-
+ {/* Fixed Header */}
+
+
+
+
-
-
- Settings
-
-
-
- Account
-
-
-
- Home
-
+
+
+
+
ImageFlow
+
+
+
+
+ Home
+
+
+
+ Settings
+
+
+
+ Account
+
+
+
+
+
+
+ {/* Main Content */}
+
+
+ {/* Page Title */}
+
+
+ Product Image Generator
+
+
+ Upload a product image and generate professional marketing shots
+
{/* Top Row - Upload + 3 Generated Images */}
{/* Upload card stays square */}
-
+
-
+ {/*
Powered by{" "}
Trigger.dev and{" "}
Flux AI
-
+ */}
-
+
);
}
diff --git a/product-image-generator/app/components/CustomPromptCard.tsx b/product-image-generator/app/components/CustomPromptCard.tsx
index 13dfdb3..791cde8 100644
--- a/product-image-generator/app/components/CustomPromptCard.tsx
+++ b/product-image-generator/app/components/CustomPromptCard.tsx
@@ -6,13 +6,6 @@ import { useState, useEffect } from "react";
import { triggerGenerationTask } from "../actions";
import type { generateAndUploadImage } from "../../src/trigger/generate-and-upload-image";
import type { ProductAnalysis } from "../types/trigger";
-
-type TaskRun = {
- id?: string;
- status?: string;
- output?: unknown;
- metadata?: unknown;
-};
import { Button } from "./ui/button";
import { Card } from "./ui/card";
diff --git a/product-image-generator/app/components/UploadCard.tsx b/product-image-generator/app/components/UploadCard.tsx
index 9065885..af32232 100644
--- a/product-image-generator/app/components/UploadCard.tsx
+++ b/product-image-generator/app/components/UploadCard.tsx
@@ -9,42 +9,32 @@ import type {
} from "../types/trigger";
import { Button } from "./ui/button";
import { Card } from "./ui/card";
-import { useRealtimeRun } from "@trigger.dev/react-hooks";
-import { triggerUploadTask } from "../actions";
+import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
import type { uploadImageToR2 } from "../../src/trigger/image-upload";
-type TaskRun = {
- id?: string;
- status?: string;
- output?: unknown;
- metadata?: unknown;
-};
-
interface UploadCardProps {
+ triggerToken: string;
onUploadComplete?: (
imageUrl: string,
productAnalysis?: ProductAnalysis
) => void;
}
-export default function UploadCard({ onUploadComplete }: UploadCardProps) {
+export default function UploadCard({
+ triggerToken,
+ onUploadComplete,
+}: UploadCardProps) {
const [isDragOver, setIsDragOver] = useState(false);
const [hasNotifiedComplete, setHasNotifiedComplete] = useState(false);
- const [isUploading, setIsUploading] = useState(false);
- const [runId, setRunId] = useState
(null);
- const [publicAccessToken, setPublicAccessToken] = useState(
- null
- );
const fileInputRef = useRef(null);
- // Subscribe to the run if we have a runId and token
- const { run, error } = useRealtimeRun(
- runId ?? undefined,
- {
- accessToken: publicAccessToken ?? "",
- enabled: Boolean(runId && publicAccessToken),
- }
- );
+ // Use realtime task trigger hook for immediate triggering
+ const { submit, run, error, isLoading } = useRealtimeTaskTrigger<
+ typeof uploadImageToR2
+ >("upload-image-to-r2", {
+ accessToken: triggerToken,
+ enabled: Boolean(triggerToken),
+ });
// Derive UI state from run with proper types
const meta = run?.metadata as UploadTaskMetadata | undefined;
@@ -85,22 +75,13 @@ export default function UploadCard({ onUploadComplete }: UploadCardProps) {
const buffer = Buffer.from(bytes);
const base64 = buffer.toString("base64");
- const result = await triggerUploadTask({
+ submit({
imageBuffer: base64,
fileName: file.name,
contentType: file.type,
});
-
- if (result.success) {
- setRunId(result.runId);
- setPublicAccessToken(result.publicAccessToken);
- } else {
- console.error("Upload failed:", result.error);
- setIsUploading(false);
- }
} catch (err) {
console.error("Upload failed:", err);
- setIsUploading(false);
}
};
@@ -144,7 +125,7 @@ export default function UploadCard({ onUploadComplete }: UploadCardProps) {
? "border-primary bg-primary/5"
: "border-primary/30 bg-card hover:border-primary/50"
} ${
- isUploading || run?.status === "EXECUTING" || run?.status === "QUEUED"
+ isLoading || run?.status === "EXECUTING" || run?.status === "QUEUED"
? "opacity-50 pointer-events-none"
: ""
}`}
@@ -165,7 +146,7 @@ export default function UploadCard({ onUploadComplete }: UploadCardProps) {
transition: "opacity 0.3s ease-in-out",
}}
/>
- {(isUploading ||
+ {(isLoading ||
run?.status === "EXECUTING" ||
run?.status === "QUEUED") && (
@@ -185,7 +166,7 @@ export default function UploadCard({ onUploadComplete }: UploadCardProps) {
- ) : isUploading || run?.id ? (
+ ) : isLoading || run?.id ? (
// Show progress state when loading or run exists
diff --git a/product-image-generator/app/page.tsx b/product-image-generator/app/page.tsx
index bb8fdb3..e7f9fc3 100644
--- a/product-image-generator/app/page.tsx
+++ b/product-image-generator/app/page.tsx
@@ -1,5 +1,9 @@
+import { auth } from "@trigger.dev/sdk";
import ProductImageGenerator from "./ProductImageGenerator";
export default async function Page() {
- return
;
+ const triggerToken = await auth.createTriggerPublicToken([
+ "upload-image-to-r2",
+ ]);
+ return
;
}
From 1ad322b05f738a06a2f3a48c9521b0fbded84915 Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Wed, 10 Sep 2025 13:58:14 +0100
Subject: [PATCH 045/232] Improved loading state
---
.../app/components/UploadCard.tsx | 34 +++++++++++++++----
1 file changed, 28 insertions(+), 6 deletions(-)
diff --git a/product-image-generator/app/components/UploadCard.tsx b/product-image-generator/app/components/UploadCard.tsx
index af32232..d066da8 100644
--- a/product-image-generator/app/components/UploadCard.tsx
+++ b/product-image-generator/app/components/UploadCard.tsx
@@ -26,6 +26,7 @@ export default function UploadCard({
}: UploadCardProps) {
const [isDragOver, setIsDragOver] = useState(false);
const [hasNotifiedComplete, setHasNotifiedComplete] = useState(false);
+ const [isUploading, setIsUploading] = useState(false);
const fileInputRef = useRef
(null);
// Use realtime task trigger hook for immediate triggering
@@ -45,6 +46,13 @@ export default function UploadCard({
const productAnalysis =
output?.productAnalysis ?? metadataResult?.productAnalysis;
+ // Clear local uploading state when run starts
+ useEffect(() => {
+ if (run?.id && isUploading) {
+ setIsUploading(false);
+ }
+ }, [run?.id, isUploading]);
+
// Notify parent when completed (only once)
useEffect(() => {
if (
@@ -70,6 +78,10 @@ export default function UploadCard({
// Upload image
const uploadImage = async (file: File) => {
try {
+ // Set loading state immediately
+ setIsUploading(true);
+ setHasNotifiedComplete(false);
+
// Convert file to base64
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
@@ -82,6 +94,7 @@ export default function UploadCard({
});
} catch (err) {
console.error("Upload failed:", err);
+ setIsUploading(false);
}
};
@@ -125,7 +138,10 @@ export default function UploadCard({
? "border-primary bg-primary/5"
: "border-primary/30 bg-card hover:border-primary/50"
} ${
- isLoading || run?.status === "EXECUTING" || run?.status === "QUEUED"
+ isUploading ||
+ isLoading ||
+ run?.status === "EXECUTING" ||
+ run?.status === "QUEUED"
? "opacity-50 pointer-events-none"
: ""
}`}
@@ -146,7 +162,8 @@ export default function UploadCard({
transition: "opacity 0.3s ease-in-out",
}}
/>
- {(isLoading ||
+ {(isUploading ||
+ isLoading ||
run?.status === "EXECUTING" ||
run?.status === "QUEUED") && (
@@ -166,26 +183,31 @@ export default function UploadCard({
- ) : isLoading || run?.id ? (
+ ) : isUploading || isLoading || run?.id ? (
// Show progress state when loading or run exists
- {progress?.message || "Processing..."}
+ {progress?.message ||
+ (isUploading ? "Starting upload..." : "Processing...")}
{progress
? `Step ${progress.step} of ${progress.total}`
+ : isUploading
+ ? "Preparing..."
: "Please wait"}
- {progress && (
+ {(progress || isUploading) && (
From 483e575c9d787f5e5cf8cd84c885f8f8f3af4ba8 Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Wed, 10 Sep 2025 14:05:02 +0100
Subject: [PATCH 046/232] Added regenerate button
---
.../app/components/GeneratedCard.tsx | 33 +++++++++++++++++--
1 file changed, 30 insertions(+), 3 deletions(-)
diff --git a/product-image-generator/app/components/GeneratedCard.tsx b/product-image-generator/app/components/GeneratedCard.tsx
index b5588fc..f25d04f 100644
--- a/product-image-generator/app/components/GeneratedCard.tsx
+++ b/product-image-generator/app/components/GeneratedCard.tsx
@@ -33,6 +33,7 @@ export default function GeneratedCard({
null
);
const [hasTriggered, setHasTriggered] = useState(false);
+ const [isRegenerating, setIsRegenerating] = useState(false);
const [isGenerating, setIsGenerating] = useState(false);
const [runId, setRunId] = useState
(null);
@@ -109,7 +110,7 @@ export default function GeneratedCard({
: "idle";
// Update generated image URL when run completes
- if (run?.status === "COMPLETED" && !generatedImageUrl) {
+ if (run?.status === "COMPLETED") {
// First try to get publicUrl from output
let publicUrl = run.output?.publicUrl;
@@ -119,8 +120,9 @@ export default function GeneratedCard({
publicUrl = result.publicUrl;
}
- if (publicUrl) {
+ if (publicUrl && publicUrl !== generatedImageUrl) {
setGeneratedImageUrl(publicUrl);
+ setIsRegenerating(false); // Clear regenerating state when new image loads
}
}
@@ -142,7 +144,7 @@ export default function GeneratedCard({
};
const handleRetry = () => {
- setGeneratedImageUrl(null);
+ setIsRegenerating(true);
setHasTriggered(false);
setIsGenerating(false);
setRunId(null);
@@ -160,13 +162,36 @@ export default function GeneratedCard({
alt={`Generated ${promptTitle}`}
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
/>
+ {/* Regenerating overlay */}
+ {isRegenerating && isTaskRunning && (
+
+
+
+
+ Regenerating...
+
+
+
+ )}
{/* Action buttons */}
+
+
+
@@ -175,6 +200,8 @@ export default function GeneratedCard({
variant="secondary"
className="w-8 h-8 rounded-full p-0 backdrop-blur-sm bg-white/90 hover:bg-white"
onClick={handleDownload}
+ disabled={isRegenerating && isTaskRunning}
+ title="Download image"
>
From 96e8c122a4b34daee0e962071df7704de042008c Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Wed, 10 Sep 2025 15:02:32 +0100
Subject: [PATCH 047/232] Styling updates
---
.../app/ProductImageGenerator.tsx | 67 +----
.../app/components/CustomPromptCard.tsx | 254 ++++++++++++++----
.../app/components/GeneratedCard.tsx | 2 +-
.../app/components/UploadCard.tsx | 10 +-
4 files changed, 213 insertions(+), 120 deletions(-)
diff --git a/product-image-generator/app/ProductImageGenerator.tsx b/product-image-generator/app/ProductImageGenerator.tsx
index 34058b1..84ebc70 100644
--- a/product-image-generator/app/ProductImageGenerator.tsx
+++ b/product-image-generator/app/ProductImageGenerator.tsx
@@ -71,7 +71,7 @@ export default function ProductImageGenerator({
uploadedImageUrl && productAnalysis ? true : false;
return (
-
+
{/* Fixed Header */}
@@ -113,6 +113,7 @@ export default function ProductImageGenerator({
Upload a product image and generate professional marketing shots
+ for your online store.
@@ -146,62 +147,16 @@ export default function ProductImageGenerator({
{/* Bottom Row - Sequential Custom Cards */}
{Array.from({ length: 4 }).map((_, index) => {
- // If there's a completed custom generation for this slot, show it
- const runId = customGenerations.runIds[index];
- const customPrompt = customGenerations.prompts[index];
-
- if (runId && customPrompt) {
- return (
-
-
-
-
Custom Scene
-
- {customPrompt}
-
-
-
- );
- }
-
- // If this is the next available slot and top row is complete, show custom prompt card
- if (index === nextCustomCardIndex && topRowGenerationsComplete) {
- return (
-
- handleCustomGenerationComplete(runId, prompt, index)
- }
- />
- );
- }
-
- // Otherwise show placeholder
+ // Always show CustomPromptCard, let it handle its own states
return (
-
-
-
-
- {index + 1}
-
-
-
- {!topRowGenerationsComplete
- ? "Complete upload and generation first"
- : index === 0
- ? "Ready for custom prompt"
- : `Slot ${index + 1}`}
-
-
-
+
+ handleCustomGenerationComplete(runId, prompt, index)
+ }
+ />
);
})}
diff --git a/product-image-generator/app/components/CustomPromptCard.tsx b/product-image-generator/app/components/CustomPromptCard.tsx
index 791cde8..0e2a26d 100644
--- a/product-image-generator/app/components/CustomPromptCard.tsx
+++ b/product-image-generator/app/components/CustomPromptCard.tsx
@@ -1,7 +1,14 @@
"use client";
import { useRealtimeRun } from "@trigger.dev/react-hooks";
-import { RefreshCw, Send } from "lucide-react";
+import {
+ RefreshCw,
+ Send,
+ Expand,
+ Download,
+ ImageIcon,
+ Sparkles,
+} from "lucide-react";
import { useState, useEffect } from "react";
import { triggerGenerationTask } from "../actions";
import type { generateAndUploadImage } from "../../src/trigger/generate-and-upload-image";
@@ -26,6 +33,10 @@ export default function CustomPromptCard({
const [publicAccessToken, setPublicAccessToken] = useState(
null
);
+ const [generatedImageUrl, setGeneratedImageUrl] = useState(
+ null
+ );
+ const [showForm, setShowForm] = useState(false);
// Subscribe to the run if we have a runId and token
const { run, error } = useRealtimeRun(
@@ -36,12 +47,37 @@ export default function CustomPromptCard({
}
);
- // Notify parent when generation completes
+ // Handle run completion
useEffect(() => {
- if (run?.status === "COMPLETED" && run?.id && onGenerationComplete) {
- onGenerationComplete(run.id, customPrompt);
+ if (run?.status === "COMPLETED") {
+ // Extract generated image URL
+ let publicUrl = run.output?.publicUrl;
+ if (!publicUrl && run.metadata?.result) {
+ const result = run.metadata.result as any;
+ publicUrl = result.publicUrl;
+ }
+
+ if (publicUrl && publicUrl !== generatedImageUrl) {
+ setGeneratedImageUrl(publicUrl);
+ setIsGenerating(false);
+ }
+
+ // Notify parent
+ if (run?.id && onGenerationComplete) {
+ onGenerationComplete(run.id, customPrompt);
+ }
+ } else if (run?.status === "FAILED") {
+ setIsGenerating(false);
}
- }, [run?.status, run?.id, onGenerationComplete, customPrompt]);
+ }, [
+ run?.status,
+ run?.output,
+ run?.metadata,
+ run?.id,
+ onGenerationComplete,
+ customPrompt,
+ generatedImageUrl,
+ ]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -110,70 +146,172 @@ export default function CustomPromptCard({
const isDisabled =
!baseImageUrl || !productAnalysis || !customPrompt.trim() || isGenerating;
- return (
-
-
-
-
-
-
- Create a scenario with this product
-
- setCustomPrompt(e.target.value)}
- disabled={isGenerating}
- className="w-full h-20 px-3 py-2 text-sm border border-input bg-transparent rounded-md shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 resize-none"
- rows={3}
- />
-
+ const handleDownload = () => {
+ if (generatedImageUrl) {
+ const link = document.createElement("a");
+ link.href = generatedImageUrl;
+ link.download = `custom-generated-${Date.now()}.png`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ }
+ };
-
-
- {isGenerating ? (
- <>
-
- Generating...
- >
- ) : (
- <>
-
- Generate
- >
- )}
-
+ const handleExpand = () => {
+ if (generatedImageUrl) {
+ window.open(generatedImageUrl, "_blank");
+ }
+ };
- {run?.id && (
+ const handleReset = () => {
+ setGeneratedImageUrl(null);
+ setCustomPrompt("");
+ setRunId(null);
+ setPublicAccessToken(null);
+ setIsGenerating(false);
+ setShowForm(false);
+ };
+
+ return (
+
+ {generatedImageUrl ? (
+ // Show generated image
+
+
+ {/* Action buttons */}
+
+
+
+
+
+
+
+
+
+
+
+ {/* Title overlay */}
+
+
+ ) : showForm ? (
+ // Show form
+
+
+
+
+
+ Add another product shot
+
+ setCustomPrompt(e.target.value)}
+ disabled={isGenerating}
+ className="w-full h-20 px-3 py-2 text-sm border border-input bg-transparent rounded-md shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 resize-none"
+ rows={3}
+ autoFocus
+ />
+
+
+
setShowForm(false)}
+ className="flex-1"
+ >
+ Cancel
+
+
-
- Regenerate
+ {isGenerating ? (
+ <>
+
+ Generating...
+ >
+ ) : (
+ <>
+
+ Generate
+ >
+ )}
- )}
+
+
+
+
+ {!baseImageUrl && (
+
+ Upload an image first to enable custom prompts
-
+ )}
-
- {!baseImageUrl && (
-
- Upload an image first to enable custom prompts
+ ) : (
+ // Show blank state (same as GeneratedCard but no text)
+
+
+ {isGenerating ? (
+
+ ) : baseImageUrl && productAnalysis ? (
+
setShowForm(true)}
+ className="w-8 h-8 rounded-full p-0"
+ >
+
+
+ ) : (
+
+ )}
- )}
-
+
+ {isGenerating
+ ? "Generating..."
+ : baseImageUrl && productAnalysis
+ ? "Click to add custom scene"
+ : ""}
+
+
+ )}
);
}
diff --git a/product-image-generator/app/components/GeneratedCard.tsx b/product-image-generator/app/components/GeneratedCard.tsx
index f25d04f..7194b8b 100644
--- a/product-image-generator/app/components/GeneratedCard.tsx
+++ b/product-image-generator/app/components/GeneratedCard.tsx
@@ -256,7 +256,7 @@ export default function GeneratedCard({
(generationProgress === "generating"
? "Generating..."
: generationProgress === "idle"
- ? "Waiting to start..."
+ ? ""
: baseImageUrl && productAnalysis && !hasTriggered
? "Click to generate"
: "Waiting for upload...")}
diff --git a/product-image-generator/app/components/UploadCard.tsx b/product-image-generator/app/components/UploadCard.tsx
index d066da8..d603d88 100644
--- a/product-image-generator/app/components/UploadCard.tsx
+++ b/product-image-generator/app/components/UploadCard.tsx
@@ -135,8 +135,8 @@ export default function UploadCard({
@@ -234,7 +234,7 @@ export default function UploadCard({
Drag and drop an image here
- or click to browse
+ or click to upload
)}
Date: Wed, 10 Sep 2025 15:17:17 +0100
Subject: [PATCH 048/232] More style updates
---
.../app/components/UploadCard.tsx | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/product-image-generator/app/components/UploadCard.tsx b/product-image-generator/app/components/UploadCard.tsx
index d603d88..b40efcb 100644
--- a/product-image-generator/app/components/UploadCard.tsx
+++ b/product-image-generator/app/components/UploadCard.tsx
@@ -1,6 +1,14 @@
"use client";
-import { Upload } from "lucide-react";
+import {
+ Loader2,
+ LoaderCircleIcon,
+ LoaderIcon,
+ LoaderPinwheelIcon,
+ LucideLoader,
+ LucideSparkles,
+ Upload,
+} from "lucide-react";
import { useRef, useState, useEffect } from "react";
import type {
ProductAnalysis,
@@ -186,8 +194,10 @@ export default function UploadCard({
) : isUploading || isLoading || run?.id ? (
// Show progress state when loading or run exists
-
-
+
{progress?.message ||
From 19440f8444b78dcb0b396f8bd86b80e3a3b83a98 Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Wed, 10 Sep 2025 15:23:12 +0100
Subject: [PATCH 049/232] More improvements
---
product-image-generator/app/ProductImageGenerator.tsx | 6 +++---
product-image-generator/app/components/UploadCard.tsx | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/product-image-generator/app/ProductImageGenerator.tsx b/product-image-generator/app/ProductImageGenerator.tsx
index 84ebc70..231575b 100644
--- a/product-image-generator/app/ProductImageGenerator.tsx
+++ b/product-image-generator/app/ProductImageGenerator.tsx
@@ -104,10 +104,10 @@ export default function ProductImageGenerator({
{/* Main Content */}
-
-
+
+
{/* Page Title */}
-
+
Product Image Generator
diff --git a/product-image-generator/app/components/UploadCard.tsx b/product-image-generator/app/components/UploadCard.tsx
index b40efcb..17dd16a 100644
--- a/product-image-generator/app/components/UploadCard.tsx
+++ b/product-image-generator/app/components/UploadCard.tsx
@@ -150,7 +150,7 @@ export default function UploadCard({
isLoading ||
run?.status === "EXECUTING" ||
run?.status === "QUEUED"
- ? "opacity-50 pointer-events-none"
+ ? "pointer-events-none"
: ""
}`}
onDragOver={handleDragOver}
From 901111fd575352e20cce3d06f384511817acb023 Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Wed, 10 Sep 2025 16:02:11 +0100
Subject: [PATCH 050/232] Tweaks
---
.../app/ProductImageGenerator.tsx | 14 ++++++--------
.../src/trigger/generate-and-upload-image.ts | 6 +++---
2 files changed, 9 insertions(+), 11 deletions(-)
diff --git a/product-image-generator/app/ProductImageGenerator.tsx b/product-image-generator/app/ProductImageGenerator.tsx
index 231575b..aa31dc4 100644
--- a/product-image-generator/app/ProductImageGenerator.tsx
+++ b/product-image-generator/app/ProductImageGenerator.tsx
@@ -78,25 +78,23 @@ export default function ProductImageGenerator({
-
+
Home
-
- Settings
+
+ Account
-
- Account
+
+ Settings
diff --git a/product-image-generator/src/trigger/generate-and-upload-image.ts b/product-image-generator/src/trigger/generate-and-upload-image.ts
index 4386597..2053436 100644
--- a/product-image-generator/src/trigger/generate-and-upload-image.ts
+++ b/product-image-generator/src/trigger/generate-and-upload-image.ts
@@ -36,7 +36,7 @@ export const generateAndUploadImage = task({
};
customPrompt?: string; // User's custom prompt for "custom" style
model?: "flux";
- size?: "1024x1024" | "1792x1024" | "1024x1792";
+ size?: "1024x1792";
strength?: number;
guidance?: number;
steps?: number;
@@ -48,7 +48,7 @@ export const generateAndUploadImage = task({
productAnalysis,
customPrompt,
model = "flux",
- size = "1024x1024",
+ size = "1024x1792",
strength = 0.2,
guidance = 7, // From your settings
steps = 20, // From your settings
@@ -104,7 +104,7 @@ export const generateAndUploadImage = task({
"isolated-table":
`Professional product photography on clean white table with studio lighting, minimalist background, commercial style`,
"lifestyle-scene":
- `Lifestyle product photography of fashionable person of any gender or ethnicity in the sunshine holding the product in their hand with a big smile on their face - they should be pointing to the product. This should be a very sophisticated lifestyle shot`,
+ `Lifestyle product photography of a person of any gender or ethnicity in the sunshine holding the product in their hand with a big smile on their face - they should be pointing to the product. This should be a cool lifestyle shot`,
"hero-shot":
`Professional lifestyle shot of elegant hands holding and presenting the product, dramatic lighting, luxury commercial photography style, perfect for marketing materials, human interaction with product`,
"custom": customPrompt || "Professional product photography",
From 2b5136fb582aad16343d4596a62fab827ec1d495 Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Wed, 10 Sep 2025 16:39:17 +0100
Subject: [PATCH 051/232] Removed imports and updated button styles
---
.../app/components/CustomPromptCard.tsx | 14 +++-----------
.../app/components/UploadCard.tsx | 16 ++++------------
2 files changed, 7 insertions(+), 23 deletions(-)
diff --git a/product-image-generator/app/components/CustomPromptCard.tsx b/product-image-generator/app/components/CustomPromptCard.tsx
index 0e2a26d..cb44723 100644
--- a/product-image-generator/app/components/CustomPromptCard.tsx
+++ b/product-image-generator/app/components/CustomPromptCard.tsx
@@ -233,7 +233,7 @@ export default function CustomPromptCard({
Add another product shot
@@ -243,21 +243,13 @@ export default function CustomPromptCard({
value={customPrompt}
onChange={(e) => setCustomPrompt(e.target.value)}
disabled={isGenerating}
- className="w-full h-20 px-3 py-2 text-sm border border-input bg-transparent rounded-md shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 resize-none"
+ className="w-full h-20 px-3 text-sm border border-input bg-transparent rounded-md shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 resize-none"
rows={3}
autoFocus
/>
- setShowForm(false)}
- className="flex-1"
- >
- Cancel
-
diff --git a/product-image-generator/app/components/UploadCard.tsx b/product-image-generator/app/components/UploadCard.tsx
index 17dd16a..699cdce 100644
--- a/product-image-generator/app/components/UploadCard.tsx
+++ b/product-image-generator/app/components/UploadCard.tsx
@@ -1,15 +1,9 @@
"use client";
-import {
- Loader2,
- LoaderCircleIcon,
- LoaderIcon,
- LoaderPinwheelIcon,
- LucideLoader,
- LucideSparkles,
- Upload,
-} from "lucide-react";
-import { useRef, useState, useEffect } from "react";
+import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
+import { LucideLoader, Upload } from "lucide-react";
+import { useEffect, useRef, useState } from "react";
+import type { uploadImageToR2 } from "../../src/trigger/image-upload";
import type {
ProductAnalysis,
UploadTaskMetadata,
@@ -17,8 +11,6 @@ import type {
} from "../types/trigger";
import { Button } from "./ui/button";
import { Card } from "./ui/card";
-import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
-import type { uploadImageToR2 } from "../../src/trigger/image-upload";
interface UploadCardProps {
triggerToken: string;
From 865cd16342b1d9b258c5e68a7ccee31c96737850 Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Wed, 10 Sep 2025 17:15:49 +0100
Subject: [PATCH 052/232] Fixed custom prompt card
---
.../app/components/CustomPromptCard.tsx | 17 +++++++----------
1 file changed, 7 insertions(+), 10 deletions(-)
diff --git a/product-image-generator/app/components/CustomPromptCard.tsx b/product-image-generator/app/components/CustomPromptCard.tsx
index cb44723..3ed06b5 100644
--- a/product-image-generator/app/components/CustomPromptCard.tsx
+++ b/product-image-generator/app/components/CustomPromptCard.tsx
@@ -94,7 +94,6 @@ export default function CustomPromptCard({
baseImageUrl,
productAnalysis,
customPrompt: customPrompt.trim(),
- model: "flux",
size: "1024x1792",
});
@@ -126,7 +125,6 @@ export default function CustomPromptCard({
baseImageUrl,
productAnalysis,
customPrompt: customPrompt.trim(),
- model: "flux",
size: "1024x1792",
});
@@ -279,18 +277,17 @@ export default function CustomPromptCard({
) : (
// Show blank state (same as GeneratedCard but no text)
-
+
setShowForm(true)}
+ className={`h-full flex flex-col items-center justify-center p-6 text-center cursor-pointer hover:bg-gray-50/50 transition-colors ${
+ !baseImageUrl ? "pointer-events-none" : ""
+ }`}
+ >
{isGenerating ? (
) : baseImageUrl && productAnalysis ? (
-
setShowForm(true)}
- className="w-8 h-8 rounded-full p-0"
- >
-
-
+
) : (
)}
From ec8b42b7771535b840c0da108b5ab26644e29c07 Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Thu, 11 Sep 2025 10:26:17 +0100
Subject: [PATCH 053/232] Added dl functionality
---
.../app/ProductImageGenerator.tsx | 209 ++++++++++++++++--
.../app/components/CustomPromptCard.tsx | 48 +++-
.../app/components/GeneratedCard.tsx | 29 ++-
.../app/components/UploadCard.tsx | 16 +-
product-image-generator/app/layout.tsx | 4 +-
5 files changed, 275 insertions(+), 31 deletions(-)
diff --git a/product-image-generator/app/ProductImageGenerator.tsx b/product-image-generator/app/ProductImageGenerator.tsx
index aa31dc4..c5ff883 100644
--- a/product-image-generator/app/ProductImageGenerator.tsx
+++ b/product-image-generator/app/ProductImageGenerator.tsx
@@ -1,6 +1,13 @@
"use client";
-import { Home, ImageIcon, Settings, User } from "lucide-react";
+import {
+ Download,
+ Home,
+ ImageIcon,
+ Settings,
+ User,
+ WandSparklesIcon,
+} from "lucide-react";
import { useState } from "react";
import CustomPromptCard from "./components/CustomPromptCard";
import GeneratedCard from "./components/GeneratedCard";
@@ -20,6 +27,11 @@ export default function ProductImageGenerator({
const [productAnalysis, setProductAnalysis] =
useState
(null);
+ // Track all generated images
+ const [generatedImages, setGeneratedImages] = useState<{
+ [key: string]: { runId: string; prompt: string; imageUrl?: string };
+ }>({});
+
// Track custom generations for bottom row
const [customGenerations, setCustomGenerations] = useState<{
runIds: (string | null)[];
@@ -39,15 +51,151 @@ export default function ProductImageGenerator({
}
};
+ const handleGenerationComplete = (
+ runId: string,
+ prompt: string,
+ imageUrl?: string,
+ key?: string
+ ) => {
+ console.log("Generation completed:", { runId, prompt, imageUrl, key });
+ setGeneratedImages((prev) => {
+ const updated = {
+ ...prev,
+ [key || runId]: { runId, prompt, imageUrl },
+ };
+ console.log("Updated generated images:", updated);
+ return updated;
+ });
+ };
+
const handleCustomGenerationComplete = (
runId: string,
prompt: string,
- index: number
+ index: number,
+ imageUrl?: string
) => {
setCustomGenerations((prev) => ({
runIds: prev.runIds.map((id, i) => (i === index ? runId : id)),
prompts: prev.prompts.map((p, i) => (i === index ? prompt : p)),
}));
+ handleGenerationComplete(runId, prompt, imageUrl, `custom-${index}`);
+ };
+
+ const handlePresetGenerationComplete = (
+ runId: string,
+ promptId: string,
+ promptTitle: string,
+ imageUrl?: string
+ ) => {
+ handleGenerationComplete(runId, promptTitle, imageUrl, promptId);
+ };
+
+ const handleDownloadAll = async () => {
+ console.log("Download button clicked!");
+ console.log("Generated images:", generatedImages);
+ console.log("Total generated images:", totalGeneratedImages);
+
+ if (totalGeneratedImages === 0) {
+ console.log("No images to download");
+ return;
+ }
+
+ try {
+ // For a single image, download directly
+ if (totalGeneratedImages === 1) {
+ const imageData = Object.values(generatedImages)[0];
+ console.log("Single image data:", imageData);
+
+ if (imageData.imageUrl) {
+ console.log("Downloading single image:", imageData.imageUrl);
+
+ // Try to fetch the image first to handle CORS
+ try {
+ const response = await fetch(imageData.imageUrl);
+ const blob = await response.blob();
+ const url = window.URL.createObjectURL(blob);
+
+ const link = document.createElement("a");
+ link.href = url;
+ link.download = `${imageData.prompt
+ .replace(/\s+/g, "-")
+ .toLowerCase()}-${Date.now()}.png`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ // Clean up the blob URL
+ window.URL.revokeObjectURL(url);
+ } catch (fetchError) {
+ console.log("Fetch failed, trying direct download:", fetchError);
+ // Fallback to direct download
+ const link = document.createElement("a");
+ link.href = imageData.imageUrl;
+ link.download = `${imageData.prompt
+ .replace(/\s+/g, "-")
+ .toLowerCase()}-${Date.now()}.png`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ }
+ } else {
+ console.log("No image URL found for single image");
+ }
+ return;
+ }
+
+ // For multiple images, download each individually
+ console.log("Downloading multiple images...");
+ Object.entries(generatedImages).forEach(([key, imageData], index) => {
+ console.log(`Image ${index + 1}:`, key, imageData);
+
+ if (imageData.imageUrl) {
+ setTimeout(async () => {
+ try {
+ console.log(
+ `Downloading image ${index + 1}:`,
+ imageData.imageUrl
+ );
+
+ // Try to fetch the image first to handle CORS
+ const response = await fetch(imageData.imageUrl!);
+ const blob = await response.blob();
+ const url = window.URL.createObjectURL(blob);
+
+ const link = document.createElement("a");
+ link.href = url;
+ link.download = `${imageData.prompt
+ .replace(/\s+/g, "-")
+ .toLowerCase()}-${Date.now()}-${index + 1}.png`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ // Clean up the blob URL
+ window.URL.revokeObjectURL(url);
+ } catch (fetchError) {
+ console.log(
+ `Fetch failed for image ${index + 1}, trying direct download:`,
+ fetchError
+ );
+ // Fallback to direct download
+ const link = document.createElement("a");
+ link.href = imageData.imageUrl!;
+ link.download = `${imageData.prompt
+ .replace(/\s+/g, "-")
+ .toLowerCase()}-${Date.now()}-${index + 1}.png`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ }
+ }, index * 1000); // Stagger downloads by 1 second
+ } else {
+ console.log(`No image URL found for image ${index + 1}`);
+ }
+ });
+ } catch (error) {
+ console.error("Failed to download images:", error);
+ }
};
const promptTitles = {
@@ -56,6 +204,10 @@ export default function ProductImageGenerator({
"hero-shot": "Hero Shot",
};
+ // Calculate total generated images
+ const totalGeneratedImages = Object.keys(generatedImages).length;
+ const hasGeneratedImages = totalGeneratedImages > 0;
+
// Determine which custom cards have completed generations
const completedCustomCards = customGenerations.runIds.filter(
(runId) => runId !== null
@@ -71,14 +223,14 @@ export default function ProductImageGenerator({
uploadedImageUrl && productAnalysis ? true : false;
return (
-
+
{/* Fixed Header */}
@@ -105,14 +257,35 @@ export default function ProductImageGenerator({
{/* Page Title */}
-
-
- Product Image Generator
-
-
- Upload a product image and generate professional marketing shots
- for your online store.
-
+
+
+
+ Product Image Generator
+
+
+ Upload a product image and generate professional marketing shots
+ for your online store.
+
+
+
+
+
+ {hasGeneratedImages
+ ? `Download ${totalGeneratedImages} image${
+ totalGeneratedImages === 1 ? "" : "s"
+ } `
+ : "Download images"}
+
+
{/* Top Row - Upload + 3 Generated Images */}
@@ -127,18 +300,21 @@ export default function ProductImageGenerator({
productAnalysis={productAnalysis}
promptId="isolated-table"
promptTitle={promptTitles["isolated-table"]}
+ onGenerationComplete={handlePresetGenerationComplete}
/>
@@ -151,8 +327,13 @@ export default function ProductImageGenerator({
key={`custom-prompt-${index}`}
baseImageUrl={uploadedImageUrl}
productAnalysis={productAnalysis}
- onGenerationComplete={(runId, prompt) =>
- handleCustomGenerationComplete(runId, prompt, index)
+ onGenerationComplete={(runId, prompt, imageUrl) =>
+ handleCustomGenerationComplete(
+ runId,
+ prompt,
+ index,
+ imageUrl
+ )
}
/>
);
diff --git a/product-image-generator/app/components/CustomPromptCard.tsx b/product-image-generator/app/components/CustomPromptCard.tsx
index 3ed06b5..4bebc7c 100644
--- a/product-image-generator/app/components/CustomPromptCard.tsx
+++ b/product-image-generator/app/components/CustomPromptCard.tsx
@@ -8,6 +8,8 @@ import {
Download,
ImageIcon,
Sparkles,
+ RotateCcw,
+ Loader2,
} from "lucide-react";
import { useState, useEffect } from "react";
import { triggerGenerationTask } from "../actions";
@@ -19,7 +21,11 @@ import { Card } from "./ui/card";
interface CustomPromptCardProps {
baseImageUrl: string | null;
productAnalysis: ProductAnalysis | null;
- onGenerationComplete?: (runId: string, prompt: string) => void;
+ onGenerationComplete?: (
+ runId: string,
+ prompt: string,
+ imageUrl?: string
+ ) => void;
}
export default function CustomPromptCard({
@@ -37,6 +43,7 @@ export default function CustomPromptCard({
null
);
const [showForm, setShowForm] = useState(false);
+ const [isRegenerating, setIsRegenerating] = useState(false);
// Subscribe to the run if we have a runId and token
const { run, error } = useRealtimeRun
(
@@ -60,14 +67,16 @@ export default function CustomPromptCard({
if (publicUrl && publicUrl !== generatedImageUrl) {
setGeneratedImageUrl(publicUrl);
setIsGenerating(false);
+ setIsRegenerating(false);
}
// Notify parent
if (run?.id && onGenerationComplete) {
- onGenerationComplete(run.id, customPrompt);
+ onGenerationComplete(run.id, customPrompt, publicUrl);
}
} else if (run?.status === "FAILED") {
setIsGenerating(false);
+ setIsRegenerating(false);
}
}, [
run?.status,
@@ -116,6 +125,7 @@ export default function CustomPromptCard({
}
try {
+ setIsRegenerating(true);
setIsGenerating(true);
setRunId(null);
setPublicAccessToken(null);
@@ -134,10 +144,12 @@ export default function CustomPromptCard({
} else {
console.error("Generation failed:", result.error);
setIsGenerating(false);
+ setIsRegenerating(false);
}
} catch (error) {
console.error("Failed to regenerate custom image:", error);
setIsGenerating(false);
+ setIsRegenerating(false);
}
};
@@ -186,14 +198,30 @@ export default function CustomPromptCard({
alt="Custom generated image"
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
/>
+ {/* Regenerating overlay */}
+ {isRegenerating && isGenerating && (
+
+
+
+
+ Regenerating...
+
+
+
+ )}
{/* Action buttons */}
@@ -202,6 +230,7 @@ export default function CustomPromptCard({
variant="secondary"
className="w-8 h-8 rounded-full p-0 backdrop-blur-sm bg-white/90 hover:bg-white"
onClick={handleExpand}
+ disabled={isRegenerating && isGenerating}
title="View full size"
>
@@ -211,10 +240,21 @@ export default function CustomPromptCard({
variant="secondary"
className="w-8 h-8 rounded-full p-0 backdrop-blur-sm bg-white/90 hover:bg-white"
onClick={handleDownload}
+ disabled={isRegenerating && isGenerating}
title="Download image"
>
+
+
+
{/* Title overlay */}
diff --git a/product-image-generator/app/components/GeneratedCard.tsx b/product-image-generator/app/components/GeneratedCard.tsx
index 7194b8b..03c7bde 100644
--- a/product-image-generator/app/components/GeneratedCard.tsx
+++ b/product-image-generator/app/components/GeneratedCard.tsx
@@ -2,7 +2,15 @@
import { Button } from "./ui/button";
import { Card } from "./ui/card";
-import { Download, RefreshCw, Expand, ImageIcon, Sparkles } from "lucide-react";
+import {
+ Download,
+ RefreshCw,
+ Expand,
+ ImageIcon,
+ Sparkles,
+ LucideLoader,
+ Loader2,
+} from "lucide-react";
import { useState, useEffect, useCallback } from "react";
import { useRealtimeRun } from "@trigger.dev/react-hooks";
import { triggerGenerationTask } from "../actions";
@@ -21,6 +29,12 @@ interface GeneratedCardProps {
productAnalysis: ProductAnalysis | null;
promptId: string;
promptTitle: string;
+ onGenerationComplete?: (
+ runId: string,
+ promptId: string,
+ promptTitle: string,
+ imageUrl?: string
+ ) => void;
}
export default function GeneratedCard({
@@ -28,6 +42,7 @@ export default function GeneratedCard({
productAnalysis,
promptId,
promptTitle,
+ onGenerationComplete,
}: GeneratedCardProps) {
const [generatedImageUrl, setGeneratedImageUrl] = useState
(
null
@@ -63,7 +78,6 @@ export default function GeneratedCard({
promptStyle: promptId,
baseImageUrl,
productAnalysis,
- model: "flux",
size: "1024x1792",
});
@@ -123,6 +137,11 @@ export default function GeneratedCard({
if (publicUrl && publicUrl !== generatedImageUrl) {
setGeneratedImageUrl(publicUrl);
setIsRegenerating(false); // Clear regenerating state when new image loads
+
+ // Notify parent of generation completion
+ if (run?.id && onGenerationComplete) {
+ onGenerationComplete(run.id, promptId, promptTitle, publicUrl);
+ }
}
}
@@ -166,7 +185,11 @@ export default function GeneratedCard({
{isRegenerating && isTaskRunning && (
-
+
Regenerating...
diff --git a/product-image-generator/app/components/UploadCard.tsx b/product-image-generator/app/components/UploadCard.tsx
index 699cdce..0d6ac8f 100644
--- a/product-image-generator/app/components/UploadCard.tsx
+++ b/product-image-generator/app/components/UploadCard.tsx
@@ -135,8 +135,8 @@ export default function UploadCard({
@@ -215,20 +215,20 @@ export default function UploadCard({
)}
{run?.id && (
-
Run ID: {run.id}
+
Run ID: {run.id}
)}
{error && (
-
Error: {error.message}
+
Error: {error.message}
)}
) : (
// Show upload area
-
+
diff --git a/product-image-generator/app/layout.tsx b/product-image-generator/app/layout.tsx
index d6c8f5c..7979c48 100644
--- a/product-image-generator/app/layout.tsx
+++ b/product-image-generator/app/layout.tsx
@@ -15,8 +15,8 @@ const playfairDisplay = Roboto({
});
export const metadata: Metadata = {
- title: "ImageFlow - Modern Image Management",
- description: "Professional image management and organization tool",
+ title: "ImageFlow - Product Image Generator",
+ description: "Professional product image generator",
};
export default function RootLayout({
From 2b34f9e542eca1a4ca29fd1daba2edae19e52d0f Mon Sep 17 00:00:00 2001
From: D-K-P <8297864+D-K-P@users.noreply.github.com>
Date: Thu, 11 Sep 2025 11:32:34 +0100
Subject: [PATCH 054/232] Centred content
---
product-image-generator/app/ProductImageGenerator.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/product-image-generator/app/ProductImageGenerator.tsx b/product-image-generator/app/ProductImageGenerator.tsx
index c5ff883..d5bb776 100644
--- a/product-image-generator/app/ProductImageGenerator.tsx
+++ b/product-image-generator/app/ProductImageGenerator.tsx
@@ -223,7 +223,7 @@ export default function ProductImageGenerator({
uploadedImageUrl && productAnalysis ? true : false;
return (
-