Skip to content

Send-to-image-to-image action, Codex i2i, and a searchable gallery picker#975

Merged
atomantic merged 10 commits into
mainfrom
feat/i2i-send-and-gallery-picker
Jun 5, 2026
Merged

Send-to-image-to-image action, Codex i2i, and a searchable gallery picker#975
atomantic merged 10 commits into
mainfrom
feat/i2i-send-and-gallery-picker

Conversation

@atomantic
Copy link
Copy Markdown
Owner

Summary

Adds two image-workflow features and extends image-to-image to the Codex backend.

Send to image-to-image — Image cards and the image lightbox gain a "Send to i2i" action next to "Remix". It opens the Image Gen page with the picture already queued as the image-to-image source and the prompt + render settings prefilled, ready to iterate. Wired across Media History, collections, the Universe Builder lightbox, and the Image Gen page's own gallery (in-page, no navigation).

Image-to-image on Codex — Previously the init-image picker only appeared for the local FLUX backend, even though the server already supports Codex i2i (codex -i + fidelity mapping). The picker now renders for local or Codex, gated by a single i2iCapable flag (isI2iCapableMode in lib/imageGenBackends.js), and the submit path forwards the init image for both.

Visual gallery picker — The init-image picker is no longer upload-only: a "Browse gallery" button opens a searchable visual picker of everything you've generated, using the same prompt/model/seed/LoRA search as Media History (extracted to lib/mediaSearch.js). The FLUX.2 multi-reference slots reuse the same picker; since the server has no gallery-basename field for references, gallery picks are fetched into a File and ride the existing multipart upload path.

No server changes — the generate route, init-image validation, and Codex i2i support already existed.

Notable implementation details

  • useMediaPreviewActions gains handleSendToImage, sharing a buildImageGenParams helper with handleRemix.
  • A single pickI2iMode / isI2iCapableMode / I2I_CAPABLE_MODES in lib/imageGenBackends.js is the source of truth for which backends support i2i (prefers local, falls back to Codex).
  • The cross-page ?initImageFile= path strips the param after applying and defers the i2i-mode switch via a ref until backends resolve.
  • A revokeIfBlob helper consolidates blob-URL cleanup across the init/reference handlers and unmount.

Test plan

  • cd client && npm test — 1656 passing (159 files), including new suites for mediaSearch, GalleryImagePicker, handleSendToImage, and updated init/reference picker assertions.
  • Lint: 0 errors.
  • Manual: npm run dev
    • /media/image → init image → Browse gallery filters by keyword; pick a tile → it becomes the init image; Generate runs i2i. Repeat on the Codex backend.
    • FLUX.2 model → reference slot → Gallery picks a generated image as a reference.
    • /media/history (and a collection) → image card/lightbox Send to i2i lands on Image Gen with the image preset and settings filled, on an i2i-capable backend.

Known follow-up (not in this PR)

The server has no gallery-basename field for reference images (only the init image), so reference gallery picks are fetched client-side into a File. A future referenceImageNames field on the generate route would let the client send basenames directly and skip the fetch.

atomantic added 10 commits June 5, 2026 14:32
Image cards and the lightbox gain a "Send to i2i" action alongside Remix
that opens Image Gen with the picture queued as the image-to-image source
and its prompt + render settings prefilled. i2i now works on the codex
backend too (not just local FLUX) — the init-image picker renders for both,
gated by a single isI2iCapableMode/i2iCapable flag.

The init-image picker is no longer upload-only: a Browse-gallery button
opens a searchable visual picker of your generated images (shared
lib/mediaSearch with Media History). The FLUX.2 multi-reference slots reuse
the same picker; since the server has no gallery-basename field for
references, gallery picks are fetched into a File for the existing upload
path.

No server changes — the generate route, init-image validation, and codex
i2i support already existed.
…leaners on auto-switch

P2: handleSendToImage dropped the source image's modelId from the /media/image
nav params — i2i may auto-switch the user to a different (i2i-capable) backend,
so carrying a provider-specific id (gpt-image-2) or a checkpoint the target
lacks would poison the local form and fail on Generate. The in-page path already
guards modelId via handleRemix.

P3: renamed handleSelectMode → switchMode and routed the deferred i2i nudge and
ensureI2iCapableMode through it, so an auto-switch reseeds cleanC2PA/denoise from
the target backend's saved defaults (previously only the manual chip path did).
…e gallery

Picking an image in the Browse-gallery picker only set the init image — it left
the prompt and render settings untouched. It now brings over the source image's
prompt, negative prompt, seed/steps/guidance/quantize, LoRAs, and dimensions
(via handleRemix on the picked item), matching the Send-to-i2i behavior.

