Skip to content

Commit b7e28e4

Browse files
fix(ui): make Z-Image model selects mutually exclusive (#8717)
* fix(ui): make Z-Image model selects mutually exclusive VAE and Qwen3 Encoder selects are disabled when Qwen3 Source is selected, and vice versa. This prevents invalid model combinations. * feat(ui): auto-select Z-Image component models on model change When switching to a Z-Image model, automatically set valid defaults if no configuration exists: - Prefers Qwen3 Source (Diffusers model) if available - Falls back to Qwen3 Encoder + FLUX VAE combination This ensures the generate button is enabled immediately after selecting a Z-Image model, without requiring manual configuration. * fix(ui): save and restore Qwen3 Source model in metadata Qwen3 Source (Diffusers Z-Image) model was not being saved to image metadata or restored during Remix. This adds: - Saving qwen3_source to metadata in buildZImageGraph - ZImageQwen3SourceModel metadata handler for parsing and recall - i18n translation for qwen3Source
1 parent d7d0512 commit b7e28e4

File tree

6 files changed

+113
-4
lines changed

6 files changed

+113
-4
lines changed

invokeai/frontend/web/public/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,7 @@
850850
"parsingFailed": "Parsing Failed",
851851
"positivePrompt": "Positive Prompt",
852852
"qwen3Encoder": "Qwen3 Encoder",
853+
"qwen3Source": "Qwen3 Source",
853854
"recallParameters": "Recall Parameters",
854855
"recallParameter": "Recall {{label}}",
855856
"scheduler": "Scheduler",

invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@ import { modelSelected } from 'features/parameters/store/actions';
2323
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
2424
import { toast } from 'features/toast/toast';
2525
import { t } from 'i18next';
26-
import { selectGlobalRefImageModels, selectRegionalRefImageModels } from 'services/api/hooks/modelsByType';
26+
import {
27+
selectFluxVAEModels,
28+
selectGlobalRefImageModels,
29+
selectQwen3EncoderModels,
30+
selectRegionalRefImageModels,
31+
selectZImageDiffusersModels,
32+
} from 'services/api/hooks/modelsByType';
2733
import type { FLUXKontextModelConfig, FLUXReduxModelConfig, IPAdapterModelConfig } from 'services/api/types';
2834
import { isFluxKontextModelConfig, isFluxReduxModelConfig } from 'services/api/types';
2935

@@ -79,6 +85,59 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
7985
dispatch(zImageQwen3SourceModelSelected(null));
8086
modelsUpdatedDisabledOrCleared += 1;
8187
}
88+
} else {
89+
// Switching to Z-Image - set defaults if no valid configuration exists
90+
const hasValidConfig = zImageQwen3SourceModel || (zImageVaeModel && zImageQwen3EncoderModel);
91+
92+
if (!hasValidConfig) {
93+
// Prefer Qwen3 Source (Diffusers model) if available
94+
const availableZImageDiffusers = selectZImageDiffusersModels(state);
95+
96+
if (availableZImageDiffusers.length > 0) {
97+
const diffusersModel = availableZImageDiffusers[0];
98+
if (diffusersModel) {
99+
dispatch(
100+
zImageQwen3SourceModelSelected({
101+
key: diffusersModel.key,
102+
hash: diffusersModel.hash,
103+
name: diffusersModel.name,
104+
base: diffusersModel.base,
105+
type: diffusersModel.type,
106+
})
107+
);
108+
}
109+
} else {
110+
// Fallback: try to set Qwen3 Encoder + VAE
111+
const availableQwen3Encoders = selectQwen3EncoderModels(state);
112+
const availableFluxVAEs = selectFluxVAEModels(state);
113+
114+
if (availableQwen3Encoders.length > 0 && availableFluxVAEs.length > 0) {
115+
const qwen3Encoder = availableQwen3Encoders[0];
116+
const fluxVAE = availableFluxVAEs[0];
117+
118+
if (qwen3Encoder) {
119+
dispatch(
120+
zImageQwen3EncoderModelSelected({
121+
key: qwen3Encoder.key,
122+
name: qwen3Encoder.name,
123+
base: qwen3Encoder.base,
124+
})
125+
);
126+
}
127+
if (fluxVAE) {
128+
dispatch(
129+
zImageVaeModelSelected({
130+
key: fluxVAE.key,
131+
hash: fluxVAE.hash,
132+
name: fluxVAE.name,
133+
base: fluxVAE.base,
134+
type: fluxVAE.type,
135+
})
136+
);
137+
}
138+
}
139+
}
140+
}
82141
}
83142

