Build image-generation progress and regenerate controls

Connect an image-generation component to job progress, cancellation and retry without confusing a visual animation with a real job result.

Live example

A mountain valley in warm evening light

Generating sample artwork...

Install the components

Add these to a React project with Tailwind CSS configured.

npx shadcn@latest add https://gradientdeck.com/ai-components/r/image-generation-blur.json

Components used: Image generation.

1. Represent the job explicitly

Keep the job's progress, completion and cancellation state in the parent. ImageGenerationBlur takes progress between zero and one and a generating or done status. Its current artwork is a seeded SVG landscape, which makes the sample deterministic and independent of an image service.

2. Drive progress from events

The demo advances progress using a clock. In a real application, replace that clock with job events or polling. If your service only reports queued, running and complete, show those stages instead of inventing a percentage. Completion should follow a successful result event.

3. Cancel and regenerate as separate actions

The example's cancel button stops the local timer and removes the unfinished image. Try again and Regenerate start a fresh run. A production cancellation also needs to reach your backend, and stale events from an older job must not overwrite the newest result.

Complete example

Save this as a client component after installing the components above.

"use client";

import { useEffect, useState } from "react";
import { ImageGenerationBlur } from "@/components/ai/image-generation-blur";

export default function ImageGenerationExample() {
  const [run, setRun] = useState(0);
  const [progress, setProgress] = useState(0);
  const [cancelled, setCancelled] = useState(false);
  useEffect(() => {
    if (cancelled) return;
    // A sample job clock. Replace this with progress events from your backend.
    const start = performance.now();
    const timer = setInterval(() => {
      const next = Math.min(1, (performance.now() - start) / 3500);
      setProgress(next);
      if (next === 1) clearInterval(timer);
    }, 80);
    return () => clearInterval(timer);
  }, [run, cancelled]);

  function regenerate() { setProgress(0); setCancelled(false); setRun(value => value + 1); }
  return <div className="w-full max-w-xl space-y-3">
    {!cancelled && <ImageGenerationBlur prompt="A mountain valley in warm evening light" progress={progress} status={progress === 1 ? "done" : "generating"} seed={run + 3} onRegenerate={regenerate} />}
    <p role="status" className="text-xs text-zinc-500">{cancelled ? "Sample job cancelled." : progress === 1 ? "Sample image ready." : "Generating sample artwork..."}</p>
    {cancelled ? <button type="button" onClick={regenerate} className="text-sm underline">Try again</button> : progress < 1 && <button type="button" onClick={() => setCancelled(true)} className="text-sm underline">Cancel generation</button>}
  </div>;
}

Connect your backend

To show generated images, replace the sample Artwork renderer in the copied component with your result image and keep the progress treatment around it. Use a stable job ID, handle failed jobs, and validate image destinations. A stopped browser animation does not prove the server stopped generating or charging for the request.

All guides →