Repository navigation
feat(generation): restore hosted-provider options, Canvas modes and model record editing - #9811
joshistoast wants to merge 8 commits into
Conversation
…record editing Add OpenAI/Gemini/Seedream options and provider named sizes to the Generate panel, compile them into the provider node and metadata, and recall them from images. Let external models generate from the Canvas in the modes they advertise (txt2img, img2img, inpaint) and reject outpaint with a named reason. Add an External Provider editor to the model detail for provider ids, modes, support flags, limits, and defaults; extend the mock backend with external fixtures and model PATCH.
… consistent Starter overlays fill only the capability, default-settings, and panel-schema fields a record never set, so saved external model edits persist through responses and generation; the router and service share one helper. Canvas asks a provider for the bounding box itself and hides render size and denoising strength for it; control layers and regional guidance are rejected by name. A named provider size is sent only when the current dimensions match its preset; the provider id is a select over the registered providers, falling back to text while the list is unavailable.
…of overriding the catalog The backend keeps overlaying the starter catalog, so starter records save only their provider ids and show capabilities, limits and defaults read-only; custom records keep the full editor. Unit tests follow the pixel-exact external grid and the mode-specific canvas rejection.
Request counts and chunk sets are unchanged; the provider core joins the editor's generation sources and the provider sections join the Generate widget's.
lstein
left a comment
There was a problem hiding this comment.
Reviewed at 172193b. Format, oxlint, tsc, the architecture check, the unit suite (10,150 tests) and the browser tests in the touched files all pass. The performance gate was not run.
Blocker
Canvas output from hosted providers is not sized to the bounding box.
canvasProcessingSize.ts sends the raw bbox. compileCanvasGraph.ts adds no resize back to it. The backend then snaps the request to the provider's size table (_bucket_request / _bucket_to_size in external_generation_default.py), resizing the init image along with it. Only inpaint is resized back (_get_resize_target_for_inpaint). Running the backend's bucketing against the starter capabilities gives:
| Model | bbox | sent / returned |
|---|---|---|
| gpt-image-1 | 640×512 | 1024×1024 (init squashed to 1024²) |
| gpt-image-1 | 512×512 | 1024×1024 |
| seedream-4-0 | 1024×1024 | 2048×2048 |
| seedream-4-0 | 1000×700 | 2496×1664 |
Gemini 3 with no matching preset gets no image_size, so a 2048 bbox comes back as a 1K image.
Staging places each result at its own size at the bbox origin (workbenchState.ts: placement = { width: image.width, height: image.height, x: bbox.x, y: bbox.y }). The staged layer therefore overflows or underfills the frame and changes aspect ratio. For img2img the content is also stretched. Core metadata records the bbox size (640×512) while the image is 1024×1024.
To reproduce: pick GPT Image 1, draw a 640×512 bbox over a full raster layer, and invoke. This is the same bbox the compile test uses, whose comment says "the provider returns one image at the bbox size".
The local path restores to the bbox, and the external path needs the same: either a resize after the provider node, or a backend resize back to the requested size for every canvas mode.
Non-blockers
- Canvas inpaint does not work correctly with any shipped provider.
graph.tssends the canvas grayscale mask (white = keep, dark = edit), which the backend loads asL.- OpenAI (
providers/openai.py) uploads that L PNG as the editsmask, but the endpoint expects alpha transparency to mark the edit region. - Gemini, Seedream, Alibaba and Atlas never read
mask_imageand repaint the whole frame. No blend tail is added, so unmasked pixels change. - Only custom records reach this today, because no starter advertises
inpaint. Consider refusing inpaint until a provider handles masks, or compositing the result over the init image through the mask.
- OpenAI (
- The regional guidance rejection is unreachable. In
prepareCanvasInvocation.ts,createRegionalGuidanceCollectorreturnsfalsefor baseexternal, becausegetRegionalGuidanceSupportis null. The regional layer is silently skipped and the run is queued without it, so the "does not support regional guidance" reason incompileCanvasGraph.tsnever fires. Control layers throwControlLayerValidationError('unsupported_adapter')inshouldCompositebefore compile runs, so users get the generic adapter message, not the named one. The compile tests passregionalGuidance/controlLayersin directly, which hides this. - Canvas readiness validates only
txt2img.workbench/invocation.tscallsgetGenerationValidationReasons(values.model, values)with the default mode on the canvas branch too. A model advertising['img2img']is blocked from Canvas even when a full raster layer would compile it as img2img. max_image_sizeis not checked before queueing. For example, Atlas FLUX.2 Pro / Z-Image Turbo (max 2048, no aspect buckets) with a 2500×1500 bbox passes the frontend and then fails in_validate_request. Separately, Seedream with 14 references plus the canvas init image plus one output is 16, over the 15 limit, so it raises at runtime (seedream.py). This happens only on Canvas.- Recall can restore a named size that is never sent.
imageRecall.tsappliesimage_sizewhen the model lists that name but does not apply the preset's width and height.resolveExternalImageSizethen drops it.- Trigger: recall an image from a backend node run in the workflow editor without core metadata. It has
image_sizebut no width/height. - Result: the size select shows "Custom" while the toast counts the field as applied.
- Either apply the preset's dimensions with the name, or skip it. The test "restores … the named size … on a remix too" uses metadata without width/height, so it asserts this behaviour.
- Trigger: recall an image from a backend node run in the workflow editor without core metadata. It has
- Recall does not reset nullable options.
gemini_temperature,gemini_thinking_levelandopenai_input_fidelityare omitted from metadata when null, and recall only applies keys that are present. Remixing a default-temperature Gemini image while the current temperature is 2 keeps 2. A same-provider recall should reset these tonull, as LoRAs are cleared when the image had none. - A typed size equal to a preset reads as "Custom" and sends no
image_size. For example, Gemini 3 Pro with 2048×2048 typed in gets its default 1K. Model switches never setexternalImageSize, whereas webv1 selects the model's first preset on switch (modelSelected.ts). - Performance timing baselines look loosened rather than re-measured (57273a4).
- The launchpad's DOMContentLoaded, load and route-ready medians rose 21–34%, and editor-minimal rose 18–44% (
projectSwitchincluded). - Canvas, workflow and gallery are flat.
- The launchpad bundle grew about 9 KB, with the same request count.
- With a 10% tolerance, this raises those routes' limits by several times the tolerance. Please re-record on a quiet machine, or explain the regression.
- The launchpad's DOMContentLoaded, load and route-ready medians rose 21–34%, and editor-minimal rose 18–44% (
- Starter-managed records: changing provider ids keeps the old catalog overlay.
apply_external_starter_model_overridesmatches onconfig.source, and a PATCH ofprovider_id/provider_model_iddoes not changesource.- Trigger: change
gemini-2.5-flash-imagetogemini-3-pro-image-previewon an installed starter record. - Result: the record keeps 2.5-flash's modes, limits and defaults, and the read-only panel presents them as describing the new target.
- Either lock the routing ids on starter-managed records, or explain in the notice that capabilities stay tied to the catalog entry.
- Trigger: change
- Smaller editor issues:
- If
/starter_modelsfails for an admin, the full form shows. A capability edit then saves, the response re-applies the overlay, and every draft field is dropped as "submitted" behind a success toast, with no sign of the revert. - A capability draft made in that fallback form stays hidden, and "Unsaved changes" still shows, once a retry turns the panel read-only. Save then discards it.
- After picking a different provider, a record's original unconfigured
provider_iddisappears from the select. Getting it back needs Reset, which discards the other edits. - The provider list comes from all registered providers, not only configured ones as the description says.
- If
Test gaps
- In
GenerateDimensionFields.tsx, replacingexternalImageSize: settings.externalImageSizewithnullpasses everyGenerateDimensionFieldsandExternalSizeSelectbrowser test. The wiring between the dimension fields and the select is untested; the select is only tested on its own. - Removing the
>= GEMINI_TEMPERATURE_MINbound (in normalization or recall) fails no test. - Removing the local-model early return in
getMetadataExternalProviderfails no test.
Canvas graphs for external models iterate the provider's image collection into an img_resize back to the bbox, carrying the provider's output metadata; inpaint is refused on Canvas because the keep-mask has no provider contract. Readiness and preparation validate the bbox, max_image_size, the Seedream image budget, and name unsupported layers; model switches select the nearest provider preset, typed preset sizes resolve their name, and recall resets omitted provider defaults and restores a named size only with unambiguous dimensions.
… baselines The External Provider editor keeps a starter-sourced record fully read-only, since the backend overlays the catalog entry matched by source on every response; edits wait for the catalog lookup, with Retry on failure, and a retained draft is shown but cannot be saved. The saved provider stays selectable after switching. Browser timing baselines return to the merge-base values; only bytes and source sets differ.
Summary
Hosted image providers (OpenAI, Gemini, Seedream and the other external generators) lost several legacy capabilities in webv2.
Provider options and named sizes. Generate offers the options each provider's node accepts (OpenAI quality, background and input fidelity; Gemini temperature and thinking level; Seedream watermark and optimize-prompt) only for models whose provider and capabilities support them, plus the provider's named image sizes and preset resolutions. They compile into the external invocation with the node's own field names, are recorded in metadata and restored by Recall All and Remix. The named size follows the dimensions: a hand-typed size equal to a preset sends that preset's name, any other size sends none, and switching to a provider model snaps the frame to its nearest preset. Recall restores a named size only when the metadata pins one preset, and a same-provider recall resets options the image left at the provider default.
Canvas. External models generate from Canvas in text-to-image and image-to-image, with the composited frame as the init image. Providers bucket the request to their own size table, so the compiled graph scales the returned image back to the exact bounding box before staging or saving; metadata keeps the provider's request identity and seed. Inpaint is refused with a named reason because the Canvas keep-mask matches no provider's edit-mask contract; outpaint, control layers and regional guidance are refused with named reasons both in readiness and at invoke. Readiness also checks the frame against the model's maximum size and the Seedream image budget (references, init image and output). The render-size slot and strength control are hidden for providers.
Model record editor. External models gain an editor for the provider (a select over the registered providers), provider model id, supported modes, seed and reference support, image and reference limits, and default width/height/image count, saved through the model-records update contract with untouched capability tables preserved. A record whose source matches a starter-catalog entry is shown read-only, routing ids included, because the backend overlays that entry's capabilities on every response, so a changed route would keep the original entry's capabilities. Edits wait for the catalog lookup, with a Retry on failure; a retained draft is shown with a note that it cannot be saved.
Related Issues / Discussions
Follows up the legacy parity audit behind #9803.
QA Instructions
From
invokeai/frontend/webv2(Node 22.14, pnpm 10.12):pnpm check:releasepasses; three browser files in untouched code (GalleryMaintenance, WorkbenchHotkeyRuntime.modal, ToastContrast) timed out once under a load average above 12 and pass when rerun alone.uv run pytest tests/app/invocations/test_frontend_external_canvas_graphs.pyvalidates the compiled Canvas graphs against the real invocation fields and edge types.Mock backend (representative profile; three fixture models are external):
Review
Resolved from the first review: hosted-provider Canvas output was placed at the provider's bucketed size instead of the bounding box (now resized back, with metadata preserved); Canvas inpaint reached providers that ignore or misread the mask (now refused); the control-layer and regional-guidance rejections were unreachable (now named in readiness and at invoke); readiness validated only text-to-image, maximum size and the Seedream budget were checked only by the backend; recall could restore a named size without its dimensions and did not reset omitted provider defaults; a typed preset size read as custom and model switches never selected a preset; starter-managed records could change routing ids while keeping the old catalog overlay, and a failed catalog lookup exposed edits the backend reverted. The browser timing baselines are back to the main values rather than re-recorded: the branch adds no eager work on those routes, and the browser performance gate passes against them.
Limitations: Canvas readiness derives the mode from pixel coverage at invoke time, so a Seedream budget or inpaint refusal that depends on the painted content appears as an invoke notice rather than a disabled button; no real provider execution was performed; OpenAI input fidelity is offered only when the model takes image inputs (the backend uses it only on the edits endpoint).
Compatibility / Rollout
New generate settings are optional with defaults and normalize on load; no project-document migration. No backend or API changes; the Python addition is a test over the frontend's graph fixture.
Checklist
What's Newcopy (if doing a release after this PR)Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.