Also default the output resolution to the source image's dimensions: gallery
picks read them from sidecar metadata; uploads decode the file's pixel size and
set width/height. i2i output should match the input size by default.
The prompt was required (client button gate + submit guards, and a server
`prompt: z.string().min(1)`), which blocked the natural i2i / unconditional /
edit cases where the init image drives the render. Empty prompt (and empty
negative prompt) are now accepted end to end. Edit-only models still require a
source image via the existing editImageMissing gate.
The multipart FormData builder drops empty-string fields, so an empty prompt
reached the generate route as `undefined` and the `z.string()` schema rejected
it ("expected string, received undefined"). Make prompt `.optional().default('')`
so an omitted/empty prompt is accepted and normalized to '', and update the
route test to assert the new accept-empty contract.
…onal)

The route accepted an empty prompt but the backend services still threw "Prompt
is required", so empty-prompt jobs failed at run time:
- local (mflux/diffusers): dropped the prompt-required guard entirely — the
  runners accept an empty `--prompt` (the regen pass has always relied on this);
  img2img is conditioned by the init image, txt2img runs unconditionally.
- codex: waive the guard when an init image is attached (the image is the edit
  instruction); a pure text-to-image codex render still requires a prompt.

Added a codex test for empty-prompt-with-init success.
…e, hardening

Self-review (5-agent /do:review) findings, all contained:
- i2i paths (in-page handleSendToImage + gallery-init pick) now pass
  handleRemix(img, { applyModel: false }) so they don't switch modelId — a
  switch to a non-FLUX.2 model silently unmounted the reference picker and
  dropped staged reference slots. Mirrors the cross-page handler that already
  drops modelId from the nav params.
- Added a codexNeedsPrompt gate (codex text-to-image with no init image needs a
  prompt) to the Generate button + handleGenerate + an inline hint, mirroring
  the server rule so the user sees a disabled button instead of a 400 toast.
- Defaulted prompt to '' in codex.js/local.js generateImage signatures (removes a
  latent TypeError if a future direct caller omits prompt).
- Deferred i2i mode nudge only clears its ref once a backend is actually picked,
  so a backend installed after first load still triggers the switch.
- encodeURIComponent on the galleryImageToFile fetch (defense-in-depth).
- Added imageGenBackends.test.js (pickI2iMode / isI2iCapableMode / capability
  set) for coverage parity with mediaSearch; fixed two stale comments.
… codex t2i guard

P2: the in-page i2i paths (handleSendToImage, handlePickGalleryInitImage) reuse
handleRemix, which only sets the prompt when the source has one — so picking an
empty-prompt source while the form held text left stale text conditioning the
render. Both now set the prompt from the source unconditionally (clearing it when
the source has none), so i2i is source-authoritative.

P2: making prompt globally optional let codex text-to-image (no init image) pass
route validation and 200-enqueue, then fail async. Added a synchronous route
guard rejecting codex t2i with no prompt + no init image (mirrors codex.js and the
client codexNeedsPrompt gate); the client gate already prevents this in the UI,
this protects direct API callers. Added a route test.
…r caps

P2: defaulting the output resolution to an uploaded image's raw dimensions could
exceed the route's per-edge (3840) / total-pixel (8,294,400) caps for common
phone photos, so the next Generate 400'd on imageEdgeSchema/refineImagePixelCap.
Added clampImageDimensions() to lib/imageGenResolutions.js (mirrors the server
caps; preserves aspect, snaps to multiples of 8, floors under both caps) and
apply it on the upload path. Gallery picks are unaffected — those dimensions were
generated within the app and already valid. Added a unit test.
…e remix keys

P2: the ?initImageFile effect and the remix-keys effect each called
setSearchParams on mount from the same searchParams snapshot. React Router's
functional updater reads that shared snapshot, so the second navigate could write
back a URL still containing initImageFile, leaving the source to re-apply on
refresh/back-nav. Merged init-image handling into the single remix-keys mount
effect so every consumed param (remix keys + initImageFile) is stripped in one
setSearchParams call.

(Codex also suggested Browse-gallery should not populate the prompt/settings;
keeping the populate behavior — it's the explicitly requested UX for the gallery
picker, matching Send-to-i2i.)
@atomantic atomantic merged commit 91386f7 into main Jun 5, 2026
2 checks passed
@atomantic atomantic deleted the feat/i2i-send-and-gallery-picker branch June 5, 2026 23:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant