mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): layers recall
This still needs some finessing - needs logic depending on the tab...
This commit is contained in:
parent
ccd399e277
commit
e537de2f6d
@ -1559,7 +1559,9 @@
|
||||
"opacityFilter": "Opacity Filter",
|
||||
"clearProcessor": "Clear Processor",
|
||||
"resetProcessor": "Reset Processor to Defaults",
|
||||
"noLayersAdded": "No Layers Added"
|
||||
"noLayersAdded": "No Layers Added",
|
||||
"layers_one": "Layer",
|
||||
"layers_other": "Layers"
|
||||
},
|
||||
"ui": {
|
||||
"tabs": {
|
||||
|
@ -255,6 +255,10 @@ export const controlLayersSlice = createSlice({
|
||||
payload: { layerId: uuidv4(), controlAdapter },
|
||||
}),
|
||||
},
|
||||
caLayerRecalled: (state, action: PayloadAction<ControlAdapterLayer>) => {
|
||||
state.layers.push({ ...action.payload, isSelected: true });
|
||||
state.selectedLayerId = action.payload.id;
|
||||
},
|
||||
caLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
||||
const { layerId, imageDTO } = action.payload;
|
||||
const layer = selectCALayerOrThrow(state, layerId);
|
||||
@ -368,6 +372,9 @@ export const controlLayersSlice = createSlice({
|
||||
},
|
||||
prepare: (ipAdapter: IPAdapterConfigV2) => ({ payload: { layerId: uuidv4(), ipAdapter } }),
|
||||
},
|
||||
ipaLayerRecalled: (state, action: PayloadAction<IPAdapterLayer>) => {
|
||||
state.layers.push(action.payload);
|
||||
},
|
||||
ipaLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
||||
const { layerId, imageDTO } = action.payload;
|
||||
const layer = selectIPALayerOrThrow(state, layerId);
|
||||
@ -462,6 +469,10 @@ export const controlLayersSlice = createSlice({
|
||||
},
|
||||
prepare: () => ({ payload: { layerId: uuidv4() } }),
|
||||
},
|
||||
rgLayerRecalled: (state, action: PayloadAction<RegionalGuidanceLayer>) => {
|
||||
state.layers.push({ ...action.payload, isSelected: true });
|
||||
state.selectedLayerId = action.payload.id;
|
||||
},
|
||||
rgLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
|
||||
const { layerId, prompt } = action.payload;
|
||||
const layer = selectRGLayerOrThrow(state, layerId);
|
||||
@ -805,6 +816,7 @@ export const {
|
||||
allLayersDeleted,
|
||||
// CA Layers
|
||||
caLayerAdded,
|
||||
caLayerRecalled,
|
||||
caLayerImageChanged,
|
||||
caLayerProcessedImageChanged,
|
||||
caLayerModelChanged,
|
||||
@ -817,6 +829,7 @@ export const {
|
||||
caLayerT2IAdaptersDeleted,
|
||||
// IPA Layers
|
||||
ipaLayerAdded,
|
||||
ipaLayerRecalled,
|
||||
ipaLayerImageChanged,
|
||||
ipaLayerMethodChanged,
|
||||
ipaLayerModelChanged,
|
||||
@ -827,6 +840,7 @@ export const {
|
||||
caOrIPALayerBeginEndStepPctChanged,
|
||||
// RG Layers
|
||||
rgLayerAdded,
|
||||
rgLayerRecalled,
|
||||
rgLayerPositivePromptChanged,
|
||||
rgLayerNegativePromptChanged,
|
||||
rgLayerPreviewColorChanged,
|
||||
|
@ -51,6 +51,7 @@ const ImageMetadataActions = (props: Props) => {
|
||||
<MetadataItem metadata={metadata} handlers={handlers.refinerScheduler} />
|
||||
<MetadataItem metadata={metadata} handlers={handlers.refinerStart} />
|
||||
<MetadataItem metadata={metadata} handlers={handlers.refinerSteps} />
|
||||
<MetadataItem metadata={metadata} handlers={handlers.layers} />
|
||||
<MetadataLoRAs metadata={metadata} />
|
||||
{activeTabName !== 'generation' && <MetadataControlNets metadata={metadata} />}
|
||||
{activeTabName !== 'generation' && <MetadataT2IAdapters metadata={metadata} />}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { objectKeys } from 'common/util/objectKeys';
|
||||
import { toast } from 'common/util/toast';
|
||||
import type { Layer } from 'features/controlLayers/store/types';
|
||||
import type { LoRA } from 'features/lora/store/loraSlice';
|
||||
import type {
|
||||
AnyControlAdapterConfigMetadata,
|
||||
@ -52,6 +53,9 @@ const renderControlAdapterValueV2: MetadataRenderValueFunc<AnyControlAdapterConf
|
||||
return `${value.model.key} (${value.model.base.toUpperCase()}) - ${value.weight}`;
|
||||
}
|
||||
};
|
||||
const renderLayersValue: MetadataRenderValueFunc<Layer[]> = async (value) => {
|
||||
return `${value.length} ${t('controlLayers.layers', { count: value.length })}`;
|
||||
};
|
||||
|
||||
const parameterSetToast = (parameter: string, description?: string) => {
|
||||
toast({
|
||||
@ -171,6 +175,7 @@ const buildHandlers: BuildMetadataHandlers = ({
|
||||
itemValidator,
|
||||
renderValue,
|
||||
renderItemValue,
|
||||
getIsVisible,
|
||||
}) => ({
|
||||
parse: buildParse({ parser, getLabel }),
|
||||
parseItem: itemParser ? buildParseItem({ itemParser, getLabel }) : undefined,
|
||||
@ -179,6 +184,7 @@ const buildHandlers: BuildMetadataHandlers = ({
|
||||
getLabel,
|
||||
renderValue: renderValue ?? resolveToString,
|
||||
renderItemValue: renderItemValue ?? resolveToString,
|
||||
getIsVisible,
|
||||
});
|
||||
|
||||
export const handlers = {
|
||||
@ -380,6 +386,14 @@ export const handlers = {
|
||||
itemValidator: validators.t2iAdapterV2,
|
||||
renderItemValue: renderControlAdapterValueV2,
|
||||
}),
|
||||
layers: buildHandlers({
|
||||
getLabel: () => t('controlLayers.layers_other'),
|
||||
parser: parsers.layers,
|
||||
recaller: recallers.layers,
|
||||
validator: validators.layers,
|
||||
renderValue: renderLayersValue,
|
||||
getIsVisible: (value) => value.length > 0,
|
||||
}),
|
||||
} as const;
|
||||
|
||||
export const parseAndRecallPrompts = async (metadata: unknown) => {
|
||||
@ -435,9 +449,22 @@ export const parseAndRecallImageDimensions = async (metadata: unknown) => {
|
||||
};
|
||||
|
||||
// These handlers should be omitted when recalling to control layers
|
||||
const TO_CONTROL_LAYERS_SKIP_KEYS: (keyof typeof handlers)[] = ['controlNets', 'ipAdapters', 't2iAdapters'];
|
||||
const TO_CONTROL_LAYERS_SKIP_KEYS: (keyof typeof handlers)[] = [
|
||||
'controlNets',
|
||||
'ipAdapters',
|
||||
't2iAdapters',
|
||||
'controlNetsV2',
|
||||
'ipAdaptersV2',
|
||||
't2iAdaptersV2',
|
||||
];
|
||||
// These handlers should be omitted when recalling to the rest of the app
|
||||
const NOT_TO_CONTROL_LAYERS_SKIP_KEYS: (keyof typeof handlers)[] = ['controlNetsV2', 'ipAdaptersV2', 't2iAdaptersV2'];
|
||||
const NOT_TO_CONTROL_LAYERS_SKIP_KEYS: (keyof typeof handlers)[] = [
|
||||
'controlNetsV2',
|
||||
'ipAdaptersV2',
|
||||
't2iAdaptersV2',
|
||||
'initialImage',
|
||||
'layers',
|
||||
];
|
||||
|
||||
export const parseAndRecallAllMetadata = async (
|
||||
metadata: unknown,
|
||||
|
@ -5,6 +5,8 @@ import {
|
||||
initialT2IAdapter,
|
||||
} from 'features/controlAdapters/util/buildControlAdapter';
|
||||
import { buildControlAdapterProcessor } from 'features/controlAdapters/util/buildControlAdapterProcessor';
|
||||
import type { Layer } from 'features/controlLayers/store/types';
|
||||
import { zLayer } from 'features/controlLayers/store/types';
|
||||
import {
|
||||
CA_PROCESSOR_DATA,
|
||||
imageDTOToImageWithDims,
|
||||
@ -623,6 +625,19 @@ const parseIPAdapterV2: MetadataParseFunc<IPAdapterConfigV2Metadata> = async (me
|
||||
return ipAdapter;
|
||||
};
|
||||
|
||||
const parseLayers: MetadataParseFunc<Layer[]> = async (metadata) => {
|
||||
try {
|
||||
const layersRaw = await getProperty(metadata, 'layers', isArray);
|
||||
const parseResults = await Promise.allSettled(layersRaw.map((layerRaw) => zLayer.parseAsync(layerRaw)));
|
||||
const layers = parseResults
|
||||
.filter((result): result is PromiseFulfilledResult<Layer> => result.status === 'fulfilled')
|
||||
.map((result) => result.value);
|
||||
return layers;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const parseAllIPAdaptersV2: MetadataParseFunc<IPAdapterConfigV2Metadata[]> = async (metadata) => {
|
||||
try {
|
||||
const ipAdaptersRaw = await getProperty(metadata, 'ipAdapters', isArray);
|
||||
@ -678,4 +693,5 @@ export const parsers = {
|
||||
t2iAdaptersV2: parseAllT2IAdaptersV2,
|
||||
ipAdapterV2: parseIPAdapterV2,
|
||||
ipAdaptersV2: parseAllIPAdaptersV2,
|
||||
layers: parseLayers,
|
||||
} as const;
|
||||
|
@ -6,19 +6,24 @@ import {
|
||||
t2iAdaptersReset,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import {
|
||||
allLayersDeleted,
|
||||
caLayerAdded,
|
||||
caLayerControlNetsDeleted,
|
||||
caLayerRecalled,
|
||||
caLayerT2IAdaptersDeleted,
|
||||
heightChanged,
|
||||
iiLayerAdded,
|
||||
ipaLayerAdded,
|
||||
ipaLayerRecalled,
|
||||
ipaLayersDeleted,
|
||||
negativePrompt2Changed,
|
||||
negativePromptChanged,
|
||||
positivePrompt2Changed,
|
||||
positivePromptChanged,
|
||||
rgLayerRecalled,
|
||||
widthChanged,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { Layer } from 'features/controlLayers/store/types';
|
||||
import { setHrfEnabled, setHrfMethod, setHrfStrength } from 'features/hrf/store/hrfSlice';
|
||||
import type { LoRA } from 'features/lora/store/loraSlice';
|
||||
import { loraRecalled, lorasReset } from 'features/lora/store/loraSlice';
|
||||
@ -290,6 +295,24 @@ const recallIPAdaptersV2: MetadataRecallFunc<IPAdapterConfigV2Metadata[]> = (ipA
|
||||
});
|
||||
};
|
||||
|
||||
const recallLayers: MetadataRecallFunc<Layer[]> = (layers) => {
|
||||
const { dispatch } = getStore();
|
||||
dispatch(allLayersDeleted());
|
||||
for (const l of layers) {
|
||||
switch (l.type) {
|
||||
case 'control_adapter_layer':
|
||||
dispatch(caLayerRecalled(l));
|
||||
break;
|
||||
case 'ip_adapter_layer':
|
||||
dispatch(ipaLayerRecalled(l));
|
||||
break;
|
||||
case 'regional_guidance_layer':
|
||||
dispatch(rgLayerRecalled(l));
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const recallers = {
|
||||
positivePrompt: recallPositivePrompt,
|
||||
negativePrompt: recallNegativePrompt,
|
||||
@ -330,4 +353,5 @@ export const recallers = {
|
||||
t2iAdaptersV2: recallT2IAdaptersV2,
|
||||
ipAdapterV2: recallIPAdapterV2,
|
||||
ipAdaptersV2: recallIPAdaptersV2,
|
||||
layers: recallLayers,
|
||||
} as const;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { getStore } from 'app/store/nanostores/store';
|
||||
import type { Layer } from 'features/controlLayers/store/types';
|
||||
import type { LoRA } from 'features/lora/store/loraSlice';
|
||||
import type {
|
||||
ControlNetConfigMetadata,
|
||||
@ -165,6 +166,29 @@ const validateIPAdaptersV2: MetadataValidateFunc<IPAdapterConfigV2Metadata[]> =
|
||||
return new Promise((resolve) => resolve(validatedIPAdapters));
|
||||
};
|
||||
|
||||
const validateLayers: MetadataValidateFunc<Layer[]> = (layers) => {
|
||||
const validatedLayers: Layer[] = [];
|
||||
for (const l of layers) {
|
||||
try {
|
||||
if (l.type === 'control_adapter_layer') {
|
||||
validateBaseCompatibility(l.controlAdapter.model?.base, 'Layer incompatible with currently-selected model');
|
||||
}
|
||||
if (l.type === 'ip_adapter_layer') {
|
||||
validateBaseCompatibility(l.ipAdapter.model?.base, 'Layer incompatible with currently-selected model');
|
||||
}
|
||||
if (l.type === 'regional_guidance_layer') {
|
||||
for (const ipa of l.ipAdapters) {
|
||||
validateBaseCompatibility(ipa.model?.base, 'Layer incompatible with currently-selected model');
|
||||
}
|
||||
}
|
||||
validatedLayers.push(l);
|
||||
} catch {
|
||||
// This is a no-op - we want to continue validating the rest of the layers, and an empty list is valid.
|
||||
}
|
||||
}
|
||||
return new Promise((resolve) => resolve(validatedLayers));
|
||||
};
|
||||
|
||||
export const validators = {
|
||||
refinerModel: validateRefinerModel,
|
||||
vaeModel: validateVAEModel,
|
||||
@ -182,4 +206,5 @@ export const validators = {
|
||||
t2iAdaptersV2: validateT2IAdaptersV2,
|
||||
ipAdapterV2: validateIPAdapterV2,
|
||||
ipAdaptersV2: validateIPAdaptersV2,
|
||||
layers: validateLayers,
|
||||
} as const;
|
||||
|
Loading…
Reference in New Issue
Block a user