Send-to-image-to-image action, Codex i2i, and a searchable gallery picker#975
Merged
Conversation
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.)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 singlei2iCapableflag (isI2iCapableModeinlib/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 aFileand ride the existing multipart upload path.No server changes — the generate route, init-image validation, and Codex i2i support already existed.
Notable implementation details
useMediaPreviewActionsgainshandleSendToImage, sharing abuildImageGenParamshelper withhandleRemix.pickI2iMode/isI2iCapableMode/I2I_CAPABLE_MODESinlib/imageGenBackends.jsis the source of truth for which backends support i2i (prefers local, falls back to Codex).?initImageFile=path strips the param after applying and defers the i2i-mode switch via a ref until backends resolve.revokeIfBlobhelper consolidates blob-URL cleanup across the init/reference handlers and unmount.Test plan
cd client && npm test— 1656 passing (159 files), including new suites formediaSearch,GalleryImagePicker,handleSendToImage, and updated init/reference picker assertions.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./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 futurereferenceImageNamesfield on the generate route would let the client send basenames directly and skip the fetch.