84143
if (SUPPORTS_REF_IMAGES_BASE_MODELS.includes(newModel.base)) {

invokeai/frontend/web/src/features/metadata/parsing.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
vaeSelected,
3333
widthChanged,
3434
zImageQwen3EncoderModelSelected,
35+
zImageQwen3SourceModelSelected,
3536
zImageVaeModelSelected,
3637
} from 'features/controlLayers/store/paramsSlice';
3738
import { refImagesRecalled } from 'features/controlLayers/store/refImagesSlice';
@@ -741,6 +742,30 @@ const ZImageVAEModel: SingleMetadataHandler<ModelIdentifierField> = {
741742
};
742743
//#endregion ZImageVAEModel
743744

745+
//#region ZImageQwen3SourceModel
746+
const ZImageQwen3SourceModel: SingleMetadataHandler<ModelIdentifierField> = {
747+
[SingleMetadataKey]: true,
748+
type: 'ZImageQwen3SourceModel',
749+
parse: async (metadata, store) => {
750+
const raw = getProperty(metadata, 'qwen3_source');
751+
const parsed = await parseModelIdentifier(raw, store, 'main');
752+
assert(parsed.type === 'main');
753+
// Only recall if the current main model is Z-Image
754+
const base = selectBase(store.getState());
755+
assert(base === 'z-image', 'ZImageQwen3SourceModel handler only works with Z-Image models');
756+
return Promise.resolve(parsed);
757+
},
758+
recall: (value, store) => {
759+
store.dispatch(zImageQwen3SourceModelSelected(value));
760+
},
761+
i18nKey: 'metadata.qwen3Source',
762+
LabelComponent: MetadataLabel,
763+
ValueComponent: ({ value }: SingleMetadataValueProps<ModelIdentifierField>) => (
764+
<MetadataPrimitiveValue value={`${value.name} (${value.base.toUpperCase()})`} />
765+
),
766+
};
767+
//#endregion ZImageQwen3SourceModel
768+
744769
//#region LoRAs
745770
const LoRAs: CollectionMetadataHandler<LoRA[]> = {
746771
[CollectionMetadataKey]: true,
@@ -977,6 +1002,7 @@ export const ImageMetadataHandlers = {
9771002
VAEModel,
9781003
Qwen3EncoderModel,
9791004
ZImageVAEModel,
1005+
ZImageQwen3SourceModel,
9801006
LoRAs,
9811007
CanvasLayers,
9821008
RefImages,

invokeai/frontend/web/src/features/nodes/util/graph/generation/buildZImageGraph.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ export const buildZImageGraph = async (arg: GraphBuilderArg): Promise<GraphBuild
185185
steps,
186186
vae: zImageVaeModel ?? undefined,
187187
qwen3_encoder: zImageQwen3EncoderModel ?? undefined,
188+
qwen3_source: zImageQwen3SourceModel ?? undefined,
188189
});
189190
g.addEdgeToMetadata(seed, 'value', 'seed');
190191
g.addEdgeToMetadata(positivePrompt, 'value', 'positive_prompt');

invokeai/frontend/web/src/features/parameters/components/Advanced/ParamZImageQwen3VaeModelSelect.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@ import type { MainModelConfig, Qwen3EncoderModelConfig, VAEModelConfig } from 's
1717

