PixelDosa UI

Image Studio

A whole workspace for making images — settings that survive the prompt, results as they land, and a history you can work from.

Image studio

Enter to send, Shift+Enter for a new line.

Nothing made yet

Describe what you want and pick a look on the right. The price of the next run is named before you commit to it.

Settings

Look

Lens-like depth and grain.

Shape
Quality

Photographic · Square · Standard

Credits

1,250 credits of 2,000 this month

4 images · this prompt12 credits

Resets on 1 October

Earlier

    Ceramic mug on a linen cloth, morning light from the left

    Product · Square · Standard · 2 hours ago · 4 results

    The same mug, but on a dark slate surface

    Product · Wide · High · Yesterday · 4 results

Installation

bash
npx shadcn@latest add @pixeldosa/image-studio

Requires `cn` at `@/lib/utils`. Format every figure yourself — costs, times and totals are strings. `onRecall` should restore both the prompt and the settings, not just the prompt; restoring one without the other is the failure this history exists to fix.

Usage

"use client";

import { ImageStudio } from "@/components/blocks/image-studio";

<ImageStudio
  onGenerate={({ text }) => start(text)}
  groups={settingGroups}
  values={settings}
  onValueChange={(group, option) => setSettings({ ...settings, [group]: option })}
  credits={{ remaining: "1,250 credits", allowance: "2,000 this month", used: 0.38 }}
  run={run}
  history={history}
  onRecall={(id) => restore(id)}
/>

Why it exists

Generation tools get described the same way: tossing prompts into a black hole. A feed that scrolls instead of an overview, so finding the good result from last Tuesday means scrolling past everything since. Settings that evaporate when you switch model, so you restate the palette and the framing in every prompt. And no idea what a run will cost until it's gone.

People work around all three with a spreadsheet of prompts kept beside the tool. This block is the workspace that makes that spreadsheet unnecessary.

Features

  • Settings sit beside the work and survive the prompt. Picked from previews, not named from memory. Changing what you're asking for doesn't lose how you wanted it.
  • History is a grid you can recall from. Each past run hands back its prompt and its settings — the thing a feed of images structurally cannot do.
  • Results appear as they land. A finished image never waits for its siblings, and a failed one says so on its own tile.
  • The price is named before the click, and the refund rule is stated before anything fails rather than after.
  • One place to stop. Stop lives on the run; the composer just stops offering to start a second one on top of it.

Props

PropTypeNotes
onGenerate(submission) => voidRequired. The composer's submission.
groups / values / onValueChangeParameterGroup[] / Record<string,string> / (groupId, optionId) => voidRequired. The settings rail.
runStudioRun | nullThe run in progress or the one that just finished.
creditsCreditsMeterPropsEverything Credits Meter takes.
historyStudioHistoryEntry[]Past runs, newest first.
onRecall(entryId: string) => voidRestore a past run's prompt and settings.
promptValue / onPromptValueChangestring / (value) => voidControl the prompt, so Recall can fill it.
onCancel / onRetry() => voidOn the run.
onKeptChange / onRegenerateRest(ids: string[]) => voidKeep the good ones, replace the rest.
onUse / onMore / onRetryItem(id: string) => voidPer result.
settingsSummarystringThe current settings as a sentence.
refundNote / regenerateCoststringStated before the click.
empty{ title, description? }Before the first run.
headingLevel2 | 3 | 4 | 5Defaults to 2.

StudioRun is { id, prompt, status, items, keptIds?, queue?, stage?, progress?, startedAt?, cost?, error? }. StudioHistoryEntry is { id, prompt, thumbnails, time?, settings?, total? }.

Accessibility

  • One labelled section; the settings rail, the run and the history each carry their own heading at the level below it, so the page outline never skips.
  • The settings are real radio groups in a fieldset, so arrow keys move between options.
  • Results are a list of buttons, not a wall of images — keyboard users move through results rather than decorations.
  • Only the running job animates; it stops under prefers-reduced-motion, and every state it carries is also in words.

When not to use

  • One generation inside a chat thread: use Generation Job on its own.
  • Choosing between two images: that's Compare View.
  • A gallery of finished work: this is a workspace for making things, not a portfolio.

Notes

onRecall must restore both the prompt and the settings. Restoring only the prompt recreates the exact problem the history exists to solve.

why it's built this way

Engineering Notes

The layout encodes the three documented failures of generation workspaces. Settings live in a rail beside the work rather than inside the prompt, because the reviewed complaint about multi-model tools is that switching model reopens every consistency problem and leaves the user restating palette and framing by hand; a rail that survives the prompt is what stops that. History is a thumbnail grid with a recall control, not a feed: comparisons of the same review task put a grid at roughly five times the density of a scrolling list, and density is the whole job when you are looking for the good one from last Tuesday. Recall returns the settings as well as the prompt, which is the part a feed of images structurally cannot do. Partial results render inside the job rather than after it, so a finished image never waits for its siblings. The composer goes busy while a run is active instead of offering a second start on top of the first, and Stop stays on the job, where the run actually is, so there is exactly one place to end it.

how it moves, and when it doesn't

Motion Notes

Motion means the machine is busy, and in a workspace that has to hold across three regions at once: only the running job moves — its stage shimmers, its pulse marks the running cells in the grid — while the settings rail and the history hold completely still, because both are the user's to work with rather than the machine's to report on. The result is that the eye is drawn to the one region that is actually changing, in a screen that would otherwise have movement in every quarter. Under `prefers-reduced-motion` every part of that stops and the same states are carried by colour, position and words.