From 530fd46f0a6ebadaeeddcba91fd4277dd701e8f3 Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Mon, 10 Aug 2026 17:23:01 +0200 Subject: [PATCH 01/12] fix(ui): recall the right VAE and text encoder per model base Several bases write their standalone VAE / text encoder into the shared `metadata.vae` and `metadata.qwen3_encoder` fields but recall them into their own params slot. The generic handlers did not account for that: - `VAEModel` excluded z-image, flux2 and krea-2 but not anima, so an Anima image rendered a duplicate VAE row and additionally wrote its VAE into the shared `params.vae` slot, which no Anima graph reads. - `Qwen3EncoderModel` had no base gate at all while dispatching hard into the Z-Image slots, so Anima and FLUX.2 Klein images pushed their encoder into `zImageQwen3EncoderModel` and cleared `zImageQwen3SourceModel` on the way. - The Anima handlers gated only on the currently selected base, never on the image's own `metadata.model.base`, so a Krea-2 image recalled while Anima was selected wrote its VAE into the Anima slot. Replace the growing `base !== 'x' && ...` chain with a documented `BASES_WITH_DEDICATED_VAE_HANDLER` set, rename `Qwen3EncoderModel` to `ZImageQwen3EncoderModel` and gate it on z-image, and harden both Anima handlers with `assertMetadataModelBase` (plus a base assert on the VAE, which is base-identified - the encoder is variant-identified, so it deliberately gets none). The same slot mismatch existed for FLUX.1, whose VAE lives in `params.fluxVAE`: its recall silently landed in `params.vae` and had no effect on generation. Add a dedicated `Flux1VAEModel` handler for it. Finally, `AnimaVAEModel`, `AnimaQwen3EncoderModel`, `Flux1VAEModel` and the three Z-Image handlers were missing from IMAGE_METADATA_ACTION_HANDLERS, so those parameters had neither a row nor a per-parameter recall button. List them, and add a guard test that diffs the UI list against the handler registry with an explicit allowlist, so the list cannot drift behind again. --- .../ImageMetadataActions.test.tsx | 58 ++++++ .../ImageMetadataActions.tsx | 6 + .../src/features/metadata/parsing.test.tsx | 195 ++++++++++++++++-- .../web/src/features/metadata/parsing.tsx | 84 +++++++- 4 files changed, 321 insertions(+), 22 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.test.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.test.tsx index 853bf2ceaad..934f4291464 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.test.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.test.tsx @@ -27,4 +27,62 @@ describe('IMAGE_METADATA_ACTION_HANDLERS', () => { expect(IMAGE_METADATA_ACTION_HANDLERS).toContain(handler); } }); + + it('includes every standalone-component handler for FLUX.1, Z-Image and Anima', () => { + // These bases keep their VAE / text encoder in dedicated params slots and write them to metadata, + // but the handlers were missing from this list — so the values were saved and gated correctly yet + // never rendered a row or a per-parameter recall button. + const handlers = [ + ImageMetadataHandlers.Flux1VAEModel, + ImageMetadataHandlers.ZImageVAEModel, + ImageMetadataHandlers.ZImageQwen3EncoderModel, + ImageMetadataHandlers.ZImageQwen3SourceModel, + ImageMetadataHandlers.AnimaVAEModel, + ImageMetadataHandlers.AnimaQwen3EncoderModel, + ]; + for (const handler of handlers) { + expect(IMAGE_METADATA_ACTION_HANDLERS).toContain(handler); + } + }); + + it('has no un-triaged handler missing from the recall parameters UI', () => { + // Guard against the list drifting behind the handler registry again: every new handler must either be + // listed above or be added here with a reason. The second group is a snapshot of pre-existing gaps — + // metadata that is written but has no recall row. Shrink it, don't grow it. + const intentionallyHidden = new Set([ + // Not per-parameter recallable by design. + 'CreatedBy', // informational only + 'ImageSize', // combined width+height, driven by the dedicated "recall size" action + + // Known gaps, not triaged in this change. + 'HiDiffusion', + 'HiDiffusionRauNet', + 'HiDiffusionWindowAttn', + 'HiDiffusionT1Ratio', + 'HiDiffusionT2Ratio', + 'ZImageSeedVarianceEnabled', + 'ZImageSeedVarianceStrength', + 'ZImageSeedVarianceRandomizePercent', + 'QwenImageVaeModel', + 'QwenImageQwenVLEncoderModel', + 'WanTransformerLowNoise', + 'WanComponentSource', + 'WanVaeModel', + 'WanT5EncoderModel', + 'WanGuidanceScaleLowNoise', + 'GeminiTemperature', + 'GeminiThinkingLevel', + 'OpenaiQuality', + 'OpenaiBackground', + 'OpenaiInputFidelity', + 'SeedreamWatermark', + 'SeedreamOptimizePrompt', + ]); + + const missing = Object.values(ImageMetadataHandlers) + .filter((handler) => !IMAGE_METADATA_ACTION_HANDLERS.includes(handler) && !intentionallyHidden.has(handler.type)) + .map((handler) => handler.type); + + expect(missing).toEqual([]); + }); }); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx index 6a589c30b7c..5cc88383dc8 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx @@ -70,9 +70,15 @@ export const IMAGE_METADATA_ACTION_HANDLERS: ImageMetadataActionHandler[] = [ ImageMetadataHandlers.Ideogram4Caption, ImageMetadataHandlers.CanvasLayers, ImageMetadataHandlers.RefImages, + ImageMetadataHandlers.Flux1VAEModel, ImageMetadataHandlers.Flux2VAEModel, ImageMetadataHandlers.KleinQwen3EncoderModel, ImageMetadataHandlers.Flux2DevMistralEncoderModel, + ImageMetadataHandlers.ZImageVAEModel, + ImageMetadataHandlers.ZImageQwen3EncoderModel, + ImageMetadataHandlers.ZImageQwen3SourceModel, + ImageMetadataHandlers.AnimaVAEModel, + ImageMetadataHandlers.AnimaQwen3EncoderModel, ImageMetadataHandlers.Krea2VAEModel, ImageMetadataHandlers.Krea2Qwen3VlEncoderModel, ImageMetadataHandlers.Krea2SeedVarianceEnabled, diff --git a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx index 1c87138572e..e21b3a1d0f2 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx @@ -165,21 +165,24 @@ describe('ImageMetadataHandlers — Klein recall gating', () => { }); describe('VAEModel (generic)', () => { - // The generic VAEModel handler must NOT also fire for FLUX.2 / Z-Image / - // Krea-2 images, otherwise the metadata viewer renders duplicate VAE rows - // next to the dedicated Flux2VAEModel / ZImageVAEModel / Krea2VAEModel - // handlers (and recalls into the wrong, shared VAE slot). - it.each(['flux2', 'z-image', 'krea-2'])('rejects parsing when current base is %s', async (base) => { - currentBase = base; - nextResolved = fakeModel('vae', base); - const store = makeStore(); + // The generic VAEModel handler must NOT also fire for bases in + // BASES_WITH_DEDICATED_VAE_HANDLER (FLUX.1 / FLUX.2 / Z-Image / Krea-2 / + // Anima), otherwise the metadata viewer renders duplicate VAE rows next to + // the dedicated handlers and recalls into the wrong, shared VAE slot. + it.each(['flux', 'flux2', 'z-image', 'krea-2', 'anima'])( + 'rejects parsing when current base is %s', + async (base) => { + currentBase = base; + nextResolved = fakeModel('vae', base); + const store = makeStore(); - await expect(ImageMetadataHandlers.VAEModel.parse({ vae: nextResolved }, store)).rejects.toThrow(); - }); + await expect(ImageMetadataHandlers.VAEModel.parse({ vae: nextResolved }, store)).rejects.toThrow(); + } + ); - it('parses successfully for non-Klein, non-Z-Image bases', async () => { - currentBase = 'sdxl'; - nextResolved = fakeModel('vae', 'sdxl'); + it.each(['sd-1', 'sdxl'])('parses successfully for %s, which uses the shared VAE slot', async (base) => { + currentBase = base; + nextResolved = fakeModel('vae', base); const store = makeStore(); const parsed = await ImageMetadataHandlers.VAEModel.parse({ vae: nextResolved }, store); @@ -504,3 +507,169 @@ describe('ImageMetadataHandlers — Krea-2 recall gating', () => { }); }); }); + +// `vae` and `qwen3_encoder` are shared metadata fields written by several bases, each recalling into its +// own params slot. Two axes must line up for a handler to fire: the *provenance* of the image +// (metadata.model.base) and the *currently selected* base (which decides which slot is live). These tests +// pin both, so a handler can never write a foreign base's model into its slot. +describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () => { + const fakeMain = (base: string) => ({ + key: 'main-key', + hash: 'main-hash', + name: `Some ${base} model`, + base, + type: 'main', + }); + + describe('AnimaVAEModel', () => { + it('parses an Anima VAE from an Anima image while Anima is selected', async () => { + currentBase = 'anima'; + nextResolved = fakeModel('vae', 'anima'); + const store = makeStore(); + + const parsed = await ImageMetadataHandlers.AnimaVAEModel.parse( + { model: fakeMain('anima'), vae: nextResolved }, + store + ); + + expect(parsed.key).toBe('vae-key'); + expect(parsed.base).toBe('anima'); + }); + + // Krea-2 may legitimately use an Anima VAE, so without the provenance check a Krea-2 image recalled + // while Anima is selected would push its VAE into the Anima slot. + it('rejects a Krea-2 image even while Anima is selected', async () => { + currentBase = 'anima'; + nextResolved = fakeModel('vae', 'anima'); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.AnimaVAEModel.parse({ model: fakeMain('krea-2'), vae: nextResolved }, store) + ).rejects.toThrow(); + }); + + it('rejects metadata that carries no model at all', async () => { + currentBase = 'anima'; + nextResolved = fakeModel('vae', 'anima'); + const store = makeStore(); + + await expect(ImageMetadataHandlers.AnimaVAEModel.parse({ vae: nextResolved }, store)).rejects.toThrow(); + }); + + it('rejects when the current base is not Anima', async () => { + currentBase = 'sdxl'; + nextResolved = fakeModel('vae', 'anima'); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.AnimaVAEModel.parse({ model: fakeMain('anima'), vae: nextResolved }, store) + ).rejects.toThrow(); + }); + + it('rejects a non-Anima VAE from Anima metadata', async () => { + currentBase = 'anima'; + nextResolved = fakeModel('vae', 'qwen-image'); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.AnimaVAEModel.parse({ model: fakeMain('anima'), vae: nextResolved }, store) + ).rejects.toThrow(); + }); + }); + + describe('AnimaQwen3EncoderModel', () => { + // Anima encoders are identified by variant (qwen3_06b), not by base — a base assert here would be + // wrong. Provenance plus the current base carry the whole gate. + it('parses regardless of the encoder base', async () => { + currentBase = 'anima'; + nextResolved = fakeModel('qwen3_encoder', 'any'); + const store = makeStore(); + + const parsed = await ImageMetadataHandlers.AnimaQwen3EncoderModel.parse( + { model: fakeMain('anima'), qwen3_encoder: nextResolved }, + store + ); + + expect(parsed.key).toBe('qwen3_encoder-key'); + expect(parsed.type).toBe('qwen3_encoder'); + }); + + it.each(['z-image', 'flux2'])('rejects %s image metadata, which writes the same field', async (base) => { + currentBase = 'anima'; + nextResolved = fakeModel('qwen3_encoder', 'any'); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.AnimaQwen3EncoderModel.parse( + { model: fakeMain(base), qwen3_encoder: nextResolved }, + store + ) + ).rejects.toThrow(); + }); + + it('rejects when the current base is not Anima', async () => { + currentBase = 'z-image'; + nextResolved = fakeModel('qwen3_encoder', 'any'); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.AnimaQwen3EncoderModel.parse( + { model: fakeMain('anima'), qwen3_encoder: nextResolved }, + store + ) + ).rejects.toThrow(); + }); + }); + + describe('ZImageQwen3EncoderModel', () => { + it('parses while Z-Image is selected', async () => { + currentBase = 'z-image'; + nextResolved = fakeModel('qwen3_encoder', 'any'); + const store = makeStore(); + + const parsed = await ImageMetadataHandlers.ZImageQwen3EncoderModel.parse({ qwen3_encoder: nextResolved }, store); + + expect(parsed.key).toBe('qwen3_encoder-key'); + }); + + // This handler recalls into the Z-Image slots (and nulls zImageQwen3SourceModel). Anima and FLUX.2 + // Klein write the same metadata field, so without the base gate they would clobber those slots. + it.each(['anima', 'flux2'])('rejects when the current base is %s', async (base) => { + currentBase = base; + nextResolved = fakeModel('qwen3_encoder', 'any'); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.ZImageQwen3EncoderModel.parse({ qwen3_encoder: nextResolved }, store) + ).rejects.toThrow(); + }); + }); + + describe('Flux1VAEModel', () => { + it('parses a FLUX VAE while FLUX.1 is selected', async () => { + currentBase = 'flux'; + nextResolved = fakeModel('vae', 'flux'); + const store = makeStore(); + + const parsed = await ImageMetadataHandlers.Flux1VAEModel.parse({ vae: nextResolved }, store); + + expect(parsed.key).toBe('vae-key'); + }); + + it('rejects when the current base is flux2, which has its own VAE slot', async () => { + currentBase = 'flux2'; + nextResolved = fakeModel('vae', 'flux'); + const store = makeStore(); + + await expect(ImageMetadataHandlers.Flux1VAEModel.parse({ vae: nextResolved }, store)).rejects.toThrow(); + }); + + it('rejects a non-FLUX VAE', async () => { + currentBase = 'flux'; + nextResolved = fakeModel('vae', 'sdxl'); + const store = makeStore(); + + await expect(ImageMetadataHandlers.Flux1VAEModel.parse({ vae: nextResolved }, store)).rejects.toThrow(); + }); + }); +}); diff --git a/invokeai/frontend/web/src/features/metadata/parsing.tsx b/invokeai/frontend/web/src/features/metadata/parsing.tsx index 86ca976a192..e2ebb0bf276 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.tsx @@ -12,6 +12,7 @@ import { animaVaeModelSelected, flux2DevMistralEncoderModelSelected, flux2VaeModelSelected, + fluxVAESelected, geminiTemperatureChanged, geminiThinkingLevelChanged, heightChanged, @@ -91,7 +92,7 @@ import { import { refImagesRecalled } from 'features/controlLayers/store/refImagesSlice'; import type { CanvasMetadata, LoRA, RefImageState } from 'features/controlLayers/store/types'; import { zCanvasMetadata, zCanvasReferenceImageState_OLD, zRefImageState } from 'features/controlLayers/store/types'; -import type { ModelIdentifierField, ModelType } from 'features/nodes/types/common'; +import type { BaseModelType, ModelIdentifierField, ModelType } from 'features/nodes/types/common'; import { zModelIdentifierField } from 'features/nodes/types/common'; import { zModelIdentifier } from 'features/nodes/types/v2/common'; import { modelSelected } from 'features/parameters/store/actions'; @@ -1482,6 +1483,26 @@ const MainModel: SingleMetadataHandler = { }; //#endregion MainModel +/** + * Bases with their own VAE slot in paramsSlice plus a dedicated handler, which nonetheless write to the + * shared `metadata.vae` field. The generic VAEModel handler must not fire for them, else the metadata + * panel renders a duplicate VAE row and "recall all" additionally writes into the (for those bases dead) + * `params.vae` slot. + * + * When adding a base here: a dedicated handler dispatching into the correct slot MUST exist, and it MUST + * be listed in IMAGE_METADATA_ACTION_HANDLERS - otherwise the row disappears without replacement. + * + * qwen-image and wan are deliberately absent: they write `qwen_image_vae` / `wan_vae_model` and never + * collide with this handler in the first place. + */ +const BASES_WITH_DEDICATED_VAE_HANDLER: ReadonlySet = new Set([ + 'flux', // Flux1VAEModel -> params.fluxVAE + 'z-image', // ZImageVAEModel -> params.zImageVaeModel + 'flux2', // Flux2VAEModel -> params.flux2VaeModel (Klein + [dev]) + 'krea-2', // Krea2VAEModel -> params.krea2VaeModel + 'anima', // AnimaVAEModel -> params.animaVaeModel +]); + //#region VAEModel const VAEModel: SingleMetadataHandler = { [SingleMetadataKey]: true, @@ -1491,11 +1512,10 @@ const VAEModel: SingleMetadataHandler = { const parsed = await parseModelIdentifier(raw, store, 'vae'); assert(parsed.type === 'vae'); assert(isCompatibleWithMainModel(parsed, store)); - // Z-Image, FLUX.2 Klein and Krea-2 have dedicated VAE handlers; avoid rendering a duplicate row. const base = selectBase(store.getState()); assert( - base !== 'z-image' && base !== 'flux2' && base !== 'krea-2', - 'VAEModel handler does not apply to Z-Image, FLUX.2 Klein or Krea-2' + !base || !BASES_WITH_DEDICATED_VAE_HANDLER.has(base), + `VAEModel handler does not apply to base "${base}" - it has a dedicated VAE handler` ); return Promise.resolve(parsed); }, @@ -1510,14 +1530,48 @@ const VAEModel: SingleMetadataHandler = { }; //#endregion VAEModel -//#region Qwen3EncoderModel -const Qwen3EncoderModel: SingleMetadataHandler = { +//#region Flux1VAEModel +/** + * FLUX.1 keeps its VAE in a dedicated slot (`params.fluxVAE`, read by buildFLUXGraph) but records it in + * the shared `metadata.vae` field. Without this handler the generic VAEModel would recall it into + * `params.vae`, which no FLUX graph ever reads - the recall looked like it worked but had no effect. + */ +const Flux1VAEModel: SingleMetadataHandler = { [SingleMetadataKey]: true, - type: 'Qwen3EncoderModel', + type: 'Flux1VAEModel', + parse: async (metadata, store) => { + const raw = getProperty(metadata, 'vae'); + const parsed = await parseModelIdentifier(raw, store, 'vae'); + assert(parsed.type === 'vae'); + assert(parsed.base === 'flux', 'Flux1VAEModel requires a FLUX VAE'); + const base = selectBase(store.getState()); + assert(base === 'flux', 'Flux1VAEModel handler only works with FLUX.1 models'); + return parsed; + }, + recall: (value, store) => { + store.dispatch(fluxVAESelected(value)); + }, + i18nKey: 'metadata.vae', + LabelComponent: MetadataLabel, + ValueComponent: ({ value }: SingleMetadataValueProps) => ( + + ), +}; +//#endregion Flux1VAEModel + +//#region ZImageQwen3EncoderModel +const ZImageQwen3EncoderModel: SingleMetadataHandler = { + [SingleMetadataKey]: true, + type: 'ZImageQwen3EncoderModel', parse: async (metadata, store) => { const raw = getProperty(metadata, 'qwen3_encoder'); const parsed = await parseModelIdentifier(raw, store, 'qwen3_encoder'); assert(parsed.type === 'qwen3_encoder'); + // `qwen3_encoder` is a shared field: Z-Image, Anima and FLUX.2 Klein all write it, each into a + // different slot. The encoder itself cannot disambiguate them (Klein and Z-Image encoders both + // satisfy isQwen3EncoderModelConfig), so gate on the currently selected base instead. + const base = selectBase(store.getState()); + assert(base === 'z-image', 'ZImageQwen3EncoderModel handler only works with Z-Image models'); return Promise.resolve(parsed); }, recall: (value, store) => { @@ -1531,7 +1585,7 @@ const Qwen3EncoderModel: SingleMetadataHandler = { ), }; -//#endregion Qwen3EncoderModel +//#endregion ZImageQwen3EncoderModel //#region T5EncoderModel const T5EncoderModel: SingleMetadataHandler = { @@ -1791,9 +1845,15 @@ const AnimaVAEModel: SingleMetadataHandler = { [SingleMetadataKey]: true, type: 'AnimaVAEModel', parse: async (metadata, store) => { + // Check provenance: `vae` is a shared field (SD/SDXL/SD3/FLUX/FLUX.2/Krea-2/Z-Image write it too). + // Without this, a Krea-2 image recalled while Anima is selected would push its Qwen-Image VAE into + // the Anima slot. + assertMetadataModelBase(metadata, 'anima', 'AnimaVAEModel'); const raw = getProperty(metadata, 'vae'); const parsed = await parseModelIdentifier(raw, store, 'vae'); assert(parsed.type === 'vae'); + // isAnimaVAEModelConfig (services/api/types.ts) is base-driven, so this assert is meaningful here. + assert(parsed.base === 'anima', 'AnimaVAEModel requires an Anima VAE'); const base = selectBase(store.getState()); assert(base === 'anima', 'AnimaVAEModel handler only works with Anima models'); return Promise.resolve(parsed); @@ -1814,9 +1874,14 @@ const AnimaQwen3EncoderModel: SingleMetadataHandler = { [SingleMetadataKey]: true, type: 'AnimaQwen3EncoderModel', parse: async (metadata, store) => { + // Check provenance: `qwen3_encoder` is also written by Z-Image and FLUX.2 Klein. + assertMetadataModelBase(metadata, 'anima', 'AnimaQwen3EncoderModel'); const raw = getProperty(metadata, 'qwen3_encoder'); const parsed = await parseModelIdentifier(raw, store, 'qwen3_encoder'); assert(parsed.type === 'qwen3_encoder'); + // Deliberately no `parsed.base` assert: Anima encoders are identified by `variant` (qwen3_06b), not + // by base - see isAnimaQwen3EncoderModelConfig. The provenance check above already guarantees the + // value came out of buildAnimaGraph, i.e. out of selectAnimaQwen3EncoderModels. const base = selectBase(store.getState()); assert(base === 'anima', 'AnimaQwen3EncoderModel handler only works with Anima models'); return Promise.resolve(parsed); @@ -2309,7 +2374,8 @@ export const ImageMetadataHandlers = { // Scheduler must be after MainModel so that base-dependent logic (z-image scheduler) works correctly Scheduler, VAEModel, - Qwen3EncoderModel, + Flux1VAEModel, + ZImageQwen3EncoderModel, T5EncoderModel, ZImageVAEModel, ZImageQwen3SourceModel, From 59892d066c08b9f226053645c951414642fa8d6c Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Mon, 10 Aug 2026 18:10:45 +0200 Subject: [PATCH 02/12] feat(nodes): add an Anima denoise node that records its own metadata Workflows only carry recallable parameters if the graph builds the metadata itself. `metadata_linked.py` offers two ways to do that: chaining Metadata Item Linked nodes, or a denoise node that writes its own metadata - but the latter existed only for SD, FLUX.1 and Z-Image. Add `anima_denoise_meta`, mirroring `z_image_denoise_meta`, with two Anima specifics: - The CFG value is recorded as `cfg_scale`, matching both the Anima graph builder and the UI's recall handler. Z-Image writes `guidance`, which for Anima would land in the FLUX guidance param instead. - The VAE and Qwen3 encoder are exposed as optional inputs. Anima loads both standalone, so unlike SD/FLUX they cannot be derived from the transformer field, and without them a recalled Anima image would be missing exactly the two components that are hardest to pick by hand. Unconnected inputs write nothing rather than a null the recall handlers would fail to resolve. --- invokeai/app/invocations/metadata_linked.py | 78 ++++ invokeai/frontend/web/openapi.json | 356 ++++++++++++++++++ .../frontend/web/src/services/api/schema.ts | 151 +++++++- .../invocations/test_denoise_noise_inputs.py | 98 +++++ 4 files changed, 678 insertions(+), 5 deletions(-) diff --git a/invokeai/app/invocations/metadata_linked.py b/invokeai/app/invocations/metadata_linked.py index 3ee70440436..5f8c18ecc98 100644 --- a/invokeai/app/invocations/metadata_linked.py +++ b/invokeai/app/invocations/metadata_linked.py @@ -7,6 +7,7 @@ from pydantic import model_validator +from invokeai.app.invocations.anima_denoise import AnimaDenoiseInvocation from invokeai.app.invocations.baseinvocation import ( BaseInvocation, BaseInvocationOutput, @@ -34,6 +35,7 @@ LoRAField, LoRALoaderOutput, ModelIdentifierField, + Qwen3EncoderField, SDXLLoRALoaderOutput, UNetField, VAEField, @@ -793,6 +795,82 @@ def _loras_to_json(obj: Union[Any, list[Any]]): return LatentsMetaOutput(**params, metadata=MetadataField.model_validate(md)) +@invocation( + "anima_denoise_meta", + title=f"{AnimaDenoiseInvocation.UIConfig.title} + Metadata", + tags=["anima", "latents", "denoise", "txt2img", "t2i", "t2l", "img2img", "i2i", "l2l"], + category="metadata", + version="1.0.0", + classification=Classification.Prototype, +) +class AnimaDenoiseMetaInvocation(AnimaDenoiseInvocation, WithMetadata): + """Run denoising process with an Anima transformer model + metadata.""" + + # Anima loads its VAE and text encoder as standalone models, so - unlike SD/FLUX, where they come out of + # the main model - they cannot be derived from the transformer field. Accept them here so a workflow can + # record the full component set the recall UI expects, without chaining extra Metadata Item Linked nodes. + vae: Optional[VAEField] = InputField( + default=None, + description="Optional. The Anima VAE, recorded to metadata so recall can restore the VAE selection.", + input=Input.Connection, + title="VAE", + ) + qwen3_encoder: Optional[Qwen3EncoderField] = InputField( + default=None, + description="Optional. The Anima Qwen3 encoder, recorded to metadata so recall can restore the encoder " + "selection.", + input=Input.Connection, + title="Qwen3 Encoder", + ) + + def invoke(self, context: InvocationContext) -> LatentsMetaOutput: + def _loras_to_json(obj: Union[Any, list[Any]]): + if not isinstance(obj, list): + obj = [obj] + + output: list[dict[str, Any]] = [] + for item in obj: + output.append( + LoRAMetadataField( + model=item.lora, + weight=item.weight, + ).model_dump(exclude_none=True, exclude={"id", "type", "is_intermediate", "use_cache"}) + ) + return output + + obj = super().invoke(context) + + md: Dict[str, Any] = {} if self.metadata is None else self.metadata.root + md.update({"width": obj.width}) + md.update({"height": obj.height}) + md.update({"steps": self.steps}) + # Anima's CFG value is recorded as `cfg_scale`, matching what the Anima graph builder writes and what + # the UI's CFGScale recall handler reads - not as `guidance`, which is FLUX's guidance-embeds scale. + md.update({"cfg_scale": self.guidance_scale}) + md.update({"denoising_start": self.denoising_start}) + md.update({"denoising_end": self.denoising_end}) + md.update({"scheduler": self.scheduler}) + md.update({"model": self.transformer.transformer}) + md.update( + { + "seed": self.noise.seed + if self.noise is not None and self.noise.seed is not None and (self.latents is None or self.add_noise) + else self.seed + } + ) + if self.vae is not None: + md.update({"vae": self.vae.vae}) + if self.qwen3_encoder is not None: + md.update({"qwen3_encoder": self.qwen3_encoder.text_encoder}) + if len(self.transformer.loras) > 0: + md.update({"loras": _loras_to_json(self.transformer.loras)}) + + params = obj.__dict__.copy() + del params["type"] + + return LatentsMetaOutput(**params, metadata=MetadataField.model_validate(md)) + + @invocation( "metadata_to_vae", title="Metadata To VAE", diff --git a/invokeai/frontend/web/openapi.json b/invokeai/frontend/web/openapi.json index 0499e6f426d..214a20c1864 100644 --- a/invokeai/frontend/web/openapi.json +++ b/invokeai/frontend/web/openapi.json @@ -13345,6 +13345,343 @@ "$ref": "#/components/schemas/LatentsOutput" } }, + "AnimaDenoiseMetaInvocation": { + "category": "metadata", + "class": "invocation", + "classification": "prototype", + "description": "Run denoising process with an Anima transformer model + metadata.", + "node_pack": "invokeai", + "properties": { + "metadata": { + "anyOf": [ + { + "$ref": "#/components/schemas/MetadataField" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Optional metadata to be saved with the image", + "field_kind": "internal", + "input": "connection", + "orig_required": false, + "ui_hidden": false + }, + "id": { + "description": "The id of this instance of an invocation. Must be unique among all instances of invocations.", + "field_kind": "node_attribute", + "title": "Id", + "type": "string" + }, + "is_intermediate": { + "default": false, + "description": "Whether or not this is an intermediate invocation.", + "field_kind": "node_attribute", + "input": "direct", + "orig_required": true, + "title": "Is Intermediate", + "type": "boolean", + "ui_hidden": false, + "ui_type": "IsIntermediate" + }, + "use_cache": { + "default": true, + "description": "Whether or not to use the cache", + "field_kind": "node_attribute", + "title": "Use Cache", + "type": "boolean" + }, + "latents": { + "anyOf": [ + { + "$ref": "#/components/schemas/LatentsField" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Latents tensor", + "field_kind": "input", + "input": "connection", + "orig_default": null, + "orig_required": false + }, + "noise": { + "anyOf": [ + { + "$ref": "#/components/schemas/LatentsField" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Noise tensor", + "field_kind": "input", + "input": "connection", + "orig_default": null, + "orig_required": false + }, + "denoise_mask": { + "anyOf": [ + { + "$ref": "#/components/schemas/DenoiseMaskField" + }, + { + "type": "null" + } + ], + "default": null, + "description": "A mask of the region to apply the denoising process to. Values of 0.0 represent the regions to be fully denoised, and 1.0 represent the regions to be preserved.", + "field_kind": "input", + "input": "connection", + "orig_default": null, + "orig_required": false + }, + "denoising_start": { + "default": 0.0, + "description": "When to start denoising, expressed a percentage of total steps", + "field_kind": "input", + "input": "any", + "maximum": 1, + "minimum": 0, + "orig_default": 0.0, + "orig_required": false, + "title": "Denoising Start", + "type": "number" + }, + "denoising_end": { + "default": 1.0, + "description": "When to stop denoising, expressed a percentage of total steps", + "field_kind": "input", + "input": "any", + "maximum": 1, + "minimum": 0, + "orig_default": 1.0, + "orig_required": false, + "title": "Denoising End", + "type": "number" + }, + "add_noise": { + "default": true, + "description": "Add noise based on denoising start.", + "field_kind": "input", + "input": "any", + "orig_default": true, + "orig_required": false, + "title": "Add Noise", + "type": "boolean" + }, + "transformer": { + "anyOf": [ + { + "$ref": "#/components/schemas/TransformerField" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Anima transformer model.", + "field_kind": "input", + "input": "connection", + "orig_required": true, + "title": "Transformer" + }, + "positive_conditioning": { + "anyOf": [ + { + "$ref": "#/components/schemas/AnimaConditioningField" + }, + { + "items": { + "$ref": "#/components/schemas/AnimaConditioningField" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Positive conditioning tensor", + "field_kind": "input", + "input": "connection", + "orig_required": true, + "title": "Positive Conditioning" + }, + "negative_conditioning": { + "anyOf": [ + { + "$ref": "#/components/schemas/AnimaConditioningField" + }, + { + "items": { + "$ref": "#/components/schemas/AnimaConditioningField" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Negative conditioning tensor", + "field_kind": "input", + "input": "connection", + "orig_default": null, + "orig_required": false, + "title": "Negative Conditioning" + }, + "guidance_scale": { + "default": 4.5, + "description": "Guidance scale for classifier-free guidance. Recommended: 4.0-5.0 for Anima.", + "field_kind": "input", + "input": "any", + "minimum": 1.0, + "orig_default": 4.5, + "orig_required": false, + "title": "Guidance Scale", + "type": "number" + }, + "width": { + "default": 1024, + "description": "Width of the generated image.", + "field_kind": "input", + "input": "any", + "multipleOf": 8, + "orig_default": 1024, + "orig_required": false, + "title": "Width", + "type": "integer" + }, + "height": { + "default": 1024, + "description": "Height of the generated image.", + "field_kind": "input", + "input": "any", + "multipleOf": 8, + "orig_default": 1024, + "orig_required": false, + "title": "Height", + "type": "integer" + }, + "steps": { + "default": 30, + "description": "Number of denoising steps. 30 recommended for Anima.", + "exclusiveMinimum": 0, + "field_kind": "input", + "input": "any", + "orig_default": 30, + "orig_required": false, + "title": "Steps", + "type": "integer" + }, + "seed": { + "default": 0, + "description": "Randomness seed for reproducibility.", + "field_kind": "input", + "input": "any", + "orig_default": 0, + "orig_required": false, + "title": "Seed", + "type": "integer" + }, + "control_lllite": { + "anyOf": [ + { + "$ref": "#/components/schemas/AnimaLLLiteField" + }, + { + "items": { + "$ref": "#/components/schemas/AnimaLLLiteField" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Anima ControlNet-LLLite conditioning (e.g. inpaint adapter, control layers). Adapters are applied in a deterministic order (sorted by model key); each model may be used at most once.", + "field_kind": "input", + "input": "connection", + "orig_default": null, + "orig_required": false, + "title": "Control Lllite" + }, + "scheduler": { + "default": "euler", + "description": "Scheduler (sampler) for the denoising process.", + "enum": ["euler", "heun", "dpmpp_2m", "dpmpp_2m_sde", "er_sde", "lcm"], + "field_kind": "input", + "input": "any", + "orig_default": "euler", + "orig_required": false, + "title": "Scheduler", + "type": "string", + "ui_choice_labels": { + "dpmpp_2m": "DPM++ 2M", + "dpmpp_2m_sde": "DPM++ 2M SDE", + "er_sde": "ER-SDE", + "euler": "Euler", + "heun": "Heun (2nd order)", + "lcm": "LCM" + } + }, + "type": { + "const": "anima_denoise_meta", + "default": "anima_denoise_meta", + "field_kind": "node_attribute", + "title": "type", + "type": "string" + }, + "vae": { + "anyOf": [ + { + "$ref": "#/components/schemas/VAEField" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Optional. The Anima VAE, recorded to metadata so recall can restore the VAE selection.", + "field_kind": "input", + "input": "connection", + "orig_default": null, + "orig_required": false, + "title": "VAE" + }, + "qwen3_encoder": { + "anyOf": [ + { + "$ref": "#/components/schemas/Qwen3EncoderField" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Optional. The Anima Qwen3 encoder, recorded to metadata so recall can restore the encoder selection.", + "field_kind": "input", + "input": "connection", + "orig_default": null, + "orig_required": false, + "title": "Qwen3 Encoder" + } + }, + "required": ["type", "id"], + "tags": ["anima", "latents", "denoise", "txt2img", "t2i", "t2l", "img2img", "i2i", "l2l"], + "title": "Denoise - Anima + Metadata", + "type": "object", + "version": "1.0.0", + "output": { + "$ref": "#/components/schemas/LatentsMetaOutput" + } + }, "AnimaImageToLatentsInvocation": { "category": "image", "class": "invocation", @@ -34484,6 +34821,9 @@ { "$ref": "#/components/schemas/AnimaDenoiseInvocation" }, + { + "$ref": "#/components/schemas/AnimaDenoiseMetaInvocation" + }, { "$ref": "#/components/schemas/AnimaImageToLatentsInvocation" }, @@ -43019,6 +43359,9 @@ { "$ref": "#/components/schemas/AnimaDenoiseInvocation" }, + { + "$ref": "#/components/schemas/AnimaDenoiseMetaInvocation" + }, { "$ref": "#/components/schemas/AnimaImageToLatentsInvocation" }, @@ -44367,6 +44710,9 @@ { "$ref": "#/components/schemas/AnimaDenoiseInvocation" }, + { + "$ref": "#/components/schemas/AnimaDenoiseMetaInvocation" + }, { "$ref": "#/components/schemas/AnimaImageToLatentsInvocation" }, @@ -45333,6 +45679,9 @@ "anima_denoise": { "$ref": "#/components/schemas/LatentsOutput" }, + "anima_denoise_meta": { + "$ref": "#/components/schemas/LatentsMetaOutput" + }, "anima_i2l": { "$ref": "#/components/schemas/LatentsOutput" }, @@ -46248,6 +46597,7 @@ "alibabacloud_image_generation", "alpha_mask_to_tensor", "anima_denoise", + "anima_denoise_meta", "anima_i2l", "anima_l2i", "anima_lllite", @@ -46628,6 +46978,9 @@ { "$ref": "#/components/schemas/AnimaDenoiseInvocation" }, + { + "$ref": "#/components/schemas/AnimaDenoiseMetaInvocation" + }, { "$ref": "#/components/schemas/AnimaImageToLatentsInvocation" }, @@ -47685,6 +48038,9 @@ { "$ref": "#/components/schemas/AnimaDenoiseInvocation" }, + { + "$ref": "#/components/schemas/AnimaDenoiseMetaInvocation" + }, { "$ref": "#/components/schemas/AnimaImageToLatentsInvocation" }, diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index 2928b7170d6..3fc304b7d3d 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -3728,6 +3728,146 @@ export type components = { */ type: "anima_denoise"; }; + /** + * Denoise - Anima + Metadata + * @description Run denoising process with an Anima transformer model + metadata. + */ + AnimaDenoiseMetaInvocation: { + /** + * @description Optional metadata to be saved with the image + * @default null + */ + metadata?: components["schemas"]["MetadataField"] | null; + /** + * Id + * @description The id of this instance of an invocation. Must be unique among all instances of invocations. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this is an intermediate invocation. + * @default false + */ + is_intermediate?: boolean; + /** + * Use Cache + * @description Whether or not to use the cache + * @default true + */ + use_cache?: boolean; + /** + * @description Latents tensor + * @default null + */ + latents?: components["schemas"]["LatentsField"] | null; + /** + * @description Noise tensor + * @default null + */ + noise?: components["schemas"]["LatentsField"] | null; + /** + * @description A mask of the region to apply the denoising process to. Values of 0.0 represent the regions to be fully denoised, and 1.0 represent the regions to be preserved. + * @default null + */ + denoise_mask?: components["schemas"]["DenoiseMaskField"] | null; + /** + * Denoising Start + * @description When to start denoising, expressed a percentage of total steps + * @default 0 + */ + denoising_start?: number; + /** + * Denoising End + * @description When to stop denoising, expressed a percentage of total steps + * @default 1 + */ + denoising_end?: number; + /** + * Add Noise + * @description Add noise based on denoising start. + * @default true + */ + add_noise?: boolean; + /** + * Transformer + * @description Anima transformer model. + * @default null + */ + transformer?: components["schemas"]["TransformerField"] | null; + /** + * Positive Conditioning + * @description Positive conditioning tensor + * @default null + */ + positive_conditioning?: components["schemas"]["AnimaConditioningField"] | components["schemas"]["AnimaConditioningField"][] | null; + /** + * Negative Conditioning + * @description Negative conditioning tensor + * @default null + */ + negative_conditioning?: components["schemas"]["AnimaConditioningField"] | components["schemas"]["AnimaConditioningField"][] | null; + /** + * Guidance Scale + * @description Guidance scale for classifier-free guidance. Recommended: 4.0-5.0 for Anima. + * @default 4.5 + */ + guidance_scale?: number; + /** + * Width + * @description Width of the generated image. + * @default 1024 + */ + width?: number; + /** + * Height + * @description Height of the generated image. + * @default 1024 + */ + height?: number; + /** + * Steps + * @description Number of denoising steps. 30 recommended for Anima. + * @default 30 + */ + steps?: number; + /** + * Seed + * @description Randomness seed for reproducibility. + * @default 0 + */ + seed?: number; + /** + * Control Lllite + * @description Anima ControlNet-LLLite conditioning (e.g. inpaint adapter, control layers). Adapters are applied in a deterministic order (sorted by model key); each model may be used at most once. + * @default null + */ + control_lllite?: components["schemas"]["AnimaLLLiteField"] | components["schemas"]["AnimaLLLiteField"][] | null; + /** + * Scheduler + * @description Scheduler (sampler) for the denoising process. + * @default euler + * @enum {string} + */ + scheduler?: "euler" | "heun" | "dpmpp_2m" | "dpmpp_2m_sde" | "er_sde" | "lcm"; + /** + * type + * @default anima_denoise_meta + * @constant + */ + type: "anima_denoise_meta"; + /** + * VAE + * @description Optional. The Anima VAE, recorded to metadata so recall can restore the VAE selection. + * @default null + */ + vae?: components["schemas"]["VAEField"] | null; + /** + * Qwen3 Encoder + * @description Optional. The Anima Qwen3 encoder, recorded to metadata so recall can restore the encoder selection. + * @default null + */ + qwen3_encoder?: components["schemas"]["Qwen3EncoderField"] | null; + }; /** * Image to Latents - Anima * @description Generates latents from an image using the Anima VAE (supports Wan 2.1 and FLUX VAE). @@ -14507,7 +14647,7 @@ export type components = { * @description The nodes in this graph */ nodes?: { - [key: string]: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; + [key: string]: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaDenoiseMetaInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; }; /** * Edges @@ -18332,7 +18472,7 @@ export type components = { * Invocation * @description The ID of the invocation */ - invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; + invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaDenoiseMetaInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; /** * Invocation Source Id * @description The ID of the prepared invocation's source node @@ -18396,7 +18536,7 @@ export type components = { * Invocation * @description The ID of the invocation */ - invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; + invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaDenoiseMetaInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; /** * Invocation Source Id * @description The ID of the prepared invocation's source node @@ -18423,6 +18563,7 @@ export type components = { alibabacloud_image_generation: components["schemas"]["ImageCollectionOutput"]; alpha_mask_to_tensor: components["schemas"]["MaskOutput"]; anima_denoise: components["schemas"]["LatentsOutput"]; + anima_denoise_meta: components["schemas"]["LatentsMetaOutput"]; anima_i2l: components["schemas"]["LatentsOutput"]; anima_l2i: components["schemas"]["ImageOutput"]; anima_lllite: components["schemas"]["AnimaLLLiteOutput"]; @@ -18779,7 +18920,7 @@ export type components = { * Invocation * @description The ID of the invocation */ - invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; + invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaDenoiseMetaInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; /** * Invocation Source Id * @description The ID of the prepared invocation's source node @@ -18860,7 +19001,7 @@ export type components = { * Invocation * @description The ID of the invocation */ - invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; + invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlibabaCloudImageGenerationInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["AnimaDenoiseInvocation"] | components["schemas"]["AnimaDenoiseMetaInvocation"] | components["schemas"]["AnimaImageToLatentsInvocation"] | components["schemas"]["AnimaLLLiteInvocation"] | components["schemas"]["AnimaLatentsToImageInvocation"] | components["schemas"]["AnimaLoRACollectionLoader"] | components["schemas"]["AnimaLoRALoaderInvocation"] | components["schemas"]["AnimaModelLoaderInvocation"] | components["schemas"]["AnimaTextEncoderInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CallSavedWorkflowInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ErnieImageDenoiseInvocation"] | components["schemas"]["ErnieImageModelLoaderInvocation"] | components["schemas"]["ErnieImagePromptEnhancerInvocation"] | components["schemas"]["ErnieImageTextEncoderInvocation"] | components["schemas"]["ErnieImageVaeDecodeInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["ExtractVideoRangeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2DevLoRACollectionLoader"] | components["schemas"]["Flux2DevLoRALoaderInvocation"] | components["schemas"]["Flux2DevModelLoaderInvocation"] | components["schemas"]["Flux2DevTextEncoderInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2PiDDecodeInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxPiDDecodeInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GeminiImageGenerationInvocation"] | components["schemas"]["Gemma2EncoderLoaderInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["Ideogram4CaptionBuilderInvocation"] | components["schemas"]["Ideogram4DenoiseInvocation"] | components["schemas"]["Ideogram4LatentsToImageInvocation"] | components["schemas"]["Ideogram4ModelLoaderInvocation"] | components["schemas"]["Ideogram4TextEncoderInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["Krea2ConditioningRebalanceInvocation"] | components["schemas"]["Krea2DenoiseInvocation"] | components["schemas"]["Krea2LoRACollectionLoader"] | components["schemas"]["Krea2LoRALoaderInvocation"] | components["schemas"]["Krea2ModelLoaderInvocation"] | components["schemas"]["Krea2SeedVarianceInvocation"] | components["schemas"]["Krea2TextEncoderInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["OklabUnsharpMaskInvocation"] | components["schemas"]["OklchImageHueAdjustmentInvocation"] | components["schemas"]["OpenAIImageGenerationInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDDecoderLoaderInvocation"] | components["schemas"]["PiDUpscaleInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["QwenImageDenoiseInvocation"] | components["schemas"]["QwenImageImageToLatentsInvocation"] | components["schemas"]["QwenImageLatentsToImageInvocation"] | components["schemas"]["QwenImageLoRACollectionLoader"] | components["schemas"]["QwenImageLoRALoaderInvocation"] | components["schemas"]["QwenImageModelLoaderInvocation"] | components["schemas"]["QwenImagePiDDecodeInvocation"] | components["schemas"]["QwenImageTextEncoderInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SD3PiDDecodeInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLPiDDecodeInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["SaveImageToFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SeedreamImageGenerationInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TextLLMInvocation"] | components["schemas"]["TextLLMWithPresetInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["VideoConcatInvocation"] | components["schemas"]["VideoFrameExtractInvocation"] | components["schemas"]["VideoInvocation"] | components["schemas"]["WanDenoiseInvocation"] | components["schemas"]["WanI2VIdealDimensionsInvocation"] | components["schemas"]["WanImageToLatentsInvocation"] | components["schemas"]["WanLatentsToImageInvocation"] | components["schemas"]["WanLatentsToVideoInvocation"] | components["schemas"]["WanLoRACollectionLoader"] | components["schemas"]["WanLoRALoaderInvocation"] | components["schemas"]["WanModelLoaderInvocation"] | components["schemas"]["WanRefImageEncoderInvocation"] | components["schemas"]["WanTI2VIdealDimensionsInvocation"] | components["schemas"]["WanTextEncoderInvocation"] | components["schemas"]["WanVideoDenoiseInvocation"] | components["schemas"]["WorkflowReturnGetInvocation"] | components["schemas"]["WorkflowReturnInvocation"] | components["schemas"]["WorkflowReturnValueInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImagePiDDecodeInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"]; /** * Invocation Source Id * @description The ID of the prepared invocation's source node diff --git a/tests/app/invocations/test_denoise_noise_inputs.py b/tests/app/invocations/test_denoise_noise_inputs.py index eb11568f961..409c8b8b081 100644 --- a/tests/app/invocations/test_denoise_noise_inputs.py +++ b/tests/app/invocations/test_denoise_noise_inputs.py @@ -10,6 +10,7 @@ from invokeai.app.invocations.flux2_denoise import Flux2DenoiseInvocation from invokeai.app.invocations.flux_denoise import FluxDenoiseInvocation from invokeai.app.invocations.metadata_linked import ( + AnimaDenoiseMetaInvocation, DenoiseLatentsMetaInvocation, FluxDenoiseLatentsMetaInvocation, ZImageDenoiseMetaInvocation, @@ -396,6 +397,103 @@ def test_z_image_metadata_ignores_external_noise_seed_when_noise_not_used(): assert result.metadata.root["seed"] == 999 +def test_anima_metadata_ignores_external_noise_seed_when_noise_not_used(): + invocation = AnimaDenoiseMetaInvocation.model_construct( + width=64, + height=64, + steps=30, + guidance_scale=4.5, + denoising_start=0.0, + denoising_end=1.0, + scheduler="euler", + latents=MagicMock(latents_name="latents"), + transformer=MagicMock(transformer="transformer", loras=[]), + noise=MagicMock(seed=123), + seed=999, + add_noise=False, + vae=None, + qwen3_encoder=None, + ) + mock_context = MagicMock() + output = LatentsOutput.build("latents", torch.zeros(1, 16, 8, 8), seed=None) + + with patch("invokeai.app.invocations.metadata_linked.AnimaDenoiseInvocation.invoke", return_value=output): + result = invocation.invoke(mock_context) + + assert result.metadata.root["seed"] == 999 + + +def test_anima_metadata_matches_the_recall_contract(): + """The keys this node writes are what the gallery's recall handlers read. + + Anima's CFG must land in `cfg_scale` (not `guidance`, which is FLUX's guidance-embeds scale and is + recalled into a different param), and the standalone VAE / Qwen3 encoder must be recorded, since + unlike SD or FLUX they cannot be derived from the transformer field. + """ + invocation = AnimaDenoiseMetaInvocation.model_construct( + width=64, + height=64, + steps=30, + guidance_scale=4.5, + denoising_start=0.0, + denoising_end=1.0, + scheduler="dpmpp_2m", + latents=None, + transformer=SimpleNamespace(transformer="anima-main-model", loras=[]), + noise=MagicMock(seed=123), + seed=999, + add_noise=True, + vae=SimpleNamespace(vae="anima-vae"), + qwen3_encoder=SimpleNamespace(text_encoder="anima-qwen3-encoder"), + ) + mock_context = MagicMock() + output = LatentsOutput.build("latents", torch.zeros(1, 16, 8, 8), seed=None) + + with patch("invokeai.app.invocations.metadata_linked.AnimaDenoiseInvocation.invoke", return_value=output): + result = invocation.invoke(mock_context) + + md = result.metadata.root + assert md["cfg_scale"] == 4.5 + assert "guidance" not in md + assert md["model"] == "anima-main-model" + assert md["vae"] == "anima-vae" + assert md["qwen3_encoder"] == "anima-qwen3-encoder" + assert md["scheduler"] == "dpmpp_2m" + assert md["steps"] == 30 + assert md["seed"] == 123 + assert md["width"] == 64 + assert md["height"] == 64 + + +def test_anima_metadata_omits_unconnected_standalone_components(): + """Both component inputs are optional - an unconnected one must not write a null into metadata, + which the recall handlers would then try (and fail) to resolve as a model.""" + invocation = AnimaDenoiseMetaInvocation.model_construct( + width=64, + height=64, + steps=30, + guidance_scale=4.5, + denoising_start=0.0, + denoising_end=1.0, + scheduler="euler", + latents=None, + transformer=SimpleNamespace(transformer="anima-main-model", loras=[]), + noise=MagicMock(seed=123), + seed=999, + add_noise=True, + vae=None, + qwen3_encoder=None, + ) + mock_context = MagicMock() + output = LatentsOutput.build("latents", torch.zeros(1, 16, 8, 8), seed=None) + + with patch("invokeai.app.invocations.metadata_linked.AnimaDenoiseInvocation.invoke", return_value=output): + result = invocation.invoke(mock_context) + + assert "vae" not in result.metadata.root + assert "qwen3_encoder" not in result.metadata.root + + def test_denoise_metadata_persists_hidiffusion_fields_when_disabled(): invocation = DenoiseLatentsMetaInvocation.model_construct( width=64, From 91983cf1374e8518459011c8e89a263c720b834b Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Wed, 19 Aug 2026 03:16:27 +0200 Subject: [PATCH 03/12] fix(ui): gate shared-field metadata recall on image provenance VAE and text-encoder handlers that read a shared metadata field (`vae`, `qwen3_encoder`, `qwen3_source`) checked only the currently selected base, not where the image came from. Viewing one model's image while another base was selected could write a foreign model into the live slot, and in the Z-Image case silently clear the user's Qwen3 source selection. All six affected handlers now assert `metadata.model.base` in addition to the selected base. Also widen the Anima VAE slot to FLUX VAEs. The Anima model loader carries no `ui_model_base` on its VAE input and both `anima_l2i` and `anima_i2l` branch explicitly on `FluxAutoEncoder`, so a workflow-built Anima image may legitimately record one. This needs a separate `isAnimaCompatibleVAEModelConfig`: `isAnimaVAEModelConfig` also feeds Krea-2's VAE pool and must stay base-driven. --- .../listeners/modelSelected.test.ts | 17 ++ .../listeners/modelSelected.ts | 3 +- .../src/features/metadata/parsing.test.tsx | 189 ++++++++++++++++-- .../web/src/features/metadata/parsing.tsx | 27 ++- .../Advanced/ParamAnimaModelSelect.tsx | 7 +- .../src/services/api/hooks/modelsByType.ts | 4 + .../web/src/services/api/types.test.ts | 24 +++ .../frontend/web/src/services/api/types.ts | 16 ++ 8 files changed, 267 insertions(+), 20 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.test.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.test.ts index 4d14e8d4758..dcf2b843cb9 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.test.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.test.ts @@ -21,6 +21,17 @@ const mockAnimaVAE = { format: 'diffusers' as const, }; +// The Anima loader accepts a FLUX VAE as a fallback, so it belongs to the Anima *slot* pool +// (selectAnimaCompatibleVAEModels) but not to the base-driven pool Krea-2 draws from. +const mockFluxVAE = { + key: 'flux-vae-key', + hash: 'flux-vae-hash', + name: 'FLUX VAE', + base: 'flux' as const, + type: 'vae' as const, + format: 'checkpoint' as const, +}; + const mockAnimaMainModel = { key: 'anima-main-key', hash: 'anima-main-hash', @@ -134,6 +145,7 @@ vi.mock('i18next', () => ({ const mockSelectAnimaQwen3EncoderModels = vi.fn((_state: unknown) => [mockAnimaQwen3Encoder]); const mockSelectAnimaVAEModels = vi.fn((_state: unknown) => [mockAnimaVAE]); +const mockSelectAnimaCompatibleVAEModels = vi.fn((_state: unknown) => [mockAnimaVAE] as unknown[]); // Krea-2 standalone-component selectors (used only by the Krea-2 auto-select branch). const mockSelectQwenImageVAEModels = vi.fn((_state: unknown) => [mockKrea2Vae]); @@ -143,6 +155,7 @@ const mockSelectZImageDiffusersModels = vi.fn((_state: unknown) => [] as unknown vi.mock('services/api/hooks/modelsByType', () => ({ selectAnimaQwen3EncoderModels: (state: unknown) => mockSelectAnimaQwen3EncoderModels(state), selectAnimaVAEModels: (state: unknown) => mockSelectAnimaVAEModels(state), + selectAnimaCompatibleVAEModels: (state: unknown) => mockSelectAnimaCompatibleVAEModels(state), selectQwenImageVAEModels: (state: unknown) => mockSelectQwenImageVAEModels(state), selectQwen3VLEncoderModels: (state: unknown) => mockSelectQwen3VLEncoderModels(state), selectQwen3EncoderModels: vi.fn(() => []), @@ -279,6 +292,7 @@ describe('modelSelected listener - Anima defaulting', () => { mockDispatch.mockClear(); mockSelectAnimaQwen3EncoderModels.mockReturnValue([mockAnimaQwen3Encoder]); mockSelectAnimaVAEModels.mockReturnValue([mockAnimaVAE]); + mockSelectAnimaCompatibleVAEModels.mockReturnValue([mockAnimaVAE]); }); it('should dispatch encoder models with full ModelIdentifierField payloads when switching to Anima', () => { @@ -350,6 +364,7 @@ describe('modelSelected listener - Anima defaulting', () => { it('should not dispatch encoder defaults when no encoder models are available', () => { mockSelectAnimaQwen3EncoderModels.mockReturnValue([]); mockSelectAnimaVAEModels.mockReturnValue([]); + mockSelectAnimaCompatibleVAEModels.mockReturnValue([]); const state = buildMockState({ model: mockFluxMainModel }); const action = modelSelected(zParameterModel.parse(mockAnimaMainModel)); @@ -508,6 +523,8 @@ describe('modelSelected listener - Krea-2 defaulting', () => { // GGUF) transformer, which is what triggers the auto-select branch. mockSelectQwenImageVAEModels.mockReturnValue([mockKrea2Vae]); mockSelectAnimaVAEModels.mockReturnValue([mockAnimaVAE]); + // Krea-2 must read the base-driven pool, never the widened Anima-slot one. + mockSelectAnimaCompatibleVAEModels.mockReturnValue([mockAnimaVAE, mockFluxVAE]); mockSelectQwen3VLEncoderModels.mockReturnValue([mockKrea2Qwen3VlEncoder]); mockSelectModelConfigsQuery.mockReturnValue({ data: {} }); mockSelectModelById.mockReturnValue({ format: 'checkpoint' }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts index fb7f9ee8434..6d31e227e2f 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts @@ -62,6 +62,7 @@ import { toast } from 'features/toast/toast'; import { t } from 'i18next'; import { modelConfigsAdapterSelectors, modelsApi, selectModelConfigsQuery } from 'services/api/endpoints/models'; import { + selectAnimaCompatibleVAEModels, selectAnimaQwen3EncoderModels, selectAnimaVAEModels, selectFluxVAEModels, @@ -224,7 +225,7 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) = if (!hasValidConfig) { const availableQwen3Encoders = selectAnimaQwen3EncoderModels(state); - const availableAnimaVAEs = selectAnimaVAEModels(state); + const availableAnimaVAEs = selectAnimaCompatibleVAEModels(state); if (availableQwen3Encoders.length > 0 && availableAnimaVAEs.length > 0) { const qwen3Encoder = availableQwen3Encoders[0]; diff --git a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx index e21b3a1d0f2..fa7f8eae961 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx @@ -24,7 +24,7 @@ vi.mock('features/controlLayers/store/paramsSlice', async (importOriginal) => { }); const fakeModel = ( - type: 'vae' | 'qwen3_encoder' | 'mistral_encoder' | 'qwen3_vl_encoder' | 't5_encoder', + type: 'vae' | 'qwen3_encoder' | 'mistral_encoder' | 'qwen3_vl_encoder' | 't5_encoder' | 'main', base: string ) => ({ key: `${type}-key`, @@ -121,7 +121,10 @@ describe('ImageMetadataHandlers — Klein recall gating', () => { nextResolved = fakeModel('qwen3_encoder', 'flux2'); const store = makeStore(); - const parsed = await ImageMetadataHandlers.KleinQwen3EncoderModel.parse({ qwen3_encoder: nextResolved }, store); + const parsed = await ImageMetadataHandlers.KleinQwen3EncoderModel.parse( + { qwen3_encoder: nextResolved, model: fakeMainModel('klein_9b') }, + store + ); expect(parsed.key).toBe('qwen3_encoder-key'); expect(parsed.type).toBe('qwen3_encoder'); @@ -133,7 +136,25 @@ describe('ImageMetadataHandlers — Klein recall gating', () => { const store = makeStore(); await expect( - ImageMetadataHandlers.KleinQwen3EncoderModel.parse({ qwen3_encoder: nextResolved }, store) + ImageMetadataHandlers.KleinQwen3EncoderModel.parse( + { qwen3_encoder: nextResolved, model: fakeMainModel('klein_9b') }, + store + ) + ).rejects.toThrow(); + }); + + // Z-Image and Anima write the same field into their own slots. Without the provenance check their + // encoder would be recalled into `params.kleinQwen3EncoderModel` (review 4966712044). + it.each(['z-image', 'anima'])('rejects %s image metadata while FLUX.2 is selected', async (base) => { + currentBase = 'flux2'; + nextResolved = fakeModel('qwen3_encoder', base); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.KleinQwen3EncoderModel.parse( + { qwen3_encoder: nextResolved, model: { key: 'main-key', hash: 'h', name: base, base, type: 'main' } }, + store + ) ).rejects.toThrow(); }); }); @@ -566,9 +587,25 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () ).rejects.toThrow(); }); - it('rejects a non-Anima VAE from Anima metadata', async () => { + // The Anima model loader's VAE input has no `ui_model_base`, and anima_l2i / anima_i2l branch + // explicitly on FluxAutoEncoder - so a workflow-built Anima image may legitimately carry a FLUX VAE, + // and the Anima VAE picker offers those too (isAnimaCompatibleVAEModelConfig). Review 4966712044. + it('parses a FLUX VAE from an Anima image', async () => { currentBase = 'anima'; - nextResolved = fakeModel('vae', 'qwen-image'); + nextResolved = fakeModel('vae', 'flux'); + const store = makeStore(); + + const parsed = await ImageMetadataHandlers.AnimaVAEModel.parse( + { model: fakeMain('anima'), vae: nextResolved }, + store + ); + + expect(parsed.base).toBe('flux'); + }); + + it.each(['qwen-image', 'flux2', 'sdxl'])('rejects a %s VAE from Anima metadata', async (vaeBase) => { + currentBase = 'anima'; + nextResolved = fakeModel('vae', vaeBase); const store = makeStore(); await expect( @@ -622,12 +659,15 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () }); describe('ZImageQwen3EncoderModel', () => { - it('parses while Z-Image is selected', async () => { + it('parses a Z-Image image while Z-Image is selected', async () => { currentBase = 'z-image'; nextResolved = fakeModel('qwen3_encoder', 'any'); const store = makeStore(); - const parsed = await ImageMetadataHandlers.ZImageQwen3EncoderModel.parse({ qwen3_encoder: nextResolved }, store); + const parsed = await ImageMetadataHandlers.ZImageQwen3EncoderModel.parse( + { model: fakeMain('z-image'), qwen3_encoder: nextResolved }, + store + ); expect(parsed.key).toBe('qwen3_encoder-key'); }); @@ -640,18 +680,102 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () const store = makeStore(); await expect( - ImageMetadataHandlers.ZImageQwen3EncoderModel.parse({ qwen3_encoder: nextResolved }, store) + ImageMetadataHandlers.ZImageQwen3EncoderModel.parse( + { model: fakeMain(base), qwen3_encoder: nextResolved }, + store + ) + ).rejects.toThrow(); + }); + + // The selected base alone is not enough: viewing an Anima or Klein image while Z-Image is selected + // must not populate the Z-Image encoder slot nor clear the source slot (review 4966712044). + it.each(['anima', 'flux2'])('rejects %s image metadata while Z-Image is selected', async (base) => { + currentBase = 'z-image'; + nextResolved = fakeModel('qwen3_encoder', 'any'); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.ZImageQwen3EncoderModel.parse( + { model: fakeMain(base), qwen3_encoder: nextResolved }, + store + ) + ).rejects.toThrow(); + }); + }); + + describe('ZImageVAEModel', () => { + it('parses a Z-Image image while Z-Image is selected', async () => { + currentBase = 'z-image'; + nextResolved = fakeModel('vae', 'z-image'); + const store = makeStore(); + + const parsed = await ImageMetadataHandlers.ZImageVAEModel.parse( + { model: fakeMain('z-image'), vae: nextResolved }, + store + ); + + expect(parsed.key).toBe('vae-key'); + }); + + it.each(['flux', 'anima'])('rejects %s image metadata while Z-Image is selected', async (base) => { + currentBase = 'z-image'; + nextResolved = fakeModel('vae', base); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.ZImageVAEModel.parse({ model: fakeMain(base), vae: nextResolved }, store) + ).rejects.toThrow(); + }); + + it('rejects when the current base is not Z-Image', async () => { + currentBase = 'flux'; + nextResolved = fakeModel('vae', 'z-image'); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.ZImageVAEModel.parse({ model: fakeMain('z-image'), vae: nextResolved }, store) + ).rejects.toThrow(); + }); + }); + + describe('ZImageQwen3SourceModel', () => { + it('parses a Z-Image image while Z-Image is selected', async () => { + currentBase = 'z-image'; + nextResolved = fakeModel('main', 'z-image'); + const store = makeStore(); + + const parsed = await ImageMetadataHandlers.ZImageQwen3SourceModel.parse( + { model: fakeMain('z-image'), qwen3_source: nextResolved }, + store + ); + + expect(parsed.key).toBe('main-key'); + }); + + it('rejects foreign image metadata while Z-Image is selected', async () => { + currentBase = 'z-image'; + nextResolved = fakeModel('main', 'z-image'); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.ZImageQwen3SourceModel.parse( + { model: fakeMain('anima'), qwen3_source: nextResolved }, + store + ) ).rejects.toThrow(); }); }); describe('Flux1VAEModel', () => { - it('parses a FLUX VAE while FLUX.1 is selected', async () => { + it('parses a FLUX VAE from a FLUX.1 image while FLUX.1 is selected', async () => { currentBase = 'flux'; nextResolved = fakeModel('vae', 'flux'); const store = makeStore(); - const parsed = await ImageMetadataHandlers.Flux1VAEModel.parse({ vae: nextResolved }, store); + const parsed = await ImageMetadataHandlers.Flux1VAEModel.parse( + { model: fakeMain('flux'), vae: nextResolved }, + store + ); expect(parsed.key).toBe('vae-key'); }); @@ -661,7 +785,9 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () nextResolved = fakeModel('vae', 'flux'); const store = makeStore(); - await expect(ImageMetadataHandlers.Flux1VAEModel.parse({ vae: nextResolved }, store)).rejects.toThrow(); + await expect( + ImageMetadataHandlers.Flux1VAEModel.parse({ model: fakeMain('flux'), vae: nextResolved }, store) + ).rejects.toThrow(); }); it('rejects a non-FLUX VAE', async () => { @@ -669,7 +795,46 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () nextResolved = fakeModel('vae', 'sdxl'); const store = makeStore(); - await expect(ImageMetadataHandlers.Flux1VAEModel.parse({ vae: nextResolved }, store)).rejects.toThrow(); + await expect( + ImageMetadataHandlers.Flux1VAEModel.parse({ model: fakeMain('flux'), vae: nextResolved }, store) + ).rejects.toThrow(); + }); + + // A non-FLUX.1 image may legitimately carry a FLUX VAE (Z-Image accepts one). Without the provenance + // check that VAE would be recalled into `params.fluxVAE` (review 4966712044). + it('rejects a FLUX VAE that came from a Z-Image image', async () => { + currentBase = 'flux'; + nextResolved = fakeModel('vae', 'flux'); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.Flux1VAEModel.parse({ model: fakeMain('z-image'), vae: nextResolved }, store) + ).rejects.toThrow(); + }); + }); + + describe('Flux2VAEModel', () => { + it('parses a FLUX.2 image while FLUX.2 is selected', async () => { + currentBase = 'flux2'; + nextResolved = fakeModel('vae', 'flux2'); + const store = makeStore(); + + const parsed = await ImageMetadataHandlers.Flux2VAEModel.parse( + { model: fakeMain('flux2'), vae: nextResolved }, + store + ); + + expect(parsed.key).toBe('vae-key'); + }); + + it('rejects a Krea-2 image while FLUX.2 is selected', async () => { + currentBase = 'flux2'; + nextResolved = fakeModel('vae', 'qwen-image'); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.Flux2VAEModel.parse({ model: fakeMain('krea-2'), vae: nextResolved }, store) + ).rejects.toThrow(); }); }); }); diff --git a/invokeai/frontend/web/src/features/metadata/parsing.tsx b/invokeai/frontend/web/src/features/metadata/parsing.tsx index e2ebb0bf276..289a7e1ddbd 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.tsx @@ -1540,6 +1540,9 @@ const Flux1VAEModel: SingleMetadataHandler = { [SingleMetadataKey]: true, type: 'Flux1VAEModel', parse: async (metadata, store) => { + // Check provenance: `vae` is a shared field. Z-Image (and any base whose VAE pool includes FLUX + // VAEs) can record a FLUX VAE, which would otherwise land in `params.fluxVAE` (review 4966712044). + assertMetadataModelBase(metadata, 'flux', 'Flux1VAEModel'); const raw = getProperty(metadata, 'vae'); const parsed = await parseModelIdentifier(raw, store, 'vae'); assert(parsed.type === 'vae'); @@ -1564,6 +1567,9 @@ const ZImageQwen3EncoderModel: SingleMetadataHandler = { [SingleMetadataKey]: true, type: 'ZImageQwen3EncoderModel', parse: async (metadata, store) => { + // Check provenance: `qwen3_encoder` is also written by Anima and FLUX.2 Klein, and this handler + // clears `zImageQwen3SourceModel` on recall (review 4966712044). + assertMetadataModelBase(metadata, 'z-image', 'ZImageQwen3EncoderModel'); const raw = getProperty(metadata, 'qwen3_encoder'); const parsed = await parseModelIdentifier(raw, store, 'qwen3_encoder'); assert(parsed.type === 'qwen3_encoder'); @@ -1613,6 +1619,9 @@ const ZImageVAEModel: SingleMetadataHandler = { [SingleMetadataKey]: true, type: 'ZImageVAEModel', parse: async (metadata, store) => { + // Check provenance: `vae` is a shared field, and this handler additionally clears + // `zImageQwen3SourceModel` - foreign metadata must never reach it (review 4966712044). + assertMetadataModelBase(metadata, 'z-image', 'ZImageVAEModel'); const raw = getProperty(metadata, 'vae'); const parsed = await parseModelIdentifier(raw, store, 'vae'); assert(parsed.type === 'vae'); @@ -1639,6 +1648,9 @@ const ZImageQwen3SourceModel: SingleMetadataHandler = { [SingleMetadataKey]: true, type: 'ZImageQwen3SourceModel', parse: async (metadata, store) => { + // `qwen3_source` is Z-Image-only today, but this handler clears both other Z-Image slots on recall, + // so it is gated on provenance like its siblings rather than on the field being unique. + assertMetadataModelBase(metadata, 'z-image', 'ZImageQwen3SourceModel'); const raw = getProperty(metadata, 'qwen3_source'); const parsed = await parseModelIdentifier(raw, store, 'main'); assert(parsed.type === 'main'); @@ -1852,8 +1864,10 @@ const AnimaVAEModel: SingleMetadataHandler = { const raw = getProperty(metadata, 'vae'); const parsed = await parseModelIdentifier(raw, store, 'vae'); assert(parsed.type === 'vae'); - // isAnimaVAEModelConfig (services/api/types.ts) is base-driven, so this assert is meaningful here. - assert(parsed.base === 'anima', 'AnimaVAEModel requires an Anima VAE'); + // Mirrors isAnimaCompatibleVAEModelConfig, i.e. the Anima VAE picker's own domain: an Anima-base + // (Wan/QwenImage) VAE, or a FLUX VAE, which anima_l2i / anima_i2l accept as a fallback. Recalling a + // FLUX VAE from a workflow-built Anima image must not be rejected (review 4966712044). + assert(parsed.base === 'anima' || parsed.base === 'flux', 'AnimaVAEModel requires an Anima or FLUX VAE'); const base = selectBase(store.getState()); assert(base === 'anima', 'AnimaVAEModel handler only works with Anima models'); return Promise.resolve(parsed); @@ -1907,6 +1921,9 @@ const Flux2VAEModel: SingleMetadataHandler = { [SingleMetadataKey]: true, type: 'Flux2VAEModel', parse: async (metadata, store) => { + // Check provenance: `vae` is a shared field - e.g. a Krea-2 image (Qwen-Image VAE) recalled while + // FLUX.2 is selected would otherwise land in `params.flux2VaeModel`. + assertMetadataModelBase(metadata, 'flux2', 'Flux2VAEModel'); const raw = getProperty(metadata, 'vae'); const parsed = await parseModelIdentifier(raw, store, 'vae'); assert(parsed.type === 'vae'); @@ -1930,11 +1947,13 @@ const KleinQwen3EncoderModel: SingleMetadataHandler = { [SingleMetadataKey]: true, type: 'KleinQwen3EncoderModel', parse: async (metadata, store) => { + // `qwen3_encoder` is no longer Klein-only: Z-Image and Anima write it too (into their own slots), + // so provenance decides, not just the field being present. FLUX.2 [dev] never writes it, so one + // flux2 check covers both variants. + assertMetadataModelBase(metadata, 'flux2', 'KleinQwen3EncoderModel'); const raw = getProperty(metadata, 'qwen3_encoder'); const parsed = await parseModelIdentifier(raw, store, 'qwen3_encoder'); assert(parsed.type === 'qwen3_encoder'); - // qwen3_encoder is Klein-only metadata; dev never writes it. Just gate on - // base. (parseModelIdentifier already rejects when the field is absent.) const base = selectBase(store.getState()); assert(base === 'flux2', 'KleinQwen3EncoderModel handler only works with FLUX.2 Klein models'); return Promise.resolve(parsed); diff --git a/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamAnimaModelSelect.tsx b/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamAnimaModelSelect.tsx index f7db015abbd..f5f79192f0b 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamAnimaModelSelect.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamAnimaModelSelect.tsx @@ -24,20 +24,21 @@ import { zModelIdentifierField } from 'features/nodes/types/common'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { + useAnimaCompatibleVAEModels, useAnimaInpaintControlNetModels, useAnimaQwen3EncoderModels, - useAnimaVAEModels, } from 'services/api/hooks/modelsByType'; import type { ControlNetModelConfig, Qwen3EncoderModelConfig, VAEModelConfig } from 'services/api/types'; /** - * Anima VAE Model Select - uses Anima-base VAE models (QwenImage/Wan 2.1 VAE) + * Anima VAE Model Select - Anima-base VAE models (QwenImage/Wan 2.1 VAE) plus FLUX VAEs, which the + * Anima model loader accepts as a fallback (see isAnimaCompatibleVAEModelConfig). */ const ParamAnimaVaeModelSelect = memo(() => { const dispatch = useAppDispatch(); const { t } = useTranslation(); const animaVaeModel = useAppSelector(selectAnimaVaeModel); - const [modelConfigs, { isLoading }] = useAnimaVAEModels(); + const [modelConfigs, { isLoading }] = useAnimaCompatibleVAEModels(); const _onChange = useCallback( (model: VAEModelConfig | null) => { diff --git a/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts b/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts index 1a616962747..510951c52c3 100644 --- a/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts +++ b/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts @@ -11,6 +11,7 @@ import { } from 'services/api/endpoints/models'; import type { AnyModelConfig, AnyModelConfigWithExternal, MainOrExternalModelConfig } from 'services/api/types'; import { + isAnimaCompatibleVAEModelConfig, isAnimaInpaintControlNetModelConfig, isAnimaQwen3EncoderModelConfig, isAnimaVAEModelConfig, @@ -114,6 +115,8 @@ export const useVAEModels = () => buildModelsHook(isVAEModelConfigOrSubmodel)(); export const useFlux1VAEModels = () => buildModelsHook(isFlux1VAEModelConfig)(); export const useFlux2VAEModels = () => buildModelsHook(isFlux2VAEModelConfig)(); export const useAnimaVAEModels = () => buildModelsHook(isAnimaVAEModelConfig)(); +/** The Anima VAE slot itself - wider than `useAnimaVAEModels`, which feeds Krea-2's pool. */ +export const useAnimaCompatibleVAEModels = () => buildModelsHook(isAnimaCompatibleVAEModelConfig)(); export const useAnimaQwen3EncoderModels = () => buildModelsHook(isAnimaQwen3EncoderModelConfig)(); export const useAnimaInpaintControlNetModels = () => buildModelsHook(isAnimaInpaintControlNetModelConfig)(); export const useZImageDiffusersModels = () => buildModelsHook(isZImageDiffusersMainModelConfig)(); @@ -174,6 +177,7 @@ export const selectFlux2DiffusersModels = buildModelsSelector(isFlux2DiffusersMa export const selectFlux2DevDiffusersModels = buildModelsSelector(isFlux2DevDiffusersMainModelConfig); export const selectFluxVAEModels = buildModelsSelector(isFluxVAEModelConfig); export const selectAnimaVAEModels = buildModelsSelector(isAnimaVAEModelConfig); +export const selectAnimaCompatibleVAEModels = buildModelsSelector(isAnimaCompatibleVAEModelConfig); export const selectQwen3VLEncoderModels = buildModelsSelector(isQwen3VLEncoderModelConfig); export const selectWanDiffusersModels = buildModelsSelector(isWanDiffusersMainModelConfig); export const selectWanVAEModels = buildModelsSelector(isWanVAEModelConfig); diff --git a/invokeai/frontend/web/src/services/api/types.test.ts b/invokeai/frontend/web/src/services/api/types.test.ts index e7c0fbc1d73..b5267bc157a 100644 --- a/invokeai/frontend/web/src/services/api/types.test.ts +++ b/invokeai/frontend/web/src/services/api/types.test.ts @@ -2,6 +2,8 @@ import { describe, expect, it } from 'vitest'; import type { MainModelConfig } from './types'; import { + isAnimaCompatibleVAEModelConfig, + isAnimaVAEModelConfig, isFlux2DiffusersMainModelConfig, isWanLowNoisePartnerOption, isZImageDiffusersMainModelConfig, @@ -33,6 +35,28 @@ describe('SDNQ pipeline model predicates', () => { }); }); +// The Anima loader accepts a FLUX VAE beside the Wan/QwenImage one, but Krea-2 draws its own VAE pool +// from the base-driven `isAnimaVAEModelConfig` and must not be offered FLUX VAEs. The two predicates +// must therefore stay distinct - collapsing them would widen Krea-2's picker as a side effect. +describe('Anima VAE predicates', () => { + const vae = (base: string) => ({ key: `${base}-vae`, type: 'vae', base, name: `${base} vae` }) as never; + + it('accepts an Anima VAE in both predicates', () => { + expect(isAnimaVAEModelConfig(vae('anima'))).toBe(true); + expect(isAnimaCompatibleVAEModelConfig(vae('anima'))).toBe(true); + }); + + it('accepts a FLUX VAE only as Anima-compatible, not as an Anima-base VAE', () => { + expect(isAnimaVAEModelConfig(vae('flux'))).toBe(false); + expect(isAnimaCompatibleVAEModelConfig(vae('flux'))).toBe(true); + }); + + it.each(['flux2', 'qwen-image', 'sdxl'])('rejects a %s VAE in both predicates', (base) => { + expect(isAnimaVAEModelConfig(vae(base))).toBe(false); + expect(isAnimaCompatibleVAEModelConfig(vae(base))).toBe(false); + }); +}); + const wanMain = (over: Record) => ({ key: 'k', diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index e7fed5ebf6a..4fe6278e40b 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -318,6 +318,22 @@ export const isAnimaVAEModelConfig = (config: AnyModelConfig, excludeSubmodels?: ); }; +/** + * VAEs the Anima model loader accepts. `AnimaModelLoaderInvocation.vae_model` carries no + * `ui_model_base`, and both `anima_l2i` and `anima_i2l` branch explicitly on `FluxAutoEncoder` + * (4D decode, no Wan denormalisation) beside `AutoencoderKLWan` - a FLUX VAE is a supported + * fallback, not merely tolerated. + * + * Kept separate from `isAnimaVAEModelConfig`, which stays base-driven because Krea-2 draws its + * own VAE pool from it and must not be offered FLUX VAEs. + */ +export const isAnimaCompatibleVAEModelConfig = ( + config: AnyModelConfig, + excludeSubmodels?: boolean +): config is VAEModelConfig => { + return isAnimaVAEModelConfig(config, excludeSubmodels) || isFlux1VAEModelConfig(config, excludeSubmodels); +}; + export const isQwenImageVAEModelConfig = ( config: AnyModelConfig, excludeSubmodels?: boolean From 8feb7308f888dcc10517ae3923677f41e0a64a74 Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Thu, 20 Aug 2026 05:17:03 +0200 Subject: [PATCH 04/12] fix(ui): accept 16-channel Wan VAEs for Anima recall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Anima model loader takes any VAE the backend can decode with — an Anima-base (Wan/QwenImage) VAE, a FLUX VAE, or a plain Wan VAE in the 16-channel A14B geometry. The frontend gated on base alone, so a workflow-built Anima image recording a Wan VAE could not recall it and recall silently left the stale VAE in place. Gate on the backend geometry instead: isAnimaCompatibleVAEModelConfig now also accepts Wan VAEs with latent_channels === 16, and rejects the 48-channel Wan2.2-VAE (TI2V-5B), which is the same AutoencoderKLWan class but a different latent space. Submodels stay out — a main model's bundled VAE carries no latent_channels, so its geometry is unverifiable. Since latent_channels is lost in ModelIdentifierField, AnimaVAEModel now gates on the full model config: parseModelConfig is split out of parseModelIdentifier, which becomes a thin wrapper over it. The resolution order (key, then hash, then name/base) and its fallthrough behaviour are unchanged. isAnimaVAEModelConfig stays base-driven — Krea-2 draws its own VAE pool from it and must not be offered FLUX or Wan VAEs. --- .../src/features/metadata/parsing.test.tsx | 44 ++++++++++++++++++- .../web/src/features/metadata/parsing.tsx | 41 ++++++++++++----- .../Advanced/ParamAnimaModelSelect.tsx | 4 +- .../web/src/services/api/types.test.ts | 26 +++++++++-- .../frontend/web/src/services/api/types.ts | 43 ++++++++++++------ 5 files changed, 128 insertions(+), 30 deletions(-) diff --git a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx index fa7f8eae961..6c92229dce4 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx @@ -25,13 +25,17 @@ vi.mock('features/controlLayers/store/paramsSlice', async (importOriginal) => { const fakeModel = ( type: 'vae' | 'qwen3_encoder' | 'mistral_encoder' | 'qwen3_vl_encoder' | 't5_encoder' | 'main', - base: string + base: string, + // Extra config fields, for handlers that gate on more than base/type (e.g. a Wan VAE's + // `latent_channels`). The store mock resolves these objects as the model's full config. + over: Record = {} ) => ({ key: `${type}-key`, hash: 'hash', name: `Some ${type}`, base, type, + ...over, }); // FLUX.2 main-model config. The `variant` is what the dev-vs-Klein VAE @@ -612,6 +616,44 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () ImageMetadataHandlers.AnimaVAEModel.parse({ model: fakeMain('anima'), vae: nextResolved }, store) ).rejects.toThrow(); }); + + // Anima's transformer works in a 16-channel latent space, so the A14B Wan VAE is as valid an input as + // an Anima-base one - `anima_l2i` / `anima_i2l` treat both as AutoencoderKLWan. Recall must gate on + // that geometry (`latent_channels`), not on the base alone (review 4972570279). + it('parses a 16-channel Wan VAE from an Anima image', async () => { + currentBase = 'anima'; + nextResolved = fakeModel('vae', 'wan', { latent_channels: 16 }); + const store = makeStore(); + + const parsed = await ImageMetadataHandlers.AnimaVAEModel.parse( + { model: fakeMain('anima'), vae: nextResolved }, + store + ); + + expect(parsed.base).toBe('wan'); + }); + + // The 48-channel Wan2.2-VAE (TI2V-5B) is the same class but a different latent space - recalling it + // would swap in a VAE that cannot decode Anima latents. + it('rejects a 48-channel Wan VAE from Anima metadata', async () => { + currentBase = 'anima'; + nextResolved = fakeModel('vae', 'wan', { latent_channels: 48 }); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.AnimaVAEModel.parse({ model: fakeMain('anima'), vae: nextResolved }, store) + ).rejects.toThrow(); + }); + + it('rejects a Wan VAE whose latent channel count is unknown', async () => { + currentBase = 'anima'; + nextResolved = fakeModel('vae', 'wan'); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.AnimaVAEModel.parse({ model: fakeMain('anima'), vae: nextResolved }, store) + ).rejects.toThrow(); + }); }); describe('AnimaQwen3EncoderModel', () => { diff --git a/invokeai/frontend/web/src/features/metadata/parsing.tsx b/invokeai/frontend/web/src/features/metadata/parsing.tsx index 289a7e1ddbd..e6e634becd1 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.tsx @@ -154,6 +154,7 @@ import { useTranslation } from 'react-i18next'; import { imagesApi } from 'services/api/endpoints/images'; import { modelsApi } from 'services/api/endpoints/models'; import type { AnyModelConfig } from 'services/api/types'; +import { isAnimaCompatibleVAEModelConfig } from 'services/api/types'; import { assert } from 'tsafe'; import z from 'zod'; @@ -1862,12 +1863,18 @@ const AnimaVAEModel: SingleMetadataHandler = { // the Anima slot. assertMetadataModelBase(metadata, 'anima', 'AnimaVAEModel'); const raw = getProperty(metadata, 'vae'); - const parsed = await parseModelIdentifier(raw, store, 'vae'); - assert(parsed.type === 'vae'); - // Mirrors isAnimaCompatibleVAEModelConfig, i.e. the Anima VAE picker's own domain: an Anima-base - // (Wan/QwenImage) VAE, or a FLUX VAE, which anima_l2i / anima_i2l accept as a fallback. Recalling a - // FLUX VAE from a workflow-built Anima image must not be rejected (review 4966712044). - assert(parsed.base === 'anima' || parsed.base === 'flux', 'AnimaVAEModel requires an Anima or FLUX VAE'); + // Gate on the full config, not just its base: the Anima-compatible set is defined by the backend's + // latent geometry, which for Wan VAEs lives in `latent_channels` and is lost in the identifier. + const config = await parseModelConfig(raw, store, 'vae'); + // Defers to the Anima VAE picker's own domain (isAnimaCompatibleVAEModelConfig): an Anima-base + // (Wan/QwenImage) VAE, a FLUX VAE, or a 16-channel Wan VAE - all of which anima_l2i / anima_i2l + // accept. Recalling those from a workflow-built Anima image must not be rejected (reviews + // 4966712044, 4972570279). + assert( + isAnimaCompatibleVAEModelConfig(config, true), + 'AnimaVAEModel requires a VAE the Anima model loader accepts' + ); + const parsed = zModelIdentifierField.parse(config); const base = selectBase(store.getState()); assert(base === 'anima', 'AnimaVAEModel handler only works with Anima models'); return Promise.resolve(parsed); @@ -2751,13 +2758,21 @@ const getModelIdentiferFromKey = async (key: string, store: AppStore): Promise => { +/** + * Resolve a metadata model reference to the installed model's *full* config. Handlers that must gate + * on more than base and type - e.g. a VAE's latent geometry - need the whole config; the identifier + * fields that actually get recalled into state are what `parseModelIdentifier` narrows this down to. + */ +const parseModelConfig = async (raw: unknown, store: AppStore, type: ModelType): Promise => { try { // First try the current format identifier: key, name, base, type, hash const { key } = zModelIdentifierField.parse(raw); const req = store.dispatch(modelsApi.endpoints.getModelConfig.initiate(key, options)); const modelConfig = await req.unwrap(); - return zModelIdentifierField.parse(modelConfig); + // Discarded on purpose - the config is what we return; this only asserts it is identifiable, so a + // config that isn't still falls through to the lookups below. + zModelIdentifierField.parse(modelConfig); + return modelConfig; } catch { // We'll try hash-based lookup next } @@ -2768,7 +2783,8 @@ const parseModelIdentifier = async (raw: unknown, store: AppStore, type: ModelTy if (hash) { const req = store.dispatch(modelsApi.endpoints.getModelConfigByHash.initiate(hash, options)); const modelConfig = await req.unwrap(); - return zModelIdentifierField.parse(modelConfig); + zModelIdentifierField.parse(modelConfig); + return modelConfig; } } catch { // We'll try the old format identifier next @@ -2780,7 +2796,12 @@ const parseModelIdentifier = async (raw: unknown, store: AppStore, type: ModelTy const arg = { name: model_name, base: base_model, type }; const req = store.dispatch(modelsApi.endpoints.getModelConfigByAttrs.initiate(arg, options)); const modelConfig = await req.unwrap(); - return zModelIdentifierField.parse(modelConfig); + zModelIdentifierField.parse(modelConfig); + return modelConfig; +}; + +const parseModelIdentifier = async (raw: unknown, store: AppStore, type: ModelType): Promise => { + return zModelIdentifierField.parse(await parseModelConfig(raw, store, type)); }; const isCompatibleWithMainModel = (candidate: ModelIdentifierField, store: AppStore) => { diff --git a/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamAnimaModelSelect.tsx b/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamAnimaModelSelect.tsx index f5f79192f0b..9308966e07d 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamAnimaModelSelect.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamAnimaModelSelect.tsx @@ -31,8 +31,8 @@ import { import type { ControlNetModelConfig, Qwen3EncoderModelConfig, VAEModelConfig } from 'services/api/types'; /** - * Anima VAE Model Select - Anima-base VAE models (QwenImage/Wan 2.1 VAE) plus FLUX VAEs, which the - * Anima model loader accepts as a fallback (see isAnimaCompatibleVAEModelConfig). + * Anima VAE Model Select - Anima-base VAE models (QwenImage/Wan 2.1 VAE) plus the other VAEs the Anima + * model loader accepts: FLUX VAEs and 16-channel Wan VAEs (see isAnimaCompatibleVAEModelConfig). */ const ParamAnimaVaeModelSelect = memo(() => { const dispatch = useAppDispatch(); diff --git a/invokeai/frontend/web/src/services/api/types.test.ts b/invokeai/frontend/web/src/services/api/types.test.ts index b5267bc157a..1092a27680e 100644 --- a/invokeai/frontend/web/src/services/api/types.test.ts +++ b/invokeai/frontend/web/src/services/api/types.test.ts @@ -35,11 +35,13 @@ describe('SDNQ pipeline model predicates', () => { }); }); -// The Anima loader accepts a FLUX VAE beside the Wan/QwenImage one, but Krea-2 draws its own VAE pool -// from the base-driven `isAnimaVAEModelConfig` and must not be offered FLUX VAEs. The two predicates -// must therefore stay distinct - collapsing them would widen Krea-2's picker as a side effect. +// The Anima loader accepts a FLUX VAE and a 16-channel Wan VAE beside the Wan/QwenImage one, but Krea-2 +// draws its own VAE pool from the base-driven `isAnimaVAEModelConfig` and must not be offered either. +// The two predicates must therefore stay distinct - collapsing them would widen Krea-2's picker as a +// side effect. describe('Anima VAE predicates', () => { - const vae = (base: string) => ({ key: `${base}-vae`, type: 'vae', base, name: `${base} vae` }) as never; + const vae = (base: string, over: Record = {}) => + ({ key: `${base}-vae`, type: 'vae', base, name: `${base} vae`, ...over }) as never; it('accepts an Anima VAE in both predicates', () => { expect(isAnimaVAEModelConfig(vae('anima'))).toBe(true); @@ -51,6 +53,22 @@ describe('Anima VAE predicates', () => { expect(isAnimaCompatibleVAEModelConfig(vae('flux'))).toBe(true); }); + // Anima's transformer works in a 16-channel latent space, which the A14B Wan VAE provides. The + // 48-channel Wan2.2-VAE (TI2V-5B) is the same AutoencoderKLWan class but a different latent space. + it('accepts a 16-channel Wan VAE as Anima-compatible', () => { + expect(isAnimaCompatibleVAEModelConfig(vae('wan', { latent_channels: 16 }))).toBe(true); + expect(isAnimaVAEModelConfig(vae('wan', { latent_channels: 16 }))).toBe(false); + }); + + it('rejects a 48-channel Wan VAE', () => { + expect(isAnimaCompatibleVAEModelConfig(vae('wan', { latent_channels: 48 }))).toBe(false); + }); + + // A main model's bundled `vae` submodel carries no `latent_channels`, so its geometry is unverifiable. + it('rejects a Wan VAE whose latent channel count is unknown', () => { + expect(isAnimaCompatibleVAEModelConfig(vae('wan'))).toBe(false); + }); + it.each(['flux2', 'qwen-image', 'sdxl'])('rejects a %s VAE in both predicates', (base) => { expect(isAnimaVAEModelConfig(vae(base))).toBe(false); expect(isAnimaCompatibleVAEModelConfig(vae(base))).toBe(false); diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index 4fe6278e40b..64fc045cc58 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -311,6 +311,13 @@ export const isFlux2VAEModelConfig = (config: AnyModelConfig, excludeSubmodels?: ); }; +export const isWanVAEModelConfig = (config: AnyModelConfig, excludeSubmodels?: boolean): config is VAEModelConfig => { + return ( + (config.type === 'vae' || (!excludeSubmodels && config.type === 'main' && checkSubmodels(['vae'], config))) && + config.base === 'wan' + ); +}; + export const isAnimaVAEModelConfig = (config: AnyModelConfig, excludeSubmodels?: boolean): config is VAEModelConfig => { return ( (config.type === 'vae' || (!excludeSubmodels && config.type === 'main' && checkSubmodels(['vae'], config))) && @@ -319,19 +326,36 @@ export const isAnimaVAEModelConfig = (config: AnyModelConfig, excludeSubmodels?: }; /** - * VAEs the Anima model loader accepts. `AnimaModelLoaderInvocation.vae_model` carries no - * `ui_model_base`, and both `anima_l2i` and `anima_i2l` branch explicitly on `FluxAutoEncoder` - * (4D decode, no Wan denormalisation) beside `AutoencoderKLWan` - a FLUX VAE is a supported - * fallback, not merely tolerated. + * Wan VAEs whose latent geometry matches Anima's, i.e. `AutoencoderKLWan` with 16 latent channels + * (Wan 2.1 / A14B). The 48-channel TI2V-5B VAE (`Wan2.2-VAE`) is the same class but a different + * latent space, so it must stay out. + * + * Submodels are excluded deliberately: a main model's bundled `vae` carries no `latent_channels` of + * its own, so its geometry cannot be verified from the config. + */ +const isAnimaCompatibleWanVAEModelConfig = (config: AnyModelConfig): config is VAEModelConfig => { + return isWanVAEModelConfig(config, true) && 'latent_channels' in config && config.latent_channels === 16; +}; + +/** + * VAEs the Anima model loader accepts, gated on the backend's latent geometry rather than on base + * alone. `AnimaModelLoaderInvocation.vae_model` carries no `ui_model_base`, and both `anima_l2i` and + * `anima_i2l` branch explicitly on `FluxAutoEncoder` (4D decode, no Wan denormalisation) beside + * `AutoencoderKLWan` - so an Anima-base VAE, a FLUX VAE and a 16-channel Wan VAE are all supported, + * not merely tolerated. * * Kept separate from `isAnimaVAEModelConfig`, which stays base-driven because Krea-2 draws its - * own VAE pool from it and must not be offered FLUX VAEs. + * own VAE pool from it and must not be offered FLUX or Wan VAEs. */ export const isAnimaCompatibleVAEModelConfig = ( config: AnyModelConfig, excludeSubmodels?: boolean ): config is VAEModelConfig => { - return isAnimaVAEModelConfig(config, excludeSubmodels) || isFlux1VAEModelConfig(config, excludeSubmodels); + return ( + isAnimaVAEModelConfig(config, excludeSubmodels) || + isFlux1VAEModelConfig(config, excludeSubmodels) || + isAnimaCompatibleWanVAEModelConfig(config) + ); }; export const isQwenImageVAEModelConfig = ( @@ -344,13 +368,6 @@ export const isQwenImageVAEModelConfig = ( ); }; -export const isWanVAEModelConfig = (config: AnyModelConfig, excludeSubmodels?: boolean): config is VAEModelConfig => { - return ( - (config.type === 'vae' || (!excludeSubmodels && config.type === 'main' && checkSubmodels(['vae'], config))) && - config.base === 'wan' - ); -}; - export const isControlNetModelConfig = (config: AnyModelConfig): config is ControlNetModelConfig => { return config.type === 'controlnet'; }; From 8799228e921ac9d739c7e9cc1d3cc6276a2a6a71 Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Thu, 20 Aug 2026 23:50:51 +0200 Subject: [PATCH 05/12] fix(ui): reparse and revalidate metadata rows when the base changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses review 4987208275 on #9489. Metadata handlers gate on the currently selected base, which they read imperatively via `selectBase(store.getState())` inside `parse` — they have to, because parsing also happens outside React, in recall-all and in hotkeys. The datum hooks depended only on `[metadata, handler, store]`, so a row carried the verdict of whichever base was selected when it mounted, and the metadata viewer stays open across model switches. Opening Anima metadata under another model and then recalling MainModel left the Anima VAE and encoder rows hidden; switching away after a row appeared left a live recall button pointed at a slot no longer in play. The three hooks now subscribe to the selected base and reparse when it changes. That reparse is asynchronous, so a click can still land while a stale row is on screen. Recalls therefore re-run the handler's own gate at click time: `recallIfStillValid` parses again and dispatches only if the gate still admits the metadata. It recalls the value the row displays, not the reparsed one — a collection row owns a single item, while `parse` returns the whole set. Fixing the hooks alone would have changed nothing: `SingleMetadataParsed` and `CollectionMetadataParsed` called `handler.recall` / `handler.recallOne` directly and resolved the store themselves, and the hooks' `recall`, `recallAll` and `recallOne` had no callers anywhere. The row components now take the hook's validating callback instead, which is the only recall path left outside `parsing.tsx`. Tested through `recallIfStillValid`, which is exported as a plain function for that purpose: it recalls under a matching base, does nothing after the base moves on, does nothing when provenance fails, and passes the row's own value through. The reparse half is not covered — the project has no testing-library or DOM environment and does not do UI tests, so it would need a new dev dependency to assert. --- .../ImageMetadataActions.tsx | 42 ++++++--- .../src/features/metadata/parsing.test.tsx | 81 ++++++++++++++++- .../web/src/features/metadata/parsing.tsx | 89 ++++++++++++++----- 3 files changed, 175 insertions(+), 37 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx index 5cc88383dc8..c6699cdf7ec 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Box, Flex, IconButton } from '@invoke-ai/ui-library'; -import { useAppStore } from 'app/store/storeHooks'; import { typedMemo } from 'common/util/typedMemo'; import type { CollectionMetadataHandler, @@ -145,29 +144,38 @@ UnrecallableMetadataParsed.displayName = 'UnrecallableMetadataParsed'; const SingleMetadataDatum = typedMemo( ({ metadata, handler }: { metadata: unknown; handler: SingleMetadataHandler }) => { - const { data } = useSingleMetadataDatum(metadata, handler); + // `recall` comes from the hook rather than being `handler.recall`: the hook re-runs the handler's gate + // before dispatching, so a row left over from a base switch cannot write into an inactive slot. + const { data, recall } = useSingleMetadataDatum(metadata, handler); if (!data.isParsed) { return null; } if (data.isSuccess) { - return ; + return ; } } ); SingleMetadataDatum.displayName = 'SingleMetadataDatum'; const SingleMetadataParsed = typedMemo( - ({ data, handler }: { data: ParsedSuccessData; handler: SingleMetadataHandler }) => { + ({ + data, + handler, + recall, + }: { + data: ParsedSuccessData; + handler: SingleMetadataHandler; + recall: (value: T) => void; + }) => { const { t } = useTranslation(); - const store = useAppStore(); const { LabelComponent, ValueComponent } = handler; const onClick = useCallback(() => { - handler.recall(data.value, store); - }, [data.value, handler, store]); + recall(data.value); + }, [data.value, recall]); return ( @@ -190,7 +198,8 @@ SingleMetadataParsed.displayName = 'SingleMetadataParsed'; const CollectionMetadataDatum = typedMemo( ({ metadata, handler }: { metadata: unknown; handler: CollectionMetadataHandler }) => { - const { data } = useCollectionMetadataDatum(metadata, handler); + // See `SingleMetadataDatum`: the hook's `recallOne` re-validates, `handler.recallOne` does not. + const { data, recallOne } = useCollectionMetadataDatum(metadata, handler); if (!data.isParsed) { return null; @@ -200,7 +209,7 @@ const CollectionMetadataDatum = typedMemo( return ( <> {data.value.map((value, i) => ( - + ))} ); @@ -210,15 +219,22 @@ const CollectionMetadataDatum = typedMemo( CollectionMetadataDatum.displayName = 'CollectionMetadataDatum'; const CollectionMetadataParsed = typedMemo( - ({ value, handler }: { value: T[number]; handler: CollectionMetadataHandler }) => { + ({ + value, + handler, + recallOne, + }: { + value: T[number]; + handler: CollectionMetadataHandler; + recallOne: (value: T[number]) => void; + }) => { const { t } = useTranslation(); - const store = useAppStore(); const { LabelComponent, ValueComponent } = handler; const onClick = useCallback(() => { - handler.recallOne(value, store); - }, [handler, store, value]); + recallOne(value); + }, [recallOne, value]); return ( diff --git a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx index 6c92229dce4..b05d10a4aaa 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx @@ -1,6 +1,6 @@ import type { AppStore } from 'app/store/store'; import type * as paramsSliceModule from 'features/controlLayers/store/paramsSlice'; -import { ImageMetadataHandlers } from 'features/metadata/parsing'; +import { ImageMetadataHandlers, recallIfStillValid } from 'features/metadata/parsing'; import type * as modelsApiModule from 'services/api/endpoints/models'; import { beforeEach, describe, expect, it, vi } from 'vitest'; @@ -880,3 +880,82 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () }); }); }); + +// A metadata row is parsed once, and the metadata viewer stays open while the user switches models. The +// row's recall therefore re-runs the handler's gate at click time: `parse` reads the *currently* selected +// base, so a row left over from the previous base must not write into a slot that is no longer in play. +// Review 4987208275. +describe('recallIfStillValid', () => { + const animaVae = () => fakeModel('vae', 'anima'); + const animaMetadata = () => ({ model: { key: 'main-key', hash: 'h', name: 'anima', base: 'anima', type: 'main' } }); + + const run = (handler: (typeof ImageMetadataHandlers)['AnimaVAEModel'], metadata: Record) => { + const recall = vi.fn(); + const store = makeStore(); + return recallIfStillValid({ metadata, handler, recall, value: nextResolved, store }).then((didRecall) => ({ + didRecall, + recall, + store, + })); + }; + + it('recalls while the handler still admits the metadata', async () => { + currentBase = 'anima'; + nextResolved = animaVae(); + + const { didRecall, recall } = await run(ImageMetadataHandlers.AnimaVAEModel, { + ...animaMetadata(), + vae: nextResolved, + }); + + expect(didRecall).toBe(true); + expect(recall).toHaveBeenCalledTimes(1); + }); + + it('does nothing once the selected base has moved on', async () => { + // The row was parsed under Anima; the user switched to SDXL before the click landed. + currentBase = 'sdxl'; + nextResolved = animaVae(); + + const { didRecall, recall } = await run(ImageMetadataHandlers.AnimaVAEModel, { + ...animaMetadata(), + vae: nextResolved, + }); + + expect(didRecall).toBe(false); + expect(recall).not.toHaveBeenCalled(); + }); + + it('does nothing when the metadata itself no longer passes the gate', async () => { + // Provenance failure rather than a base switch - same outcome, same single gate. + currentBase = 'anima'; + nextResolved = animaVae(); + + const { didRecall, recall } = await run(ImageMetadataHandlers.AnimaVAEModel, { + model: { key: 'main-key', hash: 'h', name: 'krea-2', base: 'krea-2', type: 'main' }, + vae: nextResolved, + }); + + expect(didRecall).toBe(false); + expect(recall).not.toHaveBeenCalled(); + }); + + it('passes through the value the row owns, not the reparsed one', async () => { + // A collection row owns a single item; recalling whatever `parse` returned would recall the whole set. + currentBase = 'anima'; + nextResolved = animaVae(); + const store = makeStore(); + const recall = vi.fn(); + const sentinel = { mine: true }; + + await recallIfStillValid({ + metadata: { ...animaMetadata(), vae: nextResolved }, + handler: ImageMetadataHandlers.AnimaVAEModel, + recall, + value: sentinel, + store, + }); + + expect(recall).toHaveBeenCalledWith(sentinel, store); + }); +}); diff --git a/invokeai/frontend/web/src/features/metadata/parsing.tsx b/invokeai/frontend/web/src/features/metadata/parsing.tsx index e6e634becd1..056f0bb1fb1 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Text } from '@invoke-ai/ui-library'; import type { AppStore } from 'app/store/store'; -import { useAppStore } from 'app/store/storeHooks'; +import { useAppSelector, useAppStore } from 'app/store/storeHooks'; import { WrappedError } from 'common/util/result'; import { get, isArray, isString } from 'es-toolkit/compat'; import { getPrefixedId } from 'features/controlLayers/konva/util'; @@ -2639,8 +2639,65 @@ export const MetadataUtils = { recallImageDimensions, } as const; +/** + * The selected base, subscribed to so a change re-runs the parse. + * + * Handlers read `selectBase(store.getState())` imperatively inside `parse` - they have to, because parsing + * also happens outside React (recall-all, hotkeys). A row therefore carries the verdict of whichever base + * was selected when it mounted, and the metadata viewer stays open across model switches: without this + * subscription, rows that should now appear stay hidden and rows that should now be hidden keep a live + * recall button pointed at a slot that is no longer in play. + */ +const useSelectedBaseForReparse = (): ReturnType => useAppSelector(selectBase); + +/** + * Runs a recall only if the handler's own gate still admits the metadata. + * + * The reparse driven by `useSelectedBaseForReparse` is asynchronous, so there is a window in which a row is + * still on screen under a base that no longer admits it. Re-running the gate here closes that window: a + * recall that is no longer valid does nothing rather than writing a foreign model into an inactive slot. + * + * The passed value is what gets recalled, not the reparsed one - for a collection the row owns a single + * item, and the row must do what it displays. + * + * @returns whether the recall ran. + */ +export const recallIfStillValid = async (arg: { + metadata: unknown; + handler: { parse: (metadata: unknown, store: AppStore) => Promise }; + recall: (value: TValue, store: AppStore) => void; + value: TValue; + store: AppStore; +}): Promise => { + const { metadata, handler, recall, value, store } = arg; + try { + await parseMetadataHandler(metadata, handler, store); + } catch { + // No longer applicable under the current base - the pending reparse is about to drop the row. + return false; + } + recall(value, store); + return true; +}; + +const useRevalidatedRecall = ( + metadata: unknown, + handler: { parse: (metadata: unknown, store: AppStore) => Promise }, + recall: (value: TValue, store: AppStore) => void +) => { + const store = useAppStore(); + + return useCallback( + (value: TValue) => { + void recallIfStillValid({ metadata, handler, recall, value, store }); + }, + [metadata, handler, store, recall] + ); +}; + export function useSingleMetadataDatum(metadata: unknown, handler: SingleMetadataHandler) { const store = useAppStore(); + const base = useSelectedBaseForReparse(); const [data, setData] = useState>(() => ({ isParsed: false, isSuccess: false, @@ -2668,20 +2725,16 @@ export function useSingleMetadataDatum(metadata: unknown, handler: SingleMeta return () => { isActive = false; }; - }, [metadata, handler, store]); + }, [metadata, handler, store, base]); - const recall = useCallback( - (value: T) => { - handler.recall(value, store); - }, - [handler, store] - ); + const recall = useRevalidatedRecall(metadata, handler, handler.recall); return { data, recall }; } export function useCollectionMetadataDatum(metadata: unknown, handler: CollectionMetadataHandler) { const store = useAppStore(); + const base = useSelectedBaseForReparse(); const [data, setData] = useState>(buildUnparsedData); useEffect(() => { @@ -2703,27 +2756,17 @@ export function useCollectionMetadataDatum(metadata: unknown, h return () => { isActive = false; }; - }, [metadata, handler, store]); - - const recallAll = useCallback( - (values: T) => { - handler.recall(values, store); - }, - [handler, store] - ); + }, [metadata, handler, store, base]); - const recallOne = useCallback( - (value: T[number]) => { - handler.recallOne(value, store); - }, - [handler, store] - ); + const recallAll = useRevalidatedRecall(metadata, handler, handler.recall); + const recallOne = useRevalidatedRecall(metadata, handler, handler.recallOne); return { data, recallAll, recallOne }; } export function useUnrecallableMetadataDatum(metadata: unknown, handler: UnrecallableMetadataHandler) { const store = useAppStore(); + const base = useSelectedBaseForReparse(); const [data, setData] = useState>(buildUnparsedData); useEffect(() => { @@ -2745,7 +2788,7 @@ export function useUnrecallableMetadataDatum(metadata: unknown, handler: Unre return () => { isActive = false; }; - }, [metadata, handler, store]); + }, [metadata, handler, store, base]); return { data }; } From 5af9c7d23bddb38b5e16a33be998163fbc60456a Mon Sep 17 00:00:00 2001 From: JPPhoto Date: Thu, 20 Aug 2026 19:34:18 -0500 Subject: [PATCH 06/12] fix(ui): recall Anima VAE submodels --- .../src/features/metadata/parsing.test.tsx | 42 +++++++++++++++++++ .../web/src/features/metadata/parsing.tsx | 18 +++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx index b05d10a4aaa..ea1aa4f3319 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx @@ -607,6 +607,48 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () expect(parsed.base).toBe('flux'); }); + it('parses a FLUX main-model VAE submodel from Anima metadata', async () => { + currentBase = 'anima'; + const fluxMainWithVae = fakeModel('main', 'flux', { submodels: { vae: { model_type: 'vae' } } }); + modelRegistry['main-key'] = fluxMainWithVae; + const rawVae = { ...fluxMainWithVae, submodel_type: 'vae' }; + const store = makeStore(); + + const parsed = await ImageMetadataHandlers.AnimaVAEModel.parse({ model: fakeMain('anima'), vae: rawVae }, store); + + expect(parsed.type).toBe('main'); + expect(parsed.submodel_type).toBe('vae'); + expect(parsed.base).toBe('flux'); + }); + + it('normalizes a FLUX main-model VAE submodel without subtype metadata', async () => { + currentBase = 'anima'; + const fluxMainWithVae = fakeModel('main', 'flux', { submodels: { vae: { model_type: 'vae' } } }); + modelRegistry['main-key'] = fluxMainWithVae; + const store = makeStore(); + + const parsed = await ImageMetadataHandlers.AnimaVAEModel.parse( + { model: fakeMain('anima'), vae: fluxMainWithVae }, + store + ); + + expect(parsed.type).toBe('main'); + expect(parsed.submodel_type).toBe('vae'); + expect(parsed.base).toBe('flux'); + }); + + it('rejects a FLUX main-model submodel without VAE provenance', async () => { + currentBase = 'anima'; + const fluxMainWithVae = fakeModel('main', 'flux', { submodels: { vae: { model_type: 'vae' } } }); + modelRegistry['main-key'] = fluxMainWithVae; + const rawVae = { ...fluxMainWithVae, submodel_type: 'text_encoder' }; + const store = makeStore(); + + await expect( + ImageMetadataHandlers.AnimaVAEModel.parse({ model: fakeMain('anima'), vae: rawVae }, store) + ).rejects.toThrow(); + }); + it.each(['qwen-image', 'flux2', 'sdxl'])('rejects a %s VAE from Anima metadata', async (vaeBase) => { currentBase = 'anima'; nextResolved = fakeModel('vae', vaeBase); diff --git a/invokeai/frontend/web/src/features/metadata/parsing.tsx b/invokeai/frontend/web/src/features/metadata/parsing.tsx index 056f0bb1fb1..5b18556586b 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.tsx @@ -1863,18 +1863,32 @@ const AnimaVAEModel: SingleMetadataHandler = { // the Anima slot. assertMetadataModelBase(metadata, 'anima', 'AnimaVAEModel'); const raw = getProperty(metadata, 'vae'); + const rawIdentifier = zModelIdentifierField.safeParse(raw).data; // Gate on the full config, not just its base: the Anima-compatible set is defined by the backend's // latent geometry, which for Wan VAEs lives in `latent_channels` and is lost in the identifier. const config = await parseModelConfig(raw, store, 'vae'); + const isMainVaeSubmodel = + config.type === 'main' && + rawIdentifier?.type === 'main' && + (rawIdentifier.submodel_type === null || + rawIdentifier.submodel_type === undefined || + rawIdentifier.submodel_type === 'vae'); + assert( + config.type !== 'main' || isMainVaeSubmodel, + 'AnimaVAEModel requires a VAE model or a main model VAE submodel' + ); // Defers to the Anima VAE picker's own domain (isAnimaCompatibleVAEModelConfig): an Anima-base // (Wan/QwenImage) VAE, a FLUX VAE, or a 16-channel Wan VAE - all of which anima_l2i / anima_i2l // accept. Recalling those from a workflow-built Anima image must not be rejected (reviews // 4966712044, 4972570279). assert( - isAnimaCompatibleVAEModelConfig(config, true), + isAnimaCompatibleVAEModelConfig(config, !isMainVaeSubmodel), 'AnimaVAEModel requires a VAE the Anima model loader accepts' ); - const parsed = zModelIdentifierField.parse(config); + const parsed = zModelIdentifierField.parse({ + ...config, + ...(isMainVaeSubmodel ? { submodel_type: 'vae' } : {}), + }); const base = selectBase(store.getState()); assert(base === 'anima', 'AnimaVAEModel handler only works with Anima models'); return Promise.resolve(parsed); From 00681481c5440ca726065f160265af06050e3ae9 Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Fri, 21 Aug 2026 21:32:51 +0200 Subject: [PATCH 07/12] fix(ui): align VAE slot domains between picker, default and recall The FLUX.1 and Z-Image VAE slots were auto-filled from the wider flux+flux2 pool while their pickers and recall handlers accept FLUX.1 only, and every VAE recall handler rejected the main-model VAE submodels its picker offers - so the VAE row silently disappeared instead of recalling. - default `params.fluxVAE` and `params.zImageVaeModel` from `isFlux1VAEModelConfig` - share `parseVAEModelIdentifier` across the FLUX.1 / FLUX.2 / Z-Image / Anima handlers, each gating on its own picker's guard - prefer a native Anima VAE over the FLUX/Wan fallbacks when defaulting the slot - call type guards with one argument in `buildModelsSelector`; `Array#filter` was passing the index as `excludeSubmodels` - surface a toast when a revalidated recall is skipped - collapse the three metadata datum hooks into one and export the verdict function so the base-change reparse is testable --- .../listeners/modelSelected.test.ts | 76 +++++- .../listeners/modelSelected.ts | 25 +- .../listeners/modelsLoaded.fluxVae.test.ts | 95 +++++++ .../listeners/modelsLoaded.ts | 10 +- .../src/features/metadata/parsing.test.tsx | 166 ++++++++++++- .../web/src/features/metadata/parsing.tsx | 233 ++++++++++-------- .../services/api/hooks/modelsByType.test.ts | 72 ++++++ .../src/services/api/hooks/modelsByType.ts | 11 +- .../web/src/services/api/types.test.ts | 36 +++ 9 files changed, 600 insertions(+), 124 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.fluxVae.test.ts create mode 100644 invokeai/frontend/web/src/services/api/hooks/modelsByType.test.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.test.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.test.ts index dcf2b843cb9..7afecbb4c3b 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.test.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.test.ts @@ -151,6 +151,9 @@ const mockSelectAnimaCompatibleVAEModels = vi.fn((_state: unknown) => [mockAnima const mockSelectQwenImageVAEModels = vi.fn((_state: unknown) => [mockKrea2Vae]); const mockSelectQwen3VLEncoderModels = vi.fn((_state: unknown) => [mockKrea2Qwen3VlEncoder]); const mockSelectZImageDiffusersModels = vi.fn((_state: unknown) => [] as unknown[]); +// Z-Image borrows the FLUX.1 VAE pool - flux2 VAEs are deliberately not part of it. +const mockSelectFlux1VAEModels = vi.fn((_state: unknown) => [] as unknown[]); +const mockSelectQwen3EncoderModels = vi.fn((_state: unknown) => [] as unknown[]); vi.mock('services/api/hooks/modelsByType', () => ({ selectAnimaQwen3EncoderModels: (state: unknown) => mockSelectAnimaQwen3EncoderModels(state), @@ -158,9 +161,9 @@ vi.mock('services/api/hooks/modelsByType', () => ({ selectAnimaCompatibleVAEModels: (state: unknown) => mockSelectAnimaCompatibleVAEModels(state), selectQwenImageVAEModels: (state: unknown) => mockSelectQwenImageVAEModels(state), selectQwen3VLEncoderModels: (state: unknown) => mockSelectQwen3VLEncoderModels(state), - selectQwen3EncoderModels: vi.fn(() => []), + selectQwen3EncoderModels: (state: unknown) => mockSelectQwen3EncoderModels(state), selectZImageDiffusersModels: (state: unknown) => mockSelectZImageDiffusersModels(state), - selectFluxVAEModels: vi.fn(() => []), + selectFlux1VAEModels: (state: unknown) => mockSelectFlux1VAEModels(state), selectGlobalRefImageModels: vi.fn(() => []), selectRegionalRefImageModels: vi.fn(() => []), })); @@ -245,6 +248,7 @@ const paramsSliceActual = (await vi.importActual('features/controlLayers/store/p krea2VaeModelSelected: { type: string }; krea2Qwen3VlEncoderModelSelected: { type: string }; zImageQwen3SourceModelSelected: { type: string }; + zImageVaeModelSelected: { type: string }; }; const { animaQwen3EncoderModelSelected, @@ -252,6 +256,7 @@ const { krea2VaeModelSelected, krea2Qwen3VlEncoderModelSelected, zImageQwen3SourceModelSelected, + zImageVaeModelSelected, } = paramsSliceActual; // Import after mocks are set up @@ -407,6 +412,37 @@ describe('modelSelected listener - Anima defaulting', () => { expect(vaeDispatch).toBeDefined(); expect(vaeDispatch!.payload).toBeNull(); }); + + // The compatible pool is ordered by model name, not by preference, and also holds the fallbacks the + // Anima loader merely tolerates: a FLUX VAE decodes on a different code path in anima_l2i entirely. + // Taking [0] blindly handed the default to whichever VAE happened to sort first. + it('should prefer a native Anima VAE over a compatible fallback that sorts first', () => { + mockSelectAnimaCompatibleVAEModels.mockReturnValue([mockFluxVAE, mockAnimaVAE]); + mockSelectAnimaVAEModels.mockReturnValue([mockAnimaVAE]); + + const state = buildMockState({ model: mockFluxMainModel }); + const action = modelSelected(zParameterModel.parse(mockAnimaMainModel)); + + capturedEffect!(action, { getState: () => state, dispatch: mockDispatch }); + + const vaeDispatch = dispatched.find((a) => a.type === animaVaeModelSelected.type); + expect(vaeDispatch!.payload).toMatchObject({ key: mockAnimaVAE.key, base: 'anima' }); + }); + + // ...but the fallback is still a valid default when nothing native is installed, which is the whole + // reason the pool was widened. + it('should fall back to a compatible VAE when no native Anima VAE is installed', () => { + mockSelectAnimaCompatibleVAEModels.mockReturnValue([mockFluxVAE]); + mockSelectAnimaVAEModels.mockReturnValue([]); + + const state = buildMockState({ model: mockFluxMainModel }); + const action = modelSelected(zParameterModel.parse(mockAnimaMainModel)); + + capturedEffect!(action, { getState: () => state, dispatch: mockDispatch }); + + const vaeDispatch = dispatched.find((a) => a.type === animaVaeModelSelected.type); + expect(vaeDispatch!.payload).toMatchObject({ key: mockFluxVAE.key, base: 'flux' }); + }); }); describe('modelSelected listener - Z-Image variant matching', () => { @@ -718,3 +754,39 @@ describe('modelSelected listener - Krea-2 defaulting', () => { expect(encoderDispatch!.payload).toBeNull(); }); }); + +// The Z-Image VAE slot draws from the FLUX.1 pool: its picker is `useFlux1VAEModels`, and there is no +// `base: 'z-image'` VAE config at all. Defaulting it from the wider flux+flux2 pool put a FLUX.2 VAE +// into a slot the user could not see it in and that Z-Image cannot decode with. +describe('modelSelected listener - Z-Image VAE defaulting', () => { + beforeEach(() => { + dispatched.length = 0; + mockDispatch.mockClear(); + // No diffusers model installed, so the listener falls through to the encoder + VAE branch. + mockSelectZImageDiffusersModels.mockReturnValue([]); + mockSelectQwen3EncoderModels.mockReturnValue([mockAnimaQwen3Encoder]); + mockSelectFlux1VAEModels.mockReturnValue([mockFluxVAE]); + }); + + it('should default the Z-Image VAE slot from the FLUX.1 pool', () => { + const state = buildMockState({ model: mockFluxMainModel }); + const action = modelSelected(zParameterModel.parse(mockZImageTurboMain)); + + capturedEffect!(action, { getState: () => state, dispatch: mockDispatch }); + + const vaeDispatch = dispatched.find((a) => a.type === zImageVaeModelSelected.type && a.payload !== null); + expect(vaeDispatch!.payload).toMatchObject({ key: mockFluxVAE.key, base: 'flux' }); + }); + + it('should not default the Z-Image VAE slot when the FLUX.1 pool is empty', () => { + mockSelectFlux1VAEModels.mockReturnValue([]); + + const state = buildMockState({ model: mockFluxMainModel }); + const action = modelSelected(zParameterModel.parse(mockZImageTurboMain)); + + capturedEffect!(action, { getState: () => state, dispatch: mockDispatch }); + + const vaeDispatch = dispatched.find((a) => a.type === zImageVaeModelSelected.type && a.payload !== null); + expect(vaeDispatch).toBeUndefined(); + }); +}); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts index 6d31e227e2f..352ab834268 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts @@ -65,7 +65,7 @@ import { selectAnimaCompatibleVAEModels, selectAnimaQwen3EncoderModels, selectAnimaVAEModels, - selectFluxVAEModels, + selectFlux1VAEModels, selectGlobalRefImageModels, selectQwen3EncoderModels, selectQwen3VLEncoderModels, @@ -177,7 +177,10 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) = } else { // Fallback: try to set Qwen3 Encoder + VAE const availableQwen3Encoders = selectQwen3EncoderModels(state); - const availableFluxVAEs = selectFluxVAEModels(state); + // FLUX.1 VAEs only - the Z-Image VAE picker is built from `isFlux1VAEModelConfig` and + // Z-Image cannot use a FLUX.2 VAE, so the wider `selectFluxVAEModels` (flux + flux2) would + // default the slot to a model the user can neither see in the picker nor generate with. + const availableFluxVAEs = selectFlux1VAEModels(state); if (availableQwen3Encoders.length > 0 && availableFluxVAEs.length > 0) { const qwen3Encoder = availableQwen3Encoders[0]; @@ -229,7 +232,11 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) = if (availableQwen3Encoders.length > 0 && availableAnimaVAEs.length > 0) { const qwen3Encoder = availableQwen3Encoders[0]; - const fluxVAE = availableAnimaVAEs[0]; + // Prefer a native Anima VAE. The compatible pool also contains FLUX and 16-channel Wan + // VAEs, which the Anima loader accepts as a *fallback* - anima_l2i decodes a FLUX VAE on a + // different code path entirely. The pool is ordered by model name, so picking [0] blindly + // would hand a FLUX VAE the default whenever it happens to sort first. + const animaVAE = selectAnimaVAEModels(state)[0] ?? availableAnimaVAEs[0]; if (qwen3Encoder && !animaQwen3EncoderModel) { dispatch( @@ -242,14 +249,14 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) = }) ); } - if (fluxVAE && !animaVaeModel) { + if (animaVAE && !animaVaeModel) { dispatch( animaVaeModelSelected({ - key: fluxVAE.key, - hash: fluxVAE.hash, - name: fluxVAE.name, - base: fluxVAE.base, - type: fluxVAE.type, + key: animaVAE.key, + hash: animaVAE.hash, + name: animaVAE.name, + base: animaVAE.base, + type: animaVAE.type, }) ); } diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.fluxVae.test.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.fluxVae.test.ts new file mode 100644 index 00000000000..f5105efe8fe --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.fluxVae.test.ts @@ -0,0 +1,95 @@ +import type { AppDispatch, RootState } from 'app/store/store'; +import { fluxVAESelected } from 'features/controlLayers/store/paramsSlice'; +import type { Logger } from 'roarr'; +import type { AnyModelConfig } from 'services/api/types'; +import type { JsonObject } from 'type-fest'; +import { describe, expect, it, vi } from 'vitest'; + +import { handleFLUXVAEModels } from './modelsLoaded'; + +/** + * `params.fluxVAE` is a FLUX.1-only slot: buildFLUXGraph feeds it to `flux_model_loader.vae_model` in the + * FLUX.1 branch (FLUX.2 uses `params.flux2VaeModel`), and its picker is built from + * `isFlux1VAEModelConfig`. Defaulting it from the wider flux+flux2 pool silently put a FLUX.2 VAE into a + * slot the user could not see it in and that FLUX.1 cannot load - and, downstream, produced a + * `metadata.vae` value that no recall handler would accept, so the VAE row vanished from the panel. + */ + +const flux1Vae = { + key: 'flux1-vae', + hash: 'h', + name: 'FLUX.1 VAE', + base: 'flux', + type: 'vae', + format: 'checkpoint', +} as unknown as AnyModelConfig; + +const flux2Vae = { + ...flux1Vae, + key: 'flux2-vae', + name: 'FLUX.2 VAE', + base: 'flux2', +} as unknown as AnyModelConfig; + +const flux1MainWithVaeSubmodel = { + key: 'flux1-main', + hash: 'h', + name: 'FLUX.1 dev (diffusers)', + base: 'flux', + type: 'main', + format: 'diffusers', + submodels: { vae: {} }, +} as unknown as AnyModelConfig; + +const makeState = (fluxVAE: unknown = null) => ({ params: { fluxVAE } }) as unknown as RootState; + +const log = { debug: vi.fn() } as unknown as Logger; + +const run = (models: AnyModelConfig[], fluxVAE: unknown = null) => { + const dispatch = vi.fn() as unknown as AppDispatch; + handleFLUXVAEModels(models, makeState(fluxVAE), dispatch, log); + return dispatch as unknown as ReturnType; +}; + +describe('handleFLUXVAEModels', () => { + it('selects a FLUX.1 VAE', () => { + const dispatch = run([flux1Vae]); + + expect(dispatch).toHaveBeenCalledWith(fluxVAESelected(expect.objectContaining({ key: flux1Vae.key }))); + }); + + it('never selects a FLUX.2 VAE, even when it is the only VAE installed', () => { + const dispatch = run([flux2Vae]); + + expect(dispatch).not.toHaveBeenCalled(); + }); + + it('skips a FLUX.2 VAE that sorts before the FLUX.1 one', () => { + const dispatch = run([flux2Vae, flux1Vae]); + + expect(dispatch).toHaveBeenCalledTimes(1); + expect(dispatch).toHaveBeenCalledWith(fluxVAESelected(expect.objectContaining({ key: flux1Vae.key }))); + }); + + // The picker offers bundled VAE submodels, so the auto-default may pick one too - and the recall + // handler now accepts it rather than dropping the row. + it('accepts a FLUX.1 main model bundled VAE submodel', () => { + const dispatch = run([flux1MainWithVaeSubmodel]); + + expect(dispatch).toHaveBeenCalledWith( + fluxVAESelected(expect.objectContaining({ key: flux1MainWithVaeSubmodel.key })) + ); + }); + + it('clears a selection that is no longer available', () => { + const dispatch = run([], flux1Vae); + + expect(dispatch).toHaveBeenCalledWith(fluxVAESelected(null)); + }); + + it('leaves an available selection alone', () => { + const dispatch = run([flux1Vae], flux1Vae); + + expect(dispatch).not.toHaveBeenCalled(); + }); +}); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts index 6851e9fb44b..ff7b961f096 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts @@ -42,8 +42,8 @@ import { isCLIPEmbedModelConfigOrSubmodel, isControlLayerModelConfig, isControlNetModelConfig, + isFlux1VAEModelConfig, isFluxReduxModelConfig, - isFluxVAEModelConfig, isIPAdapterModelConfig, isLoRAModelConfig, isNonFluxVAEModelConfig, @@ -482,9 +482,13 @@ const handleCLIPEmbedModels: ModelHandler = (models, state, dispatch, log) => { } }; -const handleFLUXVAEModels: ModelHandler = (models, state, dispatch, log) => { +export const handleFLUXVAEModels: ModelHandler = (models, state, dispatch, log) => { const selectedFLUXVAEModel = state.params.fluxVAE; - const fluxVAEModels = models.filter((m) => isFluxVAEModelConfig(m)); + // FLUX.1 VAEs only. `params.fluxVAE` feeds `flux_model_loader.vae_model` in the FLUX.1 branch of + // buildFLUXGraph - FLUX.2 uses `params.flux2VaeModel` instead - and the picker is built from + // `isFlux1VAEModelConfig`. Defaulting from the wider flux+flux2 pool put a FLUX.2 VAE into a slot the + // user could not see it in and that FLUX.1 cannot load. + const fluxVAEModels = models.filter((m) => isFlux1VAEModelConfig(m)); // If the currently selected model is available, we don't need to do anything if (selectedFLUXVAEModel && fluxVAEModels.some((m) => m.key === selectedFLUXVAEModel.key)) { diff --git a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx index ea1aa4f3319..fee174c6193 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx @@ -1,6 +1,6 @@ import type { AppStore } from 'app/store/store'; import type * as paramsSliceModule from 'features/controlLayers/store/paramsSlice'; -import { ImageMetadataHandlers, recallIfStillValid } from 'features/metadata/parsing'; +import { ImageMetadataHandlers, parseMetadataDatum, recallIfStillValid } from 'features/metadata/parsing'; import type * as modelsApiModule from 'services/api/endpoints/models'; import { beforeEach, describe, expect, it, vi } from 'vitest'; @@ -788,9 +788,11 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () }); describe('ZImageVAEModel', () => { - it('parses a Z-Image image while Z-Image is selected', async () => { + // There is no `base: 'z-image'` VAE config in the backend at all - the Z-Image VAE slot draws from + // the FLUX.1 pool (`useFlux1VAEModels`), which is what these fixtures reflect. + it('parses a FLUX VAE from a Z-Image image while Z-Image is selected', async () => { currentBase = 'z-image'; - nextResolved = fakeModel('vae', 'z-image'); + nextResolved = fakeModel('vae', 'flux'); const store = makeStore(); const parsed = await ImageMetadataHandlers.ZImageVAEModel.parse( @@ -801,6 +803,34 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () expect(parsed.key).toBe('vae-key'); }); + // The picker offers a main model's bundled `vae` submodel, so metadata can carry one. Asserting + // `type === 'vae'` used to drop the row for exactly those models. + it('parses a FLUX main-model VAE submodel from Z-Image metadata', async () => { + currentBase = 'z-image'; + const fluxMainWithVae = fakeModel('main', 'flux', { submodels: { vae: { model_type: 'vae' } } }); + modelRegistry['main-key'] = fluxMainWithVae; + const store = makeStore(); + + const parsed = await ImageMetadataHandlers.ZImageVAEModel.parse( + { model: fakeMain('z-image'), vae: { ...fluxMainWithVae, submodel_type: 'vae' } }, + store + ); + + expect(parsed.type).toBe('main'); + expect(parsed.submodel_type).toBe('vae'); + }); + + // Z-Image explicitly cannot use a FLUX.2 VAE, and no other base's VAE fits either. + it.each(['flux2', 'sdxl', 'anima'])('rejects a %s VAE from Z-Image metadata', async (vaeBase) => { + currentBase = 'z-image'; + nextResolved = fakeModel('vae', vaeBase); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.ZImageVAEModel.parse({ model: fakeMain('z-image'), vae: nextResolved }, store) + ).rejects.toThrow(); + }); + it.each(['flux', 'anima'])('rejects %s image metadata while Z-Image is selected', async (base) => { currentBase = 'z-image'; nextResolved = fakeModel('vae', base); @@ -813,7 +843,7 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () it('rejects when the current base is not Z-Image', async () => { currentBase = 'flux'; - nextResolved = fakeModel('vae', 'z-image'); + nextResolved = fakeModel('vae', 'flux'); const store = makeStore(); await expect( @@ -895,6 +925,51 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () ImageMetadataHandlers.Flux1VAEModel.parse({ model: fakeMain('z-image'), vae: nextResolved }, store) ).rejects.toThrow(); }); + + // `useFlux1VAEModels` offers a main model's bundled `vae` submodel, and `handleFLUXVAEModels` can + // auto-select one into `params.fluxVAE`. Asserting `type === 'vae'` dropped the row for exactly the + // models the picker offered - the recall button the PR set out to fix stayed missing. + it('parses a FLUX main-model VAE submodel from FLUX.1 metadata', async () => { + currentBase = 'flux'; + const fluxMainWithVae = fakeModel('main', 'flux', { submodels: { vae: { model_type: 'vae' } } }); + modelRegistry['main-key'] = fluxMainWithVae; + const store = makeStore(); + + const parsed = await ImageMetadataHandlers.Flux1VAEModel.parse( + { model: fakeMain('flux'), vae: { ...fluxMainWithVae, submodel_type: 'vae' } }, + store + ); + + expect(parsed.type).toBe('main'); + expect(parsed.submodel_type).toBe('vae'); + expect(parsed.base).toBe('flux'); + }); + + // `flux_model_loader` cannot load a FLUX.2 VAE, so it must not reach `params.fluxVAE` on recall + // either - the auto-default no longer puts one there. + it('rejects a FLUX.2 VAE from FLUX.1 metadata', async () => { + currentBase = 'flux'; + nextResolved = fakeModel('vae', 'flux2'); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.Flux1VAEModel.parse({ model: fakeMain('flux'), vae: nextResolved }, store) + ).rejects.toThrow(); + }); + + it('rejects a main model whose reference is not its VAE submodel', async () => { + currentBase = 'flux'; + const fluxMainWithVae = fakeModel('main', 'flux', { submodels: { vae: { model_type: 'vae' } } }); + modelRegistry['main-key'] = fluxMainWithVae; + const store = makeStore(); + + await expect( + ImageMetadataHandlers.Flux1VAEModel.parse( + { model: fakeMain('flux'), vae: { ...fluxMainWithVae, submodel_type: 'text_encoder' } }, + store + ) + ).rejects.toThrow(); + }); }); describe('Flux2VAEModel', () => { @@ -920,6 +995,32 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () ImageMetadataHandlers.Flux2VAEModel.parse({ model: fakeMain('krea-2'), vae: nextResolved }, store) ).rejects.toThrow(); }); + + // Same submodel gap as FLUX.1 / Z-Image: `useFlux2VAEModels` offers bundled VAE submodels. + it('parses a FLUX.2 main-model VAE submodel', async () => { + currentBase = 'flux2'; + const flux2MainWithVae = fakeModel('main', 'flux2', { submodels: { vae: { model_type: 'vae' } } }); + modelRegistry['main-key'] = flux2MainWithVae; + const store = makeStore(); + + const parsed = await ImageMetadataHandlers.Flux2VAEModel.parse( + { model: fakeMain('flux2'), vae: { ...flux2MainWithVae, submodel_type: 'vae' } }, + store + ); + + expect(parsed.type).toBe('main'); + expect(parsed.submodel_type).toBe('vae'); + }); + + it('rejects a FLUX.1 VAE from FLUX.2 metadata', async () => { + currentBase = 'flux2'; + nextResolved = fakeModel('vae', 'flux'); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.Flux2VAEModel.parse({ model: fakeMain('flux2'), vae: nextResolved }, store) + ).rejects.toThrow(); + }); }); }); @@ -1001,3 +1102,60 @@ describe('recallIfStillValid', () => { expect(recall).toHaveBeenCalledWith(sentinel, store); }); }); + +// `useMetadataDatum` subscribes to the selected base and re-runs this on every change, which is what +// makes a row appear or disappear when the user switches models with the viewer open. There is no DOM +// test framework here to drive the hook itself, so the extracted verdict function carries the coverage: +// same metadata, same handler, different base => different verdict. Review 4987208275. +describe('parseMetadataDatum', () => { + const animaMetadata = () => ({ + model: { key: 'main-key', hash: 'h', name: 'anima', base: 'anima', type: 'main' }, + vae: fakeModel('vae', 'anima'), + }); + + it('flips a row from hidden to shown when the base becomes the one the handler wants', async () => { + nextResolved = fakeModel('vae', 'anima'); + const metadata = animaMetadata(); + + currentBase = 'sdxl'; + const before = await parseMetadataDatum(metadata, ImageMetadataHandlers.AnimaVAEModel, makeStore()); + currentBase = 'anima'; + const after = await parseMetadataDatum(metadata, ImageMetadataHandlers.AnimaVAEModel, makeStore()); + + expect(before.isSuccess).toBe(false); + expect(before.isError).toBe(true); + expect(after.isSuccess).toBe(true); + expect(after.value?.base).toBe('anima'); + }); + + it('flips a row from shown to hidden when the base moves away', async () => { + nextResolved = fakeModel('vae', 'anima'); + const metadata = animaMetadata(); + + currentBase = 'anima'; + const before = await parseMetadataDatum(metadata, ImageMetadataHandlers.AnimaVAEModel, makeStore()); + currentBase = 'z-image'; + const after = await parseMetadataDatum(metadata, ImageMetadataHandlers.AnimaVAEModel, makeStore()); + + expect(before.isSuccess).toBe(true); + expect(after.isSuccess).toBe(false); + expect(after.isError).toBe(true); + }); + + // A rejected parse must be reported as an error datum, never as a rejected promise - the hook has no + // catch of its own, so a throw here would leave the row stuck in its previous state. + it('reports a failed parse as an error datum rather than rejecting', async () => { + currentBase = 'anima'; + nextResolved = fakeModel('vae', 'anima'); + + const data = await parseMetadataDatum( + { model: { key: 'main-key', hash: 'h', name: 'krea-2', base: 'krea-2', type: 'main' } }, + ImageMetadataHandlers.AnimaVAEModel, + makeStore() + ); + + expect(data.isParsed).toBe(true); + expect(data.isError).toBe(true); + expect(data.error).toBeInstanceOf(Error); + }); +}); diff --git a/invokeai/frontend/web/src/features/metadata/parsing.tsx b/invokeai/frontend/web/src/features/metadata/parsing.tsx index 5b18556586b..0ba7241e5c6 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.tsx @@ -154,7 +154,7 @@ import { useTranslation } from 'react-i18next'; import { imagesApi } from 'services/api/endpoints/images'; import { modelsApi } from 'services/api/endpoints/models'; import type { AnyModelConfig } from 'services/api/types'; -import { isAnimaCompatibleVAEModelConfig } from 'services/api/types'; +import { isAnimaCompatibleVAEModelConfig, isFlux1VAEModelConfig, isFlux2VAEModelConfig } from 'services/api/types'; import { assert } from 'tsafe'; import z from 'zod'; @@ -1544,10 +1544,15 @@ const Flux1VAEModel: SingleMetadataHandler = { // Check provenance: `vae` is a shared field. Z-Image (and any base whose VAE pool includes FLUX // VAEs) can record a FLUX VAE, which would otherwise land in `params.fluxVAE` (review 4966712044). assertMetadataModelBase(metadata, 'flux', 'Flux1VAEModel'); - const raw = getProperty(metadata, 'vae'); - const parsed = await parseModelIdentifier(raw, store, 'vae'); - assert(parsed.type === 'vae'); - assert(parsed.base === 'flux', 'Flux1VAEModel requires a FLUX VAE'); + // The slot's domain is exactly its picker's (`useFlux1VAEModels`): a FLUX.1 VAE, standalone or as a + // main model's bundled `vae` submodel. FLUX.2 VAEs are excluded - `flux_model_loader` cannot load + // one - and so is every other base. + const parsed = await parseVAEModelIdentifier({ + raw: getProperty(metadata, 'vae'), + store, + isCompatible: isFlux1VAEModelConfig, + handlerType: 'Flux1VAEModel', + }); const base = selectBase(store.getState()); assert(base === 'flux', 'Flux1VAEModel handler only works with FLUX.1 models'); return parsed; @@ -1623,9 +1628,14 @@ const ZImageVAEModel: SingleMetadataHandler = { // Check provenance: `vae` is a shared field, and this handler additionally clears // `zImageQwen3SourceModel` - foreign metadata must never reach it (review 4966712044). assertMetadataModelBase(metadata, 'z-image', 'ZImageVAEModel'); - const raw = getProperty(metadata, 'vae'); - const parsed = await parseModelIdentifier(raw, store, 'vae'); - assert(parsed.type === 'vae'); + // Z-Image borrows the FLUX.1 VAE pool - its picker is `useFlux1VAEModels` and it explicitly cannot + // use a FLUX.2 VAE - so this slot has the same domain as `params.fluxVAE`, submodels included. + const parsed = await parseVAEModelIdentifier({ + raw: getProperty(metadata, 'vae'), + store, + isCompatible: isFlux1VAEModelConfig, + handlerType: 'ZImageVAEModel', + }); // Only recall if the current main model is Z-Image const base = selectBase(store.getState()); assert(base === 'z-image', 'ZImageVAEModel handler only works with Z-Image models'); @@ -1862,32 +1872,15 @@ const AnimaVAEModel: SingleMetadataHandler = { // Without this, a Krea-2 image recalled while Anima is selected would push its Qwen-Image VAE into // the Anima slot. assertMetadataModelBase(metadata, 'anima', 'AnimaVAEModel'); - const raw = getProperty(metadata, 'vae'); - const rawIdentifier = zModelIdentifierField.safeParse(raw).data; - // Gate on the full config, not just its base: the Anima-compatible set is defined by the backend's - // latent geometry, which for Wan VAEs lives in `latent_channels` and is lost in the identifier. - const config = await parseModelConfig(raw, store, 'vae'); - const isMainVaeSubmodel = - config.type === 'main' && - rawIdentifier?.type === 'main' && - (rawIdentifier.submodel_type === null || - rawIdentifier.submodel_type === undefined || - rawIdentifier.submodel_type === 'vae'); - assert( - config.type !== 'main' || isMainVaeSubmodel, - 'AnimaVAEModel requires a VAE model or a main model VAE submodel' - ); // Defers to the Anima VAE picker's own domain (isAnimaCompatibleVAEModelConfig): an Anima-base // (Wan/QwenImage) VAE, a FLUX VAE, or a 16-channel Wan VAE - all of which anima_l2i / anima_i2l - // accept. Recalling those from a workflow-built Anima image must not be rejected (reviews - // 4966712044, 4972570279). - assert( - isAnimaCompatibleVAEModelConfig(config, !isMainVaeSubmodel), - 'AnimaVAEModel requires a VAE the Anima model loader accepts' - ); - const parsed = zModelIdentifierField.parse({ - ...config, - ...(isMainVaeSubmodel ? { submodel_type: 'vae' } : {}), + // accept. Gating on the full config matters here beyond the submodel question: the Wan arm reads + // `latent_channels`, which the identifier does not carry (reviews 4966712044, 4972570279). + const parsed = await parseVAEModelIdentifier({ + raw: getProperty(metadata, 'vae'), + store, + isCompatible: isAnimaCompatibleVAEModelConfig, + handlerType: 'AnimaVAEModel', }); const base = selectBase(store.getState()); assert(base === 'anima', 'AnimaVAEModel handler only works with Anima models'); @@ -1945,9 +1938,13 @@ const Flux2VAEModel: SingleMetadataHandler = { // Check provenance: `vae` is a shared field - e.g. a Krea-2 image (Qwen-Image VAE) recalled while // FLUX.2 is selected would otherwise land in `params.flux2VaeModel`. assertMetadataModelBase(metadata, 'flux2', 'Flux2VAEModel'); - const raw = getProperty(metadata, 'vae'); - const parsed = await parseModelIdentifier(raw, store, 'vae'); - assert(parsed.type === 'vae'); + // Same domain as the picker (`useFlux2VAEModels`), which both Klein and [dev] share. + const parsed = await parseVAEModelIdentifier({ + raw: getProperty(metadata, 'vae'), + store, + isCompatible: isFlux2VAEModelConfig, + handlerType: 'Flux2VAEModel', + }); const base = selectBase(store.getState()); assert(base === 'flux2', 'Flux2VAEModel handler only works with FLUX.2 models'); return parsed; @@ -2696,82 +2693,86 @@ export const recallIfStillValid = async (arg: { const useRevalidatedRecall = ( metadata: unknown, - handler: { parse: (metadata: unknown, store: AppStore) => Promise }, + handler: { parse: (metadata: unknown, store: AppStore) => Promise; i18nKey: string }, recall: (value: TValue, store: AppStore) => void ) => { const store = useAppStore(); return useCallback( (value: TValue) => { - void recallIfStillValid({ metadata, handler, recall, value, store }); + void recallIfStillValid({ metadata, handler, recall, value, store }).then((didRecall) => { + if (!didRecall) { + // The button is still on screen because the reparse has not landed yet. Silently doing + // nothing reads as a broken button, so say so - same toast `recallByHandler` uses when a + // parse fails. + failedToast(t(handler.i18nKey)); + } + }); }, [metadata, handler, store, recall] ); }; -export function useSingleMetadataDatum(metadata: unknown, handler: SingleMetadataHandler) { +/** + * The parse verdict for one metadata row, against the store's *current* state. + * + * Extracted from the hooks below so it can be tested directly: the verdict is a function of the selected + * base as much as of the metadata (handlers read `selectBase(store.getState())` imperatively inside + * `parse`), and there is no DOM test framework here to drive a hook through a base switch. + */ +export const parseMetadataDatum = async ( + metadata: unknown, + handler: { parse: (metadata: unknown, store: AppStore) => Promise }, + store: AppStore +): Promise | ParsedErrorData> => { + try { + return buildParsedSuccessData(await parseMetadataHandler(metadata, handler, store)); + } catch (error) { + return buildParsedErrorData(WrappedError.wrap(error)); + } +}; + +/** + * Keeps one row's parse verdict in sync with the metadata, the handler and the selected base. + * + * Single source for all three datum hooks: the `base` dependency is the whole point of the subscription + * (see `useSelectedBaseForReparse`), and having it written out three times invited exactly one of them + * to be forgotten. + */ +const useMetadataDatum = ( + metadata: unknown, + handler: { parse: (metadata: unknown, store: AppStore) => Promise } +): Data => { const store = useAppStore(); const base = useSelectedBaseForReparse(); - const [data, setData] = useState>(() => ({ - isParsed: false, - isSuccess: false, - isError: false, - value: null, - error: null, - })); + const [data, setData] = useState>(buildUnparsedData); useEffect(() => { let isActive = true; - void parseMetadataHandler(metadata, handler, store).then( - (value) => { - if (isActive) { - setData(buildParsedSuccessData(value)); - } - }, - (error) => { - if (isActive) { - setData(buildParsedErrorData(WrappedError.wrap(error))); - } + void parseMetadataDatum(metadata, handler, store).then((next) => { + if (isActive) { + setData(next); } - ); + }); return () => { isActive = false; }; }, [metadata, handler, store, base]); + return data; +}; + +export function useSingleMetadataDatum(metadata: unknown, handler: SingleMetadataHandler) { + const data = useMetadataDatum(metadata, handler); const recall = useRevalidatedRecall(metadata, handler, handler.recall); return { data, recall }; } export function useCollectionMetadataDatum(metadata: unknown, handler: CollectionMetadataHandler) { - const store = useAppStore(); - const base = useSelectedBaseForReparse(); - const [data, setData] = useState>(buildUnparsedData); - - useEffect(() => { - let isActive = true; - - void parseMetadataHandler(metadata, handler, store).then( - (value) => { - if (isActive) { - setData(buildParsedSuccessData(value)); - } - }, - (error) => { - if (isActive) { - setData(buildParsedErrorData(WrappedError.wrap(error))); - } - } - ); - - return () => { - isActive = false; - }; - }, [metadata, handler, store, base]); - + const data = useMetadataDatum(metadata, handler); const recallAll = useRevalidatedRecall(metadata, handler, handler.recall); const recallOne = useRevalidatedRecall(metadata, handler, handler.recallOne); @@ -2779,30 +2780,7 @@ export function useCollectionMetadataDatum(metadata: unknown, h } export function useUnrecallableMetadataDatum(metadata: unknown, handler: UnrecallableMetadataHandler) { - const store = useAppStore(); - const base = useSelectedBaseForReparse(); - const [data, setData] = useState>(buildUnparsedData); - - useEffect(() => { - let isActive = true; - - void parseMetadataHandler(metadata, handler, store).then( - (value) => { - if (isActive) { - setData(buildParsedSuccessData(value)); - } - }, - (error) => { - if (isActive) { - setData(buildParsedErrorData(WrappedError.wrap(error))); - } - } - ); - - return () => { - isActive = false; - }; - }, [metadata, handler, store, base]); + const data = useMetadataDatum(metadata, handler); return { data }; } @@ -2861,6 +2839,51 @@ const parseModelIdentifier = async (raw: unknown, store: AppStore, type: ModelTy return zModelIdentifierField.parse(await parseModelConfig(raw, store, type)); }; +/** + * Resolve a `vae` metadata reference to the identifier a VAE slot should be filled with. + * + * A VAE slot may legitimately hold a *main model's bundled `vae` submodel*: every VAE picker is built + * from a `is*VAEModelConfig` guard called without `excludeSubmodels`, so main models with a `vae` + * submodel show up as options, and the graph builders then record that identifier verbatim. Asserting + * `parsed.type === 'vae'` - as each of these handlers used to - therefore dropped the metadata row + * outright for exactly the models the picker offered. + * + * Gating on the full config rather than on the identifier also lets callers check properties that the + * identifier does not carry, such as a Wan VAE's `latent_channels`. + * + * @param isCompatible the slot's own domain, i.e. the same guard its picker is built from. + * @param handlerType used in the assertion messages, so a rejected row is traceable to its handler. + */ +const parseVAEModelIdentifier = async (arg: { + raw: unknown; + store: AppStore; + isCompatible: (config: AnyModelConfig, excludeSubmodels?: boolean) => boolean; + handlerType: string; +}): Promise => { + const { raw, store, isCompatible, handlerType } = arg; + const rawIdentifier = zModelIdentifierField.safeParse(raw).data; + const config = await parseModelConfig(raw, store, 'vae'); + // A main model only qualifies when the reference actually points at its VAE. An absent submodel_type + // counts: the linear-UI graph builders record the picker's identifier as-is, without one. + const isMainVaeSubmodel = + config.type === 'main' && + rawIdentifier?.type === 'main' && + (rawIdentifier.submodel_type === null || + rawIdentifier.submodel_type === undefined || + rawIdentifier.submodel_type === 'vae'); + assert( + config.type !== 'main' || isMainVaeSubmodel, + `${handlerType} requires a VAE model or a main model VAE submodel` + ); + assert(isCompatible(config, !isMainVaeSubmodel), `${handlerType} requires a VAE this slot can hold`); + // Normalised so the recalled value is unambiguous downstream - a bare main-model identifier in a VAE + // slot would be indistinguishable from a main-model selection. + return zModelIdentifierField.parse({ + ...config, + ...(isMainVaeSubmodel ? { submodel_type: 'vae' } : {}), + }); +}; + const isCompatibleWithMainModel = (candidate: ModelIdentifierField, store: AppStore) => { const base = selectBase(store.getState()); if (!base) { diff --git a/invokeai/frontend/web/src/services/api/hooks/modelsByType.test.ts b/invokeai/frontend/web/src/services/api/hooks/modelsByType.test.ts new file mode 100644 index 00000000000..9edeb976199 --- /dev/null +++ b/invokeai/frontend/web/src/services/api/hooks/modelsByType.test.ts @@ -0,0 +1,72 @@ +import type { RootState } from 'app/store/store'; +import type * as modelsEndpointModule from 'services/api/endpoints/models'; +import type { AnyModelConfig } from 'services/api/types'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +/** + * `buildModelsSelector` filters the installed models with a type guard. Several of those guards take an + * optional second parameter (`excludeSubmodels`), and `Array#filter` passes the element *index* as the + * second argument - so a point-free `.filter(typeGuard)` evaluated the first entry with submodels + * included and every other entry with them excluded. Pool membership then depended on a model's position + * in the (name-ordered) list rather than on the model. + */ + +let configs: AnyModelConfig[] = []; + +vi.mock('services/api/endpoints/models', async (importOriginal) => { + const mod = await importOriginal(); + return { + ...mod, + selectModelConfigsQuery: () => ({ + data: { + ids: configs.map((config) => config.key), + entities: Object.fromEntries(configs.map((config) => [config.key, config])), + }, + }), + selectMissingModelsQuery: () => ({ data: undefined }), + }; +}); + +const { selectAnimaCompatibleVAEModels } = await import('services/api/hooks/modelsByType'); + +const state = {} as RootState; + +const standaloneVae = (key: string, base: string) => + ({ key, hash: `${key}-hash`, name: key, base, type: 'vae', format: 'checkpoint' }) as unknown as AnyModelConfig; + +const mainWithVaeSubmodel = (key: string, base: string) => + ({ + key, + hash: `${key}-hash`, + name: key, + base, + type: 'main', + format: 'diffusers', + submodels: { vae: {} }, + }) as unknown as AnyModelConfig; + +beforeEach(() => { + configs = []; +}); + +describe('buildModelsSelector', () => { + it('includes a main-model VAE submodel regardless of its position in the list', () => { + const first = mainWithVaeSubmodel('flux-main-a', 'flux'); + const second = mainWithVaeSubmodel('flux-main-b', 'flux'); + configs = [first, second]; + + expect(selectAnimaCompatibleVAEModels(state).map((config) => config.key)).toEqual([first.key, second.key]); + }); + + it('does not drop entries that only differ from the first by their index', () => { + configs = [standaloneVae('anima-vae', 'anima'), mainWithVaeSubmodel('flux-main', 'flux')]; + + expect(selectAnimaCompatibleVAEModels(state)).toHaveLength(2); + }); + + it('still rejects models the guard does not admit', () => { + configs = [standaloneVae('sdxl-vae', 'sdxl'), standaloneVae('flux2-vae', 'flux2')]; + + expect(selectAnimaCompatibleVAEModels(state)).toEqual([]); + }); +}); diff --git a/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts b/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts index 510951c52c3..5b97b62cfa5 100644 --- a/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts +++ b/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts @@ -157,7 +157,10 @@ const buildModelsSelector = return modelConfigsAdapterSelectors .selectAll(result.data) - .filter(typeGuard) + // Called with exactly one argument on purpose: several guards take an optional `excludeSubmodels` + // second parameter, and a point-free `.filter(typeGuard)` would hand them the array *index* - so + // the first entry would be evaluated with submodels included and every other one without. + .filter((config): config is T => typeGuard(config)) .filter((config) => !missingModelKeys.has(config.key)); }; export const selectIPAdapterModels = buildModelsSelector(isIPAdapterModelConfig); @@ -176,6 +179,12 @@ export const selectZImageDiffusersModels = buildModelsSelector(isZImageDiffusers export const selectFlux2DiffusersModels = buildModelsSelector(isFlux2DiffusersMainModelConfig); export const selectFlux2DevDiffusersModels = buildModelsSelector(isFlux2DevDiffusersMainModelConfig); export const selectFluxVAEModels = buildModelsSelector(isFluxVAEModelConfig); +/** + * FLUX.1 VAEs only. The `params.fluxVAE` and `params.zImageVaeModel` slots both hold one of these - + * their pickers are built from `isFlux1VAEModelConfig`, and neither slot can use a FLUX.2 VAE - so the + * listeners that auto-fill them must draw from this pool, not from the wider `selectFluxVAEModels`. + */ +export const selectFlux1VAEModels = buildModelsSelector(isFlux1VAEModelConfig); export const selectAnimaVAEModels = buildModelsSelector(isAnimaVAEModelConfig); export const selectAnimaCompatibleVAEModels = buildModelsSelector(isAnimaCompatibleVAEModelConfig); export const selectQwen3VLEncoderModels = buildModelsSelector(isQwen3VLEncoderModelConfig); diff --git a/invokeai/frontend/web/src/services/api/types.test.ts b/invokeai/frontend/web/src/services/api/types.test.ts index 1092a27680e..d99abf0a726 100644 --- a/invokeai/frontend/web/src/services/api/types.test.ts +++ b/invokeai/frontend/web/src/services/api/types.test.ts @@ -73,6 +73,42 @@ describe('Anima VAE predicates', () => { expect(isAnimaVAEModelConfig(vae(base))).toBe(false); expect(isAnimaCompatibleVAEModelConfig(vae(base))).toBe(false); }); + + // Every VAE picker is built from one of these guards called with a *single* argument, so main models + // with a bundled `vae` submodel are part of each slot's domain. The `excludeSubmodels` parameter is + // what distinguishes that from a geometry check the submodel cannot answer - see the Wan arm below. + describe('main-model VAE submodels', () => { + const mainWithVae = (base: string) => + ({ key: `${base}-main`, type: 'main', base, name: `${base} main`, submodels: { vae: {} } }) as never; + + it('accepts a FLUX main-model VAE submodel unless submodels are excluded', () => { + expect(isAnimaCompatibleVAEModelConfig(mainWithVae('flux'))).toBe(true); + expect(isAnimaCompatibleVAEModelConfig(mainWithVae('flux'), true)).toBe(false); + }); + + it('accepts an Anima main-model VAE submodel unless submodels are excluded', () => { + expect(isAnimaVAEModelConfig(mainWithVae('anima'))).toBe(true); + expect(isAnimaVAEModelConfig(mainWithVae('anima'), true)).toBe(false); + expect(isAnimaCompatibleVAEModelConfig(mainWithVae('anima'))).toBe(true); + }); + + // A bundled submodel carries no `latent_channels`, so a Wan main model can never clear the geometry + // check - regardless of what is passed for `excludeSubmodels`. + it('never accepts a Wan main-model VAE submodel', () => { + expect(isAnimaCompatibleVAEModelConfig(mainWithVae('wan'))).toBe(false); + expect(isAnimaCompatibleVAEModelConfig(mainWithVae('wan'), true)).toBe(false); + }); + + // Point-free `.filter(guard)` would hand these guards the array *index* as `excludeSubmodels`. + // TypeScript rejects that at this call site, but not inside `buildModelsSelector`, whose generic + // signature declares the guard as single-parameter - see modelsByType.test.ts for that coverage. + it('changes its verdict based on the second argument', () => { + const config = mainWithVae('flux'); + + expect(isAnimaCompatibleVAEModelConfig(config, false)).toBe(true); + expect(isAnimaCompatibleVAEModelConfig(config, true)).toBe(false); + }); + }); }); const wanMain = (over: Record) => From 26656749b33b1e7230cc07dbda31615d3103d330 Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Fri, 21 Aug 2026 21:37:25 +0200 Subject: [PATCH 08/12] Chore typegen --- .../web/src/services/api/hooks/modelsByType.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts b/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts index 5b97b62cfa5..ac9a6900764 100644 --- a/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts +++ b/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts @@ -155,13 +155,15 @@ const buildModelsSelector = modelConfigsAdapterSelectors.selectAll(missingResult.data ?? { ids: [], entities: {} }).map((m) => m.key) ); - return modelConfigsAdapterSelectors - .selectAll(result.data) - // Called with exactly one argument on purpose: several guards take an optional `excludeSubmodels` - // second parameter, and a point-free `.filter(typeGuard)` would hand them the array *index* - so - // the first entry would be evaluated with submodels included and every other one without. - .filter((config): config is T => typeGuard(config)) - .filter((config) => !missingModelKeys.has(config.key)); + return ( + modelConfigsAdapterSelectors + .selectAll(result.data) + // Called with exactly one argument on purpose: several guards take an optional `excludeSubmodels` + // second parameter, and a point-free `.filter(typeGuard)` would hand them the array *index* - so + // the first entry would be evaluated with submodels included and every other one without. + .filter((config): config is T => typeGuard(config)) + .filter((config) => !missingModelKeys.has(config.key)) + ); }; export const selectIPAdapterModels = buildModelsSelector(isIPAdapterModelConfig); export const selectGlobalRefImageModels = buildModelsSelector( From 359787a70ccfd63ada50df77781573230658ab54 Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Fri, 21 Aug 2026 21:52:40 +0200 Subject: [PATCH 09/12] Fix export --- invokeai/frontend/web/src/services/api/hooks/modelsByType.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts b/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts index ac9a6900764..485b42b32f3 100644 --- a/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts +++ b/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts @@ -180,7 +180,7 @@ export const selectQwenVLEncoderModels = buildModelsSelector(isQwenVLEncoderMode export const selectZImageDiffusersModels = buildModelsSelector(isZImageDiffusersMainModelConfig); export const selectFlux2DiffusersModels = buildModelsSelector(isFlux2DiffusersMainModelConfig); export const selectFlux2DevDiffusersModels = buildModelsSelector(isFlux2DevDiffusersMainModelConfig); -export const selectFluxVAEModels = buildModelsSelector(isFluxVAEModelConfig); +const selectFluxVAEModels = buildModelsSelector(isFluxVAEModelConfig); /** * FLUX.1 VAEs only. The `params.fluxVAE` and `params.zImageVaeModel` slots both hold one of these - * their pickers are built from `isFlux1VAEModelConfig`, and neither slot can use a FLUX.2 VAE - so the From 67f1972aa3afa8545e80ec9f63cd8407706e2085 Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Fri, 21 Aug 2026 22:14:42 +0200 Subject: [PATCH 10/12] fix(ui): drop the now-dead flux+flux2 VAE predicate selectFluxVAEModels lost its last consumer when the FLUX.1 and Z-Image VAE defaults moved to selectFlux1VAEModels. Removing the selector also leaves isFluxVAEModelConfig unused, which knip flags in turn - both go. --- .../listenerMiddleware/listeners/modelSelected.ts | 4 ++-- .../frontend/web/src/services/api/hooks/modelsByType.ts | 4 +--- invokeai/frontend/web/src/services/api/types.ts | 7 ------- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts index 352ab834268..2b9b67740af 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts @@ -178,8 +178,8 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) = // Fallback: try to set Qwen3 Encoder + VAE const availableQwen3Encoders = selectQwen3EncoderModels(state); // FLUX.1 VAEs only - the Z-Image VAE picker is built from `isFlux1VAEModelConfig` and - // Z-Image cannot use a FLUX.2 VAE, so the wider `selectFluxVAEModels` (flux + flux2) would - // default the slot to a model the user can neither see in the picker nor generate with. + // Z-Image cannot use a FLUX.2 VAE, so a wider flux+flux2 pool would default the slot to + // a model the user can neither see in the picker nor generate with. const availableFluxVAEs = selectFlux1VAEModels(state); if (availableQwen3Encoders.length > 0 && availableFluxVAEs.length > 0) { diff --git a/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts b/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts index 485b42b32f3..1cbccef9290 100644 --- a/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts +++ b/invokeai/frontend/web/src/services/api/hooks/modelsByType.ts @@ -25,7 +25,6 @@ import { isFlux2VAEModelConfig, isFluxKontextModelConfig, isFluxReduxModelConfig, - isFluxVAEModelConfig, isGemma2EncoderModelConfig, isIPAdapterModelConfig, isLLaVAModelConfig, @@ -180,11 +179,10 @@ export const selectQwenVLEncoderModels = buildModelsSelector(isQwenVLEncoderMode export const selectZImageDiffusersModels = buildModelsSelector(isZImageDiffusersMainModelConfig); export const selectFlux2DiffusersModels = buildModelsSelector(isFlux2DiffusersMainModelConfig); export const selectFlux2DevDiffusersModels = buildModelsSelector(isFlux2DevDiffusersMainModelConfig); -const selectFluxVAEModels = buildModelsSelector(isFluxVAEModelConfig); /** * FLUX.1 VAEs only. The `params.fluxVAE` and `params.zImageVaeModel` slots both hold one of these - * their pickers are built from `isFlux1VAEModelConfig`, and neither slot can use a FLUX.2 VAE - so the - * listeners that auto-fill them must draw from this pool, not from the wider `selectFluxVAEModels`. + * listeners that auto-fill them must draw from this pool, not from the wider flux+flux2 one. */ export const selectFlux1VAEModels = buildModelsSelector(isFlux1VAEModelConfig); export const selectAnimaVAEModels = buildModelsSelector(isAnimaVAEModelConfig); diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index 64fc045cc58..93589541742 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -290,13 +290,6 @@ export const isNonFluxVAEModelConfig = ( ); }; -export const isFluxVAEModelConfig = (config: AnyModelConfig, excludeSubmodels?: boolean): config is VAEModelConfig => { - return ( - (config.type === 'vae' || (!excludeSubmodels && config.type === 'main' && checkSubmodels(['vae'], config))) && - (config.base === 'flux' || config.base === 'flux2') - ); -}; - export const isFlux1VAEModelConfig = (config: AnyModelConfig, excludeSubmodels?: boolean): config is VAEModelConfig => { return ( (config.type === 'vae' || (!excludeSubmodels && config.type === 'main' && checkSubmodels(['vae'], config))) && From 279a81a59d1d94dfa403cb4392d95c1fc8e43767 Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Fri, 21 Aug 2026 22:23:13 +0200 Subject: [PATCH 11/12] fix(ui): gate Qwen3 encoder recall on the variant, not just the type Qwen3 encoders are split by `variant`, not by base: Anima uses the 0.6B (hidden_size 1024), Z-Image and Klein the 4B/8B (2560/4096). A ModelIdentifierField carries no variant, so all three recall handlers accepted each other's encoders and left the slot with one whose embeddings the transformer cannot consume. Resolve the full config and apply the guard each slot's picker is built from - isAnimaQwen3EncoderModelConfig for Anima, isQwen3EncoderModelConfig for Z-Image and Klein. --- .../src/features/metadata/parsing.test.tsx | 86 ++++++++++++++++--- .../web/src/features/metadata/parsing.tsx | 79 +++++++++++++---- 2 files changed, 138 insertions(+), 27 deletions(-) diff --git a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx index fee174c6193..9a845d3e5ed 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx @@ -122,7 +122,7 @@ describe('ImageMetadataHandlers — Klein recall gating', () => { describe('KleinQwen3EncoderModel', () => { it('parses metadata.qwen3_encoder when base is flux2', async () => { currentBase = 'flux2'; - nextResolved = fakeModel('qwen3_encoder', 'flux2'); + nextResolved = fakeModel('qwen3_encoder', 'flux2', { variant: 'qwen3_8b' }); const store = makeStore(); const parsed = await ImageMetadataHandlers.KleinQwen3EncoderModel.parse( @@ -136,7 +136,7 @@ describe('ImageMetadataHandlers — Klein recall gating', () => { it('rejects when base is not flux2', async () => { currentBase = 'sdxl'; - nextResolved = fakeModel('qwen3_encoder', 'flux2'); + nextResolved = fakeModel('qwen3_encoder', 'flux2', { variant: 'qwen3_8b' }); const store = makeStore(); await expect( @@ -151,7 +151,7 @@ describe('ImageMetadataHandlers — Klein recall gating', () => { // encoder would be recalled into `params.kleinQwen3EncoderModel` (review 4966712044). it.each(['z-image', 'anima'])('rejects %s image metadata while FLUX.2 is selected', async (base) => { currentBase = 'flux2'; - nextResolved = fakeModel('qwen3_encoder', base); + nextResolved = fakeModel('qwen3_encoder', base, { variant: 'qwen3_8b' }); const store = makeStore(); await expect( @@ -161,6 +161,21 @@ describe('ImageMetadataHandlers — Klein recall gating', () => { ) ).rejects.toThrow(); }); + + // Klein uses the 4B/8B encoders; Anima's 0.6B produces 1024-wide embeddings it cannot consume. The + // split lives in `variant`, which the identifier does not carry (review 4997022178). + it('rejects Animas 0.6B encoder', async () => { + currentBase = 'flux2'; + nextResolved = fakeModel('qwen3_encoder', 'flux2', { variant: 'qwen3_06b' }); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.KleinQwen3EncoderModel.parse( + { qwen3_encoder: nextResolved, model: fakeMainModel('klein_9b') }, + store + ) + ).rejects.toThrow(); + }); }); describe('Flux2DevMistralEncoderModel', () => { @@ -698,12 +713,19 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () }); }); + // Qwen3 encoders are split by `variant`, not by base: Anima's 0.6B produces 1024-wide embeddings, the + // 4B (2560) and 8B (4096) ones Z-Image and Klein use do not. The identifier carries no variant, so a + // handler asserting only on `type` accepts a sibling whose embeddings the transformer cannot consume + // (review 4997022178). + const animaEncoder = () => fakeModel('qwen3_encoder', 'any', { variant: 'qwen3_06b' }); + const largeEncoder = (variant: 'qwen3_4b' | 'qwen3_8b' = 'qwen3_4b') => + fakeModel('qwen3_encoder', 'any', { variant }); + describe('AnimaQwen3EncoderModel', () => { - // Anima encoders are identified by variant (qwen3_06b), not by base — a base assert here would be - // wrong. Provenance plus the current base carry the whole gate. + // Anima encoders are identified by variant, not by base - a base assert here would be wrong. it('parses regardless of the encoder base', async () => { currentBase = 'anima'; - nextResolved = fakeModel('qwen3_encoder', 'any'); + nextResolved = animaEncoder(); const store = makeStore(); const parsed = await ImageMetadataHandlers.AnimaQwen3EncoderModel.parse( @@ -715,11 +737,39 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () expect(parsed.type).toBe('qwen3_encoder'); }); - it.each(['z-image', 'flux2'])('rejects %s image metadata, which writes the same field', async (base) => { + // The Anima picker offers 0.6B encoders only. A workflow-built Anima image can nonetheless record a + // 4B/8B one, and recalling it leaves the slot with an encoder Anima cannot use. + it.each(['qwen3_4b', 'qwen3_8b'] as const)('rejects a %s encoder', async (variant) => { + currentBase = 'anima'; + nextResolved = largeEncoder(variant); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.AnimaQwen3EncoderModel.parse( + { model: fakeMain('anima'), qwen3_encoder: nextResolved }, + store + ) + ).rejects.toThrow(); + }); + + it('rejects an encoder whose variant is unknown', async () => { currentBase = 'anima'; nextResolved = fakeModel('qwen3_encoder', 'any'); const store = makeStore(); + await expect( + ImageMetadataHandlers.AnimaQwen3EncoderModel.parse( + { model: fakeMain('anima'), qwen3_encoder: nextResolved }, + store + ) + ).rejects.toThrow(); + }); + + it.each(['z-image', 'flux2'])('rejects %s image metadata, which writes the same field', async (base) => { + currentBase = 'anima'; + nextResolved = animaEncoder(); + const store = makeStore(); + await expect( ImageMetadataHandlers.AnimaQwen3EncoderModel.parse( { model: fakeMain(base), qwen3_encoder: nextResolved }, @@ -730,7 +780,7 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () it('rejects when the current base is not Anima', async () => { currentBase = 'z-image'; - nextResolved = fakeModel('qwen3_encoder', 'any'); + nextResolved = animaEncoder(); const store = makeStore(); await expect( @@ -745,7 +795,7 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () describe('ZImageQwen3EncoderModel', () => { it('parses a Z-Image image while Z-Image is selected', async () => { currentBase = 'z-image'; - nextResolved = fakeModel('qwen3_encoder', 'any'); + nextResolved = largeEncoder(); const store = makeStore(); const parsed = await ImageMetadataHandlers.ZImageQwen3EncoderModel.parse( @@ -756,11 +806,25 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () expect(parsed.key).toBe('qwen3_encoder-key'); }); + // The mirror of the Anima case: Z-Image cannot consume 1024-wide embeddings either. + it('rejects Animas 0.6B encoder', async () => { + currentBase = 'z-image'; + nextResolved = animaEncoder(); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.ZImageQwen3EncoderModel.parse( + { model: fakeMain('z-image'), qwen3_encoder: nextResolved }, + store + ) + ).rejects.toThrow(); + }); + // This handler recalls into the Z-Image slots (and nulls zImageQwen3SourceModel). Anima and FLUX.2 // Klein write the same metadata field, so without the base gate they would clobber those slots. it.each(['anima', 'flux2'])('rejects when the current base is %s', async (base) => { currentBase = base; - nextResolved = fakeModel('qwen3_encoder', 'any'); + nextResolved = largeEncoder(); const store = makeStore(); await expect( @@ -775,7 +839,7 @@ describe('ImageMetadataHandlers — Anima / Z-Image / FLUX.1 recall gating', () // must not populate the Z-Image encoder slot nor clear the source slot (review 4966712044). it.each(['anima', 'flux2'])('rejects %s image metadata while Z-Image is selected', async (base) => { currentBase = 'z-image'; - nextResolved = fakeModel('qwen3_encoder', 'any'); + nextResolved = largeEncoder(); const store = makeStore(); await expect( diff --git a/invokeai/frontend/web/src/features/metadata/parsing.tsx b/invokeai/frontend/web/src/features/metadata/parsing.tsx index 0ba7241e5c6..9d569ef4827 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.tsx @@ -154,7 +154,13 @@ import { useTranslation } from 'react-i18next'; import { imagesApi } from 'services/api/endpoints/images'; import { modelsApi } from 'services/api/endpoints/models'; import type { AnyModelConfig } from 'services/api/types'; -import { isAnimaCompatibleVAEModelConfig, isFlux1VAEModelConfig, isFlux2VAEModelConfig } from 'services/api/types'; +import { + isAnimaCompatibleVAEModelConfig, + isAnimaQwen3EncoderModelConfig, + isFlux1VAEModelConfig, + isFlux2VAEModelConfig, + isQwen3EncoderModelConfig, +} from 'services/api/types'; import { assert } from 'tsafe'; import z from 'zod'; @@ -1576,12 +1582,18 @@ const ZImageQwen3EncoderModel: SingleMetadataHandler = { // Check provenance: `qwen3_encoder` is also written by Anima and FLUX.2 Klein, and this handler // clears `zImageQwen3SourceModel` on recall (review 4966712044). assertMetadataModelBase(metadata, 'z-image', 'ZImageQwen3EncoderModel'); - const raw = getProperty(metadata, 'qwen3_encoder'); - const parsed = await parseModelIdentifier(raw, store, 'qwen3_encoder'); - assert(parsed.type === 'qwen3_encoder'); - // `qwen3_encoder` is a shared field: Z-Image, Anima and FLUX.2 Klein all write it, each into a - // different slot. The encoder itself cannot disambiguate them (Klein and Z-Image encoders both - // satisfy isQwen3EncoderModelConfig), so gate on the currently selected base instead. + // The picker's domain (`useQwen3EncoderModels`): the 4B/8B encoders, i.e. everything except Anima's + // 0.6B, whose 1024-wide embeddings Z-Image cannot consume. That split lives in `variant`, so the + // full config is needed - the identifier alone cannot tell the two apart. + const parsed = await parseModelIdentifierMatching({ + raw: getProperty(metadata, 'qwen3_encoder'), + store, + type: 'qwen3_encoder', + isCompatible: isQwen3EncoderModelConfig, + handlerType: 'ZImageQwen3EncoderModel', + }); + // Klein and Z-Image encoders both satisfy isQwen3EncoderModelConfig, so the variant cannot separate + // those two - the currently selected base does. const base = selectBase(store.getState()); assert(base === 'z-image', 'ZImageQwen3EncoderModel handler only works with Z-Image models'); return Promise.resolve(parsed); @@ -1904,12 +1916,17 @@ const AnimaQwen3EncoderModel: SingleMetadataHandler = { parse: async (metadata, store) => { // Check provenance: `qwen3_encoder` is also written by Z-Image and FLUX.2 Klein. assertMetadataModelBase(metadata, 'anima', 'AnimaQwen3EncoderModel'); - const raw = getProperty(metadata, 'qwen3_encoder'); - const parsed = await parseModelIdentifier(raw, store, 'qwen3_encoder'); - assert(parsed.type === 'qwen3_encoder'); - // Deliberately no `parsed.base` assert: Anima encoders are identified by `variant` (qwen3_06b), not - // by base - see isAnimaQwen3EncoderModelConfig. The provenance check above already guarantees the - // value came out of buildAnimaGraph, i.e. out of selectAnimaQwen3EncoderModels. + // Deliberately no `base` assert - Anima encoders are identified by `variant`, not by base - but the + // variant itself must be checked, which needs the full config: Anima's text encoder produces + // 1024-wide embeddings, and a 4B (2560) or 8B (4096) encoder recalled into this slot fails the next + // generation. This is the picker's own domain (`useAnimaQwen3EncoderModels`). + const parsed = await parseModelIdentifierMatching({ + raw: getProperty(metadata, 'qwen3_encoder'), + store, + type: 'qwen3_encoder', + isCompatible: isAnimaQwen3EncoderModelConfig, + handlerType: 'AnimaQwen3EncoderModel', + }); const base = selectBase(store.getState()); assert(base === 'anima', 'AnimaQwen3EncoderModel handler only works with Anima models'); return Promise.resolve(parsed); @@ -1969,9 +1986,14 @@ const KleinQwen3EncoderModel: SingleMetadataHandler = { // so provenance decides, not just the field being present. FLUX.2 [dev] never writes it, so one // flux2 check covers both variants. assertMetadataModelBase(metadata, 'flux2', 'KleinQwen3EncoderModel'); - const raw = getProperty(metadata, 'qwen3_encoder'); - const parsed = await parseModelIdentifier(raw, store, 'qwen3_encoder'); - assert(parsed.type === 'qwen3_encoder'); + // Same domain as the picker (`useQwen3EncoderModels`) - Klein 4B/9B, never Anima's 0.6B. + const parsed = await parseModelIdentifierMatching({ + raw: getProperty(metadata, 'qwen3_encoder'), + store, + type: 'qwen3_encoder', + isCompatible: isQwen3EncoderModelConfig, + handlerType: 'KleinQwen3EncoderModel', + }); const base = selectBase(store.getState()); assert(base === 'flux2', 'KleinQwen3EncoderModel handler only works with FLUX.2 Klein models'); return Promise.resolve(parsed); @@ -2839,6 +2861,31 @@ const parseModelIdentifier = async (raw: unknown, store: AppStore, type: ModelTy return zModelIdentifierField.parse(await parseModelConfig(raw, store, type)); }; +/** + * Resolve a metadata model reference and gate it on the *full config*, not on the identifier. + * + * A `ModelIdentifierField` carries only key/hash/name/base/type, so a handler asserting on it accepts + * every sibling of the right type. Where a slot's domain is defined by `variant` - Qwen3 encoders split + * into Anima's 0.6B (hidden_size 1024) and the 4B/8B ones Z-Image and Klein use - that is not enough: + * recalling the wrong variant fills the slot with an encoder whose embeddings the transformer cannot + * consume (review 4997022178). + * + * @param isCompatible the slot's own domain, i.e. the same guard its picker is built from. + * @param handlerType used in the assertion message, so a rejected row is traceable to its handler. + */ +const parseModelIdentifierMatching = async (arg: { + raw: unknown; + store: AppStore; + type: ModelType; + isCompatible: (config: AnyModelConfig) => boolean; + handlerType: string; +}): Promise => { + const { raw, store, type, isCompatible, handlerType } = arg; + const config = await parseModelConfig(raw, store, type); + assert(isCompatible(config), `${handlerType} requires a model this slot can hold`); + return zModelIdentifierField.parse(config); +}; + /** * Resolve a `vae` metadata reference to the identifier a VAE slot should be filled with. * From 9eb2c081cebfe6bc741c7df200ef48d4f63caa27 Mon Sep 17 00:00:00 2001 From: Alexander Eichhorn Date: Sat, 22 Aug 2026 18:48:13 +0200 Subject: [PATCH 12/12] fix(ui): validate Anima component slots and gate VAE recall on provenance Nothing reconciled params.animaVaeModel / params.animaQwen3EncoderModel when the model list changed, so uninstalling a selected component left a dangling key that only surfaced as a failed generation - both slots are required by buildAnimaGraph. Add handleAnimaComponents, built on a pure getAnimaComponentUpdates mirroring the Krea-2 sync, with the same native-over-fallback preference as the modelSelected listener. A compatible FLUX/Wan VAE survives a refetch rather than being swapped for the native one. The generic VAEModel handler also gated on the selected base alone, so an image from a dedicated-slot base viewed with no main model selected fell through to it and offered a recall into the dead params.vae. Gate on the image's own base too. --- .../listeners/animaComponentSync.test.ts | 102 ++++++++++++++++++ .../listeners/animaComponentSync.ts | 49 +++++++++ .../listeners/modelsLoaded.anima.test.ts | 100 +++++++++++++++++ .../listeners/modelsLoaded.ts | 29 +++++ .../src/features/metadata/parsing.test.tsx | 52 +++++++++ .../web/src/features/metadata/parsing.tsx | 16 +++ 6 files changed, 348 insertions(+) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/animaComponentSync.test.ts create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/animaComponentSync.ts create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.anima.test.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/animaComponentSync.test.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/animaComponentSync.test.ts new file mode 100644 index 00000000000..c67aa091ae7 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/animaComponentSync.test.ts @@ -0,0 +1,102 @@ +import type { ModelIdentifierField } from 'features/nodes/types/common'; +import { describe, expect, it } from 'vitest'; + +import { getAnimaComponentUpdates } from './animaComponentSync'; + +/** + * Nothing validated `params.animaVaeModel` / `params.animaQwen3EncoderModel` when the model list + * changed, so uninstalling a selected component left a dangling key in state. Both are required by + * buildAnimaGraph, so the failure only surfaced at generation time (review 4998711432). + */ + +const model = (key: string, base = 'anima'): ModelIdentifierField => + ({ key, hash: `${key}-hash`, name: key, base, type: 'vae' }) as ModelIdentifierField; + +const animaVae = model('anima-vae'); +const fluxVae = model('flux-vae', 'flux'); +const encoder = model('qwen3-06b', 'any'); + +const base = { + selectedVae: null, + selectedEncoder: null, + nativeVaes: [animaVae], + compatibleVaes: [animaVae, fluxVae], + availableEncoders: [encoder], +}; + +describe('getAnimaComponentUpdates', () => { + it('leaves an available selection alone', () => { + expect(getAnimaComponentUpdates({ ...base, selectedVae: animaVae, selectedEncoder: encoder })).toEqual({}); + }); + + it('replaces a VAE that is no longer installed', () => { + const updates = getAnimaComponentUpdates({ + ...base, + selectedVae: model('uninstalled-vae'), + selectedEncoder: encoder, + }); + + expect(updates).toEqual({ vae: animaVae }); + }); + + it('clears a VAE that is no longer installed when nothing can replace it', () => { + const updates = getAnimaComponentUpdates({ + ...base, + selectedVae: model('uninstalled-vae'), + selectedEncoder: encoder, + nativeVaes: [], + compatibleVaes: [], + }); + + expect(updates).toEqual({ vae: null }); + }); + + it('replaces an encoder that is no longer installed', () => { + const updates = getAnimaComponentUpdates({ + ...base, + selectedVae: animaVae, + selectedEncoder: model('uninstalled-encoder', 'any'), + }); + + expect(updates).toEqual({ encoder }); + }); + + it('clears an encoder that is no longer installed when nothing can replace it', () => { + const updates = getAnimaComponentUpdates({ + ...base, + selectedVae: animaVae, + selectedEncoder: model('uninstalled-encoder', 'any'), + availableEncoders: [], + }); + + expect(updates).toEqual({ encoder: null }); + }); + + // A FLUX VAE is a valid Anima selection, so it must survive a refetch rather than being swapped for + // the native one on every model-list update. + it('keeps a compatible fallback VAE that is still installed', () => { + expect(getAnimaComponentUpdates({ ...base, selectedVae: fluxVae, selectedEncoder: encoder })).toEqual({}); + }); + + // ...but when it has to pick, it picks the native one - same preference as the modelSelected listener. + it('prefers a native VAE over a compatible fallback that sorts first', () => { + const updates = getAnimaComponentUpdates({ + ...base, + selectedVae: model('uninstalled-vae'), + selectedEncoder: encoder, + compatibleVaes: [fluxVae, animaVae], + }); + + expect(updates).toEqual({ vae: animaVae }); + }); + + it('fills an empty slot when a component is available', () => { + expect(getAnimaComponentUpdates(base)).toEqual({ vae: animaVae, encoder }); + }); + + it('dispatches nothing when a slot is empty and nothing is installed', () => { + expect(getAnimaComponentUpdates({ ...base, nativeVaes: [], compatibleVaes: [], availableEncoders: [] })).toEqual( + {} + ); + }); +}); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/animaComponentSync.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/animaComponentSync.ts new file mode 100644 index 00000000000..999557f987b --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/animaComponentSync.ts @@ -0,0 +1,49 @@ +import type { ModelIdentifierField } from 'features/nodes/types/common'; + +type AnimaComponentSyncArg = { + selectedVae: ModelIdentifierField | null; + selectedEncoder: ModelIdentifierField | null; + /** Anima-base VAEs (Wan 2.1 / QwenImage) - what the model was trained with. */ + nativeVaes: ModelIdentifierField[]; + /** Everything the Anima loader accepts, i.e. the native ones plus FLUX and 16-channel Wan VAEs. */ + compatibleVaes: ModelIdentifierField[]; + /** Qwen3 0.6B encoders - the only variant whose 1024-wide embeddings Anima can consume. */ + availableEncoders: ModelIdentifierField[]; +}; + +type AnimaComponentUpdates = { + vae?: ModelIdentifierField | null; + encoder?: ModelIdentifierField | null; +}; + +/** + * Reconcile the Anima standalone-component slots against the installed models. + * + * Anima keeps its VAE and Qwen3 encoder in dedicated params slots, and nothing validated them when the + * model list changed: uninstalling the selected VAE left a dangling key in state that only surfaced as + * a failed generation. Both slots are required by `buildAnimaGraph`, so a dangling one is not merely + * cosmetic. + * + * Returns only the keys that need dispatching, so an unchanged slot causes no dispatch at all. + */ +export const getAnimaComponentUpdates = (arg: AnimaComponentSyncArg): AnimaComponentUpdates => { + const { selectedVae, selectedEncoder, nativeVaes, compatibleVaes, availableEncoders } = arg; + + // Same preference as the modelSelected listener: a native Anima VAE beats a FLUX/Wan fallback, which + // the loader merely tolerates and which decodes on a different code path. + const defaultVae = nativeVaes[0] ?? compatibleVaes[0]; + const defaultEncoder = availableEncoders[0]; + + const hasSelectedVae = selectedVae !== null && selectedVae !== undefined; + const hasSelectedEncoder = selectedEncoder !== null && selectedEncoder !== undefined; + const selectedVaeIsAvailable = hasSelectedVae && compatibleVaes.some((vae) => vae.key === selectedVae.key); + const selectedEncoderIsAvailable = + hasSelectedEncoder && availableEncoders.some((encoder) => encoder.key === selectedEncoder.key); + + return { + ...(!selectedVaeIsAvailable && (hasSelectedVae || defaultVae) ? { vae: defaultVae ?? null } : {}), + ...(!selectedEncoderIsAvailable && (hasSelectedEncoder || defaultEncoder) + ? { encoder: defaultEncoder ?? null } + : {}), + }; +}; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.anima.test.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.anima.test.ts new file mode 100644 index 00000000000..42cabac1ee7 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.anima.test.ts @@ -0,0 +1,100 @@ +import type { AppDispatch, RootState } from 'app/store/store'; +import { animaQwen3EncoderModelSelected, animaVaeModelSelected } from 'features/controlLayers/store/paramsSlice'; +import type { Logger } from 'roarr'; +import type { AnyModelConfig } from 'services/api/types'; +import type { JsonObject } from 'type-fest'; +import { describe, expect, it, vi } from 'vitest'; + +import { handleAnimaComponents } from './modelsLoaded'; + +/** + * Wiring around `getAnimaComponentUpdates` (covered on its own in animaComponentSync.test.ts): the base + * guard, and that a dangling selection is actually dispatched away. Both Anima component slots are + * required by buildAnimaGraph, so a stale one only surfaced as a failed generation (review 4998711432). + */ + +const animaVae = { + key: 'anima-vae', + hash: 'h', + name: 'Wan 2.1 VAE', + base: 'anima', + type: 'vae', + format: 'checkpoint', +} as unknown as AnyModelConfig; + +const qwen3Encoder = { + key: 'qwen3-06b', + hash: 'h', + name: 'Qwen3 0.6B', + base: 'any', + type: 'qwen3_encoder', + variant: 'qwen3_06b', + format: 'checkpoint', +} as unknown as AnyModelConfig; + +const uninstalled = { key: 'gone', hash: 'h', name: 'Gone', base: 'anima', type: 'vae' }; + +const makeState = (params: Record) => + ({ + params: { + model: { key: 'anima-main', hash: 'h', name: 'Anima', base: 'anima', type: 'main' }, + animaVaeModel: null, + animaQwen3EncoderModel: null, + ...params, + }, + }) as unknown as RootState; + +const log = { debug: vi.fn() } as unknown as Logger; + +const run = (models: AnyModelConfig[], state: RootState) => { + const dispatch = vi.fn() as unknown as AppDispatch; + handleAnimaComponents(models, state, dispatch, log); + return dispatch as unknown as ReturnType; +}; + +describe('handleAnimaComponents', () => { + it('does nothing while another base is selected', () => { + const state = makeState({ + model: { key: 'sdxl', hash: 'h', name: 'SDXL', base: 'sdxl', type: 'main' }, + animaVaeModel: uninstalled, + }); + + expect(run([], state)).not.toHaveBeenCalled(); + }); + + it('clears a selected VAE that is no longer installed', () => { + const dispatch = run( + [qwen3Encoder], + makeState({ animaVaeModel: uninstalled, animaQwen3EncoderModel: qwen3Encoder }) + ); + + expect(dispatch).toHaveBeenCalledWith(animaVaeModelSelected(null)); + }); + + it('replaces a selected VAE that is no longer installed when one is available', () => { + const dispatch = run( + [animaVae, qwen3Encoder], + makeState({ animaVaeModel: uninstalled, animaQwen3EncoderModel: qwen3Encoder }) + ); + + expect(dispatch).toHaveBeenCalledWith(animaVaeModelSelected(expect.objectContaining({ key: animaVae.key }))); + }); + + it('fills both empty slots from the installed models', () => { + const dispatch = run([animaVae, qwen3Encoder], makeState({})); + + expect(dispatch).toHaveBeenCalledWith(animaVaeModelSelected(expect.objectContaining({ key: animaVae.key }))); + expect(dispatch).toHaveBeenCalledWith( + animaQwen3EncoderModelSelected(expect.objectContaining({ key: qwen3Encoder.key })) + ); + }); + + it('leaves an intact configuration alone', () => { + const dispatch = run( + [animaVae, qwen3Encoder], + makeState({ animaVaeModel: animaVae, animaQwen3EncoderModel: qwen3Encoder }) + ); + + expect(dispatch).not.toHaveBeenCalled(); + }); +}); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts index ff7b961f096..319e7ce01f6 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts @@ -3,6 +3,8 @@ import type { AppDispatch, AppStartListening, RootState } from 'app/store/store' import { controlLayerModelChanged, rgRefImageModelChanged } from 'features/controlLayers/store/canvasSlice'; import { loraDeleted } from 'features/controlLayers/store/lorasSlice'; import { + animaQwen3EncoderModelSelected, + animaVaeModelSelected, clipEmbedModelSelected, fluxVAESelected, krea2Qwen3VlEncoderModelSelected, @@ -38,6 +40,8 @@ import type { Logger } from 'roarr'; import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models'; import type { AnyModelConfig } from 'services/api/types'; import { + isAnimaCompatibleVAEModelConfig, + isAnimaQwen3EncoderModelConfig, isAnimaVAEModelConfig, isCLIPEmbedModelConfigOrSubmodel, isControlLayerModelConfig, @@ -57,6 +61,7 @@ import { } from 'services/api/types'; import type { JsonObject } from 'type-fest'; +import { getAnimaComponentUpdates } from './animaComponentSync'; import { getKrea2ComponentUpdates } from './krea2ComponentSync'; const log = logger('models'); @@ -85,6 +90,7 @@ export const addModelsLoadedListener = (startAppListening: AppStartListening) => handleMainModels(models, state, dispatch, log); handleKrea2Components(models, state, dispatch, log); + handleAnimaComponents(models, state, dispatch, log); handleRefinerModels(models, state, dispatch, log); handleVAEModels(models, state, dispatch, log); handleLoRAModels(models, state, dispatch, log); @@ -126,6 +132,29 @@ export const handleKrea2Components: ModelHandler = (models, state, dispatch) => } }; +export const handleAnimaComponents: ModelHandler = (models, state, dispatch) => { + // Only reconcile while Anima is the selected base. Switching away nulls both slots (see the + // modelSelected listener), so there is nothing to validate then - but a session restored with Anima + // selected and the VAE since uninstalled lands here with a dangling key. + if (state.params.model?.base !== 'anima') { + return; + } + + const updates = getAnimaComponentUpdates({ + selectedVae: state.params.animaVaeModel, + selectedEncoder: state.params.animaQwen3EncoderModel, + nativeVaes: models.filter((model) => isAnimaVAEModelConfig(model)), + compatibleVaes: models.filter((model) => isAnimaCompatibleVAEModelConfig(model)), + availableEncoders: models.filter((model) => isAnimaQwen3EncoderModelConfig(model)), + }); + if ('vae' in updates) { + dispatch(animaVaeModelSelected(updates.vae ? zModelIdentifierField.parse(updates.vae) : null)); + } + if ('encoder' in updates) { + dispatch(animaQwen3EncoderModelSelected(updates.encoder ? zModelIdentifierField.parse(updates.encoder) : null)); + } +}; + type ModelHandler = ( models: AnyModelConfig[], state: RootState, diff --git a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx index 9a845d3e5ed..895944c38ca 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.test.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.test.tsx @@ -228,6 +228,58 @@ describe('ImageMetadataHandlers — Klein recall gating', () => { const parsed = await ImageMetadataHandlers.VAEModel.parse({ vae: nextResolved }, store); expect(parsed.key).toBe('vae-key'); }); + + // The selected base alone is not enough. With no main model selected - startup, or after the last + // one is uninstalled - `selectBase` is null and every dedicated handler bows out, so an Anima image + // fell through to here and offered a recall into the (for Anima dead) `params.vae`. Review + // 4998711432. + it.each(['flux', 'flux2', 'z-image', 'krea-2', 'anima'])( + 'rejects a %s image even when no main model is selected', + async (imageBase) => { + currentBase = null; + nextResolved = fakeModel('vae', imageBase); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.VAEModel.parse( + { + model: { key: 'main-key', hash: 'h', name: imageBase, base: imageBase, type: 'main' }, + vae: nextResolved, + }, + store + ) + ).rejects.toThrow(); + } + ); + + // ...but an image from a base that genuinely uses the shared slot still recalls, which is the whole + // reason the null-base case is allowed through at all. + it('parses an SDXL image when no main model is selected', async () => { + currentBase = null; + nextResolved = fakeModel('vae', 'sdxl'); + const store = makeStore(); + + const parsed = await ImageMetadataHandlers.VAEModel.parse( + { model: { key: 'main-key', hash: 'h', name: 'sdxl', base: 'sdxl', type: 'main' }, vae: nextResolved }, + store + ); + + expect(parsed.key).toBe('vae-key'); + }); + + // An Anima image must not claim the shared slot while SDXL happens to be selected either. + it('rejects an Anima image while SDXL is selected', async () => { + currentBase = 'sdxl'; + nextResolved = fakeModel('vae', 'anima'); + const store = makeStore(); + + await expect( + ImageMetadataHandlers.VAEModel.parse( + { model: { key: 'main-key', hash: 'h', name: 'anima', base: 'anima', type: 'main' }, vae: nextResolved }, + store + ) + ).rejects.toThrow(); + }); }); describe('T5EncoderModel', () => { diff --git a/invokeai/frontend/web/src/features/metadata/parsing.tsx b/invokeai/frontend/web/src/features/metadata/parsing.tsx index 9d569ef4827..a653afc10b8 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.tsx @@ -187,6 +187,12 @@ const getProperty = (obj: unknown, path: string): unknown => { return get(obj, path) as unknown; }; +const getMetadataModelBase = (metadata: unknown): string | undefined => { + const rawModel = getProperty(metadata, 'model'); + const modelBase = (rawModel as { base?: unknown } | undefined)?.base; + return isString(modelBase) ? modelBase : undefined; +}; + const assertMetadataModelBase = (metadata: unknown, expectedBase: string, handlerType: string): void => { const rawModel = getProperty(metadata, 'model'); const modelBase = (rawModel as { base?: unknown } | undefined)?.base; @@ -1519,6 +1525,16 @@ const VAEModel: SingleMetadataHandler = { const parsed = await parseModelIdentifier(raw, store, 'vae'); assert(parsed.type === 'vae'); assert(isCompatibleWithMainModel(parsed, store)); + // Two axes, because either one alone leaves a hole. The selected base decides which slot is live; + // the image's own base decides which handler owns the row. Without the provenance half, an Anima + // image viewed with no main model selected (`base` null, startup or after the last model is + // uninstalled) falls through to here and offers a recall into the - for Anima dead - `params.vae` + // (review 4998711432). + const metadataBase = getMetadataModelBase(metadata); + assert( + !metadataBase || !BASES_WITH_DEDICATED_VAE_HANDLER.has(metadataBase as BaseModelType), + `VAEModel handler does not apply to "${metadataBase}" images - that base has a dedicated VAE handler` + ); const base = selectBase(store.getState()); assert( !base || !BASES_WITH_DEDICATED_VAE_HANDLER.has(base),