1818
/**
1919
* Z-Image VAE Model Select - uses FLUX VAE models
20+
* Disabled when Qwen3 Source is selected (mutually exclusive)
2021
*/
2122
const ParamZImageVaeModelSelect = memo(() => {
2223
const dispatch = useAppDispatch();
2324
const { t } = useTranslation();
2425
const zImageVaeModel = useAppSelector(selectZImageVaeModel);
26+
const zImageQwen3SourceModel = useAppSelector(selectZImageQwen3SourceModel);
2527
const [modelConfigs, { isLoading }] = useFluxVAEModels();
2628

29+
// Disable when Qwen3 Source is selected
30+
const isDisabled = zImageQwen3SourceModel !== null;
31+
2732
const _onChange = useCallback(
2833
(model: VAEModelConfig | null) => {
2934
if (model) {
@@ -43,14 +48,15 @@ const ParamZImageVaeModelSelect = memo(() => {
4348
});
4449

4550
return (
46-
<FormControl minW={0} flexGrow={1} gap={2}>
51+
<FormControl minW={0} flexGrow={1} gap={2} isDisabled={isDisabled}>
4752
<FormLabel m={0}>{t('modelManager.zImageVae')}</FormLabel>
4853
<Combobox
4954
value={value}
5055
options={options}
5156
onChange={onChange}
5257
noOptionsMessage={noOptionsMessage}
5358
isClearable
59+
isDisabled={isDisabled}
5460
placeholder={t('modelManager.zImageVaePlaceholder')}
5561
/>
5662
</FormControl>
@@ -61,13 +67,18 @@ ParamZImageVaeModelSelect.displayName = 'ParamZImageVaeModelSelect';
6167

6268
/**
6369
* Z-Image Qwen3 Encoder Model Select
70+
* Disabled when Qwen3 Source is selected (mutually exclusive)
6471
*/
6572
const ParamZImageQwen3EncoderModelSelect = memo(() => {
6673
const dispatch = useAppDispatch();
6774
const { t } = useTranslation();
6875
const zImageQwen3EncoderModel = useAppSelector(selectZImageQwen3EncoderModel);
76+
const zImageQwen3SourceModel = useAppSelector(selectZImageQwen3SourceModel);
6977
const [modelConfigs, { isLoading }] = useQwen3EncoderModels();
7078

79+
// Disable when Qwen3 Source is selected
80+
const isDisabled = zImageQwen3SourceModel !== null;
81+
7182
const _onChange = useCallback(
7283
(model: Qwen3EncoderModelConfig | null) => {
7384
if (model) {
@@ -87,14 +98,15 @@ const ParamZImageQwen3EncoderModelSelect = memo(() => {
8798
});
8899

89100
return (
90-
<FormControl minW={0} flexGrow={1} gap={2}>
101+
<FormControl minW={0} flexGrow={1} gap={2} isDisabled={isDisabled}>
91102
<FormLabel m={0}>{t('modelManager.zImageQwen3Encoder')}</FormLabel>
92103
<Combobox
93104
value={value}
94105
options={options}
95106
onChange={onChange}
96107
noOptionsMessage={noOptionsMessage}
97108
isClearable
109+
isDisabled={isDisabled}
98110
placeholder={t('modelManager.zImageQwen3EncoderPlaceholder')}
99111
/>
100112
</FormControl>
@@ -105,13 +117,19 @@ ParamZImageQwen3EncoderModelSelect.displayName = 'ParamZImageQwen3EncoderModelSe
105117

106118
/**
107119
* Z-Image Qwen3 Source Model Select - Diffusers Z-Image models for fallback
120+
* Disabled when VAE or Qwen3 Encoder is selected (mutually exclusive)
108121
*/
109122
const ParamZImageQwen3SourceModelSelect = memo(() => {
110123
const dispatch = useAppDispatch();
111124
const { t } = useTranslation();
112125
const zImageQwen3SourceModel = useAppSelector(selectZImageQwen3SourceModel);
126+
const zImageVaeModel = useAppSelector(selectZImageVaeModel);
127+
const zImageQwen3EncoderModel = useAppSelector(selectZImageQwen3EncoderModel);
113128
const [modelConfigs, { isLoading }] = useZImageDiffusersModels();
114129

130+
// Disable when VAE or Qwen3 Encoder is selected
131+
const isDisabled = zImageVaeModel !== null || zImageQwen3EncoderModel !== null;
132+
115133
const _onChange = useCallback(
116134
(model: MainModelConfig | null) => {
117135
if (model) {
@@ -131,14 +149,15 @@ const ParamZImageQwen3SourceModelSelect = memo(() => {
131149
});
132150

133151
return (
134-
<FormControl minW={0} flexGrow={1} gap={2}>
152+
<FormControl minW={0} flexGrow={1} gap={2} isDisabled={isDisabled}>
135153
<FormLabel m={0}>{t('modelManager.zImageQwen3Source')}</FormLabel>
136154
<Combobox
137155
value={value}
138156
options={options}
139157
onChange={onChange}
140158
noOptionsMessage={noOptionsMessage}
141159
isClearable
160+
isDisabled={isDisabled}
142161
placeholder={t('modelManager.zImageQwen3SourcePlaceholder')}
143162
/>
144163
</FormControl>

invokeai/frontend/web/src/services/api/hooks/modelsByType.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,6 @@ export const selectGlobalRefImageModels = buildModelsSelector(
8080
export const selectRegionalRefImageModels = buildModelsSelector(
8181
(config) => isIPAdapterModelConfig(config) || isFluxReduxModelConfig(config)
8282
);
83+
export const selectQwen3EncoderModels = buildModelsSelector(isQwen3EncoderModelConfig);
84+
export const selectZImageDiffusersModels = buildModelsSelector(isZImageDiffusersMainModelConfig);
85+
export const selectFluxVAEModels = buildModelsSelector(isFluxVAEModelConfig);

0 commit comments

Comments
 (0)