Skip to content

Commit 0dcd23b

Browse files
feat(ui): add readiness checks for LoRAs
If incompatible LoRAs are added, prevent Invoking. The logic to prevent adding incompatible LoRAs to graphs already existed. This does not fix any generation bugs; just a visual inconsistency where it looks like Invoke would use an incompatible LoRA.
1 parent d6f42c7 commit 0dcd23b

File tree

2 files changed

+162
-95
lines changed

2 files changed

+162
-95
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,6 +1281,7 @@
12811281
"modelIncompatibleScaledBboxWidth": "Scaled bbox width is {{width}} but {{model}} requires multiple of {{multiple}}",
12821282
"modelIncompatibleScaledBboxHeight": "Scaled bbox height is {{height}} but {{model}} requires multiple of {{multiple}}",
12831283
"fluxModelMultipleControlLoRAs": "Can only use 1 Control LoRA at a time",
1284+
"incompatibleLoRAs": "Incompatible LoRA(s) added",
12841285
"canvasIsFiltering": "Canvas is busy (filtering)",
12851286
"canvasIsTransforming": "Canvas is busy (transforming)",
12861287
"canvasIsRasterizing": "Canvas is busy (rasterizing)",

invokeai/frontend/web/src/features/queue/store/readiness.ts

Lines changed: 161 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import type { AppConfig } from 'app/types/invokeai';
99
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
1010
import { debounce, groupBy, upperFirst } from 'es-toolkit/compat';
1111
import { useCanvasManagerSafe } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
12+
import { selectAddedLoRAs } from 'features/controlLayers/store/lorasSlice';
1213
import { selectMainModelConfig, selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
1314
import { selectRefImagesSlice } from 'features/controlLayers/store/refImagesSlice';
1415
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
15-
import type { CanvasState, ParamsState, RefImagesState } from 'features/controlLayers/store/types';
16+
import type { CanvasState, LoRA, ParamsState, RefImagesState } from 'features/controlLayers/store/types';
1617
import {
1718
getControlLayerWarnings,
1819
getGlobalReferenceImageWarnings,
@@ -73,96 +74,124 @@ export type Reason = { prefix?: string; content: string };
7374
export const $reasonsWhyCannotEnqueue = atom<Reason[]>([]);
7475
export const $isReadyToEnqueue = computed($reasonsWhyCannotEnqueue, (reasons) => reasons.length === 0);
7576

76-
const debouncedUpdateReasons = debounce(
77-
async (
78-
tab: TabName,
79-
isConnected: boolean,
80-
canvas: CanvasState,
81-
params: ParamsState,
82-
refImages: RefImagesState,
83-
dynamicPrompts: DynamicPromptsState,
84-
canvasIsFiltering: boolean,
85-
canvasIsTransforming: boolean,
86-
canvasIsRasterizing: boolean,
87-
canvasIsCompositing: boolean,
88-
canvasIsSelectingObject: boolean,
89-
nodes: NodesState,
90-
workflowSettings: WorkflowSettingsState,
91-
templates: Templates,
92-
upscale: UpscaleState,
93-
config: AppConfig,
94-
store: AppStore,
95-
isInPublishFlow: boolean,
96-
isChatGPT4oHighModelDisabled: (model: ParameterModel) => boolean,
97-
isVideoEnabled: boolean,
98-
promptExpansionRequest: PromptExpansionRequestState,
99-
video: VideoState
100-
) => {
101-
if (tab === 'generate') {
102-
const model = selectMainModelConfig(store.getState());
103-
const reasons = await getReasonsWhyCannotEnqueueGenerateTab({
104-
isConnected,
105-
model,
106-
params,
107-
refImages,
108-
dynamicPrompts,
109-
isChatGPT4oHighModelDisabled,
110-
promptExpansionRequest,
111-
});
112-
$reasonsWhyCannotEnqueue.set(reasons);
113-
} else if (tab === 'canvas') {
114-
const model = selectMainModelConfig(store.getState());
115-
const reasons = await getReasonsWhyCannotEnqueueCanvasTab({
116-
isConnected,
117-
model,
118-
canvas,
119-
params,
120-
refImages,
121-
dynamicPrompts,
122-
canvasIsFiltering,
123-
canvasIsTransforming,
124-
canvasIsRasterizing,
125-
canvasIsCompositing,
126-
canvasIsSelectingObject,
127-
isChatGPT4oHighModelDisabled,
128-
promptExpansionRequest,
129-
});
130-
$reasonsWhyCannotEnqueue.set(reasons);
131-
} else if (tab === 'workflows') {
132-
const reasons = await getReasonsWhyCannotEnqueueWorkflowsTab({
133-
dispatch: store.dispatch,
134-
nodesState: nodes,
135-
workflowSettingsState: workflowSettings,
136-
isConnected,
137-
templates,
138-
isInPublishFlow,
139-
});
140-
$reasonsWhyCannotEnqueue.set(reasons);
141-
} else if (tab === 'upscaling') {
142-
const reasons = getReasonsWhyCannotEnqueueUpscaleTab({
143-
isConnected,
144-
upscale,
145-
config,
146-
params,
147-
promptExpansionRequest,
148-
});
149-
$reasonsWhyCannotEnqueue.set(reasons);
150-
} else if (tab === 'video') {
151-
const reasons = getReasonsWhyCannotEnqueueVideoTab({
152-
isConnected,
153-
video,
154-
params,
155-
promptExpansionRequest,
156-
dynamicPrompts,
157-
isVideoEnabled,
158-
});
159-
$reasonsWhyCannotEnqueue.set(reasons);
160-
} else {
161-
$reasonsWhyCannotEnqueue.set(EMPTY_ARRAY);
162-
}
163-
},
164-
300
165-
);
77+
type UpdateReasonsArg = {
78+
tab: TabName;
79+
isConnected: boolean;
80+
canvas: CanvasState;
81+
params: ParamsState;
82+
refImages: RefImagesState;
83+
dynamicPrompts: DynamicPromptsState;
84+
canvasIsFiltering: boolean;
85+
canvasIsTransforming: boolean;
86+
canvasIsRasterizing: boolean;
87+
canvasIsCompositing: boolean;
88+
canvasIsSelectingObject: boolean;
89+
nodes: NodesState;
90+
workflowSettings: WorkflowSettingsState;
91+
templates: Templates;
92+
upscale: UpscaleState;
93+
config: AppConfig;
94+
loras: LoRA[];
95+
store: AppStore;
96+
isInPublishFlow: boolean;
97+
isChatGPT4oHighModelDisabled: (model: ParameterModel) => boolean;
98+
isVideoEnabled: boolean;
99+
promptExpansionRequest: PromptExpansionRequestState;
100+
video: VideoState;
101+
};
102+
103+
const debouncedUpdateReasons = debounce(async (arg: UpdateReasonsArg) => {
104+
const {
105+
tab,
106+
isConnected,
107+
canvas,
108+
params,
109+
refImages,
110+
dynamicPrompts,
111+
canvasIsFiltering,
112+
canvasIsTransforming,
113+
canvasIsRasterizing,
114+
canvasIsCompositing,
115+
canvasIsSelectingObject,
116+
nodes,
117+
workflowSettings,
118+
templates,
119+
upscale,
120+
config,
121+
loras,
122+
store,
123+
isInPublishFlow,
124+
isChatGPT4oHighModelDisabled,
125+
isVideoEnabled,
126+
promptExpansionRequest,
127+
video,
128+
} = arg;
129+
if (tab === 'generate') {
130+
const model = selectMainModelConfig(store.getState());
131+
const reasons = await getReasonsWhyCannotEnqueueGenerateTab({
132+
isConnected,
133+
model,
134+
params,
135+
refImages,
136+
dynamicPrompts,
137+
isChatGPT4oHighModelDisabled,
138+
promptExpansionRequest,
139+
loras,
140+
});
141+
$reasonsWhyCannotEnqueue.set(reasons);
142+
} else if (tab === 'canvas') {
143+
const model = selectMainModelConfig(store.getState());
144+
const reasons = await getReasonsWhyCannotEnqueueCanvasTab({
145+
isConnected,
146+
model,
147+
canvas,
148+
params,
149+
refImages,
150+
dynamicPrompts,
151+
canvasIsFiltering,
152+
canvasIsTransforming,
153+
canvasIsRasterizing,
154+
canvasIsCompositing,
155+
canvasIsSelectingObject,
156+
isChatGPT4oHighModelDisabled,
157+
promptExpansionRequest,
158+
loras,
159+
});
160+
$reasonsWhyCannotEnqueue.set(reasons);
161+
} else if (tab === 'workflows') {
162+
const reasons = await getReasonsWhyCannotEnqueueWorkflowsTab({
163+
dispatch: store.dispatch,
164+
nodesState: nodes,
165+
workflowSettingsState: workflowSettings,
166+
isConnected,
167+
templates,
168+
isInPublishFlow,
169+
});
170+
$reasonsWhyCannotEnqueue.set(reasons);
171+
} else if (tab === 'upscaling') {
172+
const reasons = getReasonsWhyCannotEnqueueUpscaleTab({
173+
isConnected,
174+
upscale,
175+
config,
176+
params,
177+
promptExpansionRequest,
178+
loras,
179+
});
180+
$reasonsWhyCannotEnqueue.set(reasons);
181+
} else if (tab === 'video') {
182+
const reasons = getReasonsWhyCannotEnqueueVideoTab({
183+
isConnected,
184+
video,
185+
params,
186+
promptExpansionRequest,
187+
dynamicPrompts,
188+
isVideoEnabled,
189+
});
190+
$reasonsWhyCannotEnqueue.set(reasons);
191+
} else {
192+
$reasonsWhyCannotEnqueue.set(EMPTY_ARRAY);
193+
}
194+
}, 300);
166195

167196
export const useReadinessWatcher = () => {
168197
useAssertSingleton('useReadinessWatcher');
@@ -177,6 +206,7 @@ export const useReadinessWatcher = () => {
177206
const workflowSettings = useAppSelector(selectWorkflowSettingsSlice);
178207
const upscale = useAppSelector(selectUpscaleSlice);
179208
const config = useAppSelector(selectConfigSlice);
209+
const loras = useAppSelector(selectAddedLoRAs);
180210
const templates = useStore($templates);
181211
const isConnected = useStore($isConnected);
182212
const canvasIsFiltering = useStore(canvasManager?.stateApi.$isFiltering ?? $false);
@@ -190,7 +220,7 @@ export const useReadinessWatcher = () => {
190220
const promptExpansionRequest = useStore(promptExpansionApi.$state);
191221
const video = useAppSelector(selectVideoSlice);
192222
useEffect(() => {
193-
debouncedUpdateReasons(
223+
debouncedUpdateReasons({
194224
tab,
195225
isConnected,
196226
canvas,
@@ -207,13 +237,14 @@ export const useReadinessWatcher = () => {
207237
templates,
208238
upscale,
209239
config,
240+
loras,
210241
store,
211242
isInPublishFlow,
212243
isChatGPT4oHighModelDisabled,
213244
isVideoEnabled,
214245
promptExpansionRequest,
215-
video
216-
);
246+
video,
247+
});
217248
}, [
218249
store,
219250
canvas,
@@ -232,6 +263,7 @@ export const useReadinessWatcher = () => {
232263
templates,
233264
upscale,
234265
workflowSettings,
266+
loras,
235267
isInPublishFlow,
236268
isChatGPT4oHighModelDisabled,
237269
isVideoEnabled,
@@ -289,6 +321,7 @@ const getReasonsWhyCannotEnqueueGenerateTab = (arg: {
289321
model: MainModelConfig | null | undefined;
290322
params: ParamsState;
291323
refImages: RefImagesState;
324+
loras: LoRA[];
292325
dynamicPrompts: DynamicPromptsState;
293326
isChatGPT4oHighModelDisabled: (model: ParameterModel) => boolean;
294327
promptExpansionRequest: PromptExpansionRequestState;
@@ -298,6 +331,7 @@ const getReasonsWhyCannotEnqueueGenerateTab = (arg: {
298331
model,
299332
params,
300333
refImages,
334+
loras,
301335
dynamicPrompts,
302336
isChatGPT4oHighModelDisabled,
303337
promptExpansionRequest,
@@ -333,6 +367,16 @@ const getReasonsWhyCannotEnqueueGenerateTab = (arg: {
333367
reasons.push({ content: i18n.t('parameters.invoke.modelDisabledForTrial', { modelName: model.name }) });
334368
}
335369

370+
if (model) {
371+
for (const lora of loras.filter(({ isEnabled }) => isEnabled === true)) {
372+
if (model.base !== lora.model.base) {
373+
reasons.push({ content: i18n.t('parameters.invoke.incompatibleLoRAs') });
374+
// Just add the warning once.
375+
break;
376+
}
377+
}
378+
}
379+
336380
if (promptExpansionRequest.isPending) {
337381
reasons.push({ content: i18n.t('parameters.invoke.promptExpansionPending') });
338382
} else if (promptExpansionRequest.isSuccess) {
@@ -447,9 +491,10 @@ const getReasonsWhyCannotEnqueueUpscaleTab = (arg: {
447491
upscale: UpscaleState;
448492
config: AppConfig;
449493
params: ParamsState;
494+
loras: LoRA[];
450495
promptExpansionRequest: PromptExpansionRequestState;
451496
}) => {
452-
const { isConnected, upscale, config, params, promptExpansionRequest } = arg;
497+
const { isConnected, upscale, config, params, loras, promptExpansionRequest } = arg;
453498
const reasons: Reason[] = [];
454499

455500
if (!isConnected) {
@@ -484,6 +529,15 @@ const getReasonsWhyCannotEnqueueUpscaleTab = (arg: {
484529
if (!upscale.tileControlnetModel) {
485530
reasons.push({ content: i18n.t('upscaling.missingTileControlNetModel') });
486531
}
532+
if (model) {
533+
for (const lora of loras.filter(({ isEnabled }) => isEnabled === true)) {
534+
if (model.base !== lora.model.base) {
535+
reasons.push({ content: i18n.t('parameters.invoke.incompatibleLoRAs') });
536+
// Just add the warning once.
537+
break;
538+
}
539+
}
540+
}
487541
}
488542

489543
if (promptExpansionRequest.isPending) {
@@ -501,6 +555,7 @@ const getReasonsWhyCannotEnqueueCanvasTab = (arg: {
501555
canvas: CanvasState;
502556
params: ParamsState;
503557
refImages: RefImagesState;
558+
loras: LoRA[];
504559
dynamicPrompts: DynamicPromptsState;
505560
canvasIsFiltering: boolean;
506561
canvasIsTransforming: boolean;
@@ -516,6 +571,7 @@ const getReasonsWhyCannotEnqueueCanvasTab = (arg: {
516571
canvas,
517572
params,
518573
refImages,
574+
loras,
519575
dynamicPrompts,
520576
canvasIsFiltering,
521577
canvasIsTransforming,
@@ -656,6 +712,16 @@ const getReasonsWhyCannotEnqueueCanvasTab = (arg: {
656712
}
657713
}
658714

715+
if (model) {
716+
for (const lora of loras.filter(({ isEnabled }) => isEnabled === true)) {
717+
if (model.base !== lora.model.base) {
718+
reasons.push({ content: i18n.t('parameters.invoke.incompatibleLoRAs') });
719+
// Just add the warning once.
720+
break;
721+
}
722+
}
723+
}
724+
659725
if (model && isChatGPT4oHighModelDisabled(model)) {
660726
reasons.push({ content: i18n.t('parameters.invoke.modelDisabledForTrial', { modelName: model.name }) });
661727
}

0 commit comments

Comments
 (0)