mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
refactor(ui): canvas v2 (wip)
This commit is contained in:
parent
d135c48319
commit
8533f207dc
@ -1,5 +1,6 @@
|
|||||||
import { createDraftSafeSelectorCreator, createSelectorCreator, lruMemoize } from '@reduxjs/toolkit';
|
import { createDraftSafeSelectorCreator, createSelector, createSelectorCreator, lruMemoize } from '@reduxjs/toolkit';
|
||||||
import type { GetSelectorsOptions } from '@reduxjs/toolkit/dist/entities/state_selectors';
|
import type { GetSelectorsOptions } from '@reduxjs/toolkit/dist/entities/state_selectors';
|
||||||
|
import type { RootState } from 'app/store/store';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,3 +20,5 @@ export const getSelectorsOptions: GetSelectorsOptions = {
|
|||||||
argsMemoize: lruMemoize,
|
argsMemoize: lruMemoize,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createAppSelector = createSelector.withTypes<RootState>();
|
||||||
|
@ -4,7 +4,6 @@ import { logger } from 'app/logging/logger';
|
|||||||
import { idbKeyValDriver } from 'app/store/enhancers/reduxRemember/driver';
|
import { idbKeyValDriver } from 'app/store/enhancers/reduxRemember/driver';
|
||||||
import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
|
import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
|
||||||
import type { JSONObject } from 'common/types';
|
import type { JSONObject } from 'common/types';
|
||||||
import { canvasPersistConfig } from 'features/canvas/store/canvasSlice';
|
|
||||||
import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice';
|
import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice';
|
||||||
import {
|
import {
|
||||||
controlAdaptersV2PersistConfig,
|
controlAdaptersV2PersistConfig,
|
||||||
@ -104,7 +103,6 @@ export type PersistConfig<T = any> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
|
const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
|
||||||
[canvasPersistConfig.name]: canvasPersistConfig,
|
|
||||||
[galleryPersistConfig.name]: galleryPersistConfig,
|
[galleryPersistConfig.name]: galleryPersistConfig,
|
||||||
[generationPersistConfig.name]: generationPersistConfig,
|
[generationPersistConfig.name]: generationPersistConfig,
|
||||||
[nodesPersistConfig.name]: nodesPersistConfig,
|
[nodesPersistConfig.name]: nodesPersistConfig,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
|
import type { ProcessorTypeV2 } from 'features/controlLayers/store/types';
|
||||||
import type { ParameterPrecision, ParameterScheduler } from 'features/parameters/types/parameterSchemas';
|
import type { ParameterPrecision, ParameterScheduler } from 'features/parameters/types/parameterSchemas';
|
||||||
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
||||||
import type { O } from 'ts-toolbelt';
|
import type { O } from 'ts-toolbelt';
|
||||||
@ -83,7 +83,7 @@ export type AppConfig = {
|
|||||||
sd: {
|
sd: {
|
||||||
defaultModel?: string;
|
defaultModel?: string;
|
||||||
disabledControlNetModels: string[];
|
disabledControlNetModels: string[];
|
||||||
disabledControlNetProcessors: (keyof typeof CONTROLNET_PROCESSORS)[];
|
disabledControlNetProcessors: ProcessorTypeV2;
|
||||||
// Core parameters
|
// Core parameters
|
||||||
iterations: NumericalParameterConfig;
|
iterations: NumericalParameterConfig;
|
||||||
width: NumericalParameterConfig; // initial value comes from model
|
width: NumericalParameterConfig; // initial value comes from model
|
||||||
|
@ -17,10 +17,6 @@ const accept: Accept = {
|
|||||||
const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (activeTabName) => {
|
const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (activeTabName) => {
|
||||||
let postUploadAction: PostUploadAction = { type: 'TOAST' };
|
let postUploadAction: PostUploadAction = { type: 'TOAST' };
|
||||||
|
|
||||||
if (activeTabName === 'canvas') {
|
|
||||||
postUploadAction = { type: 'SET_CANVAS_INITIAL_IMAGE' };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeTabName === 'upscaling') {
|
if (activeTabName === 'upscaling') {
|
||||||
postUploadAction = { type: 'SET_UPSCALE_INITIAL_IMAGE' };
|
postUploadAction = { type: 'SET_UPSCALE_INITIAL_IMAGE' };
|
||||||
}
|
}
|
||||||
@ -30,10 +26,9 @@ const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (ac
|
|||||||
|
|
||||||
export const useFullscreenDropzone = () => {
|
export const useFullscreenDropzone = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const postUploadAction = useAppSelector(selectPostUploadAction);
|
|
||||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||||
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
||||||
|
const postUploadAction = useAppSelector(selectPostUploadAction);
|
||||||
const [uploadImage] = useUploadImageMutation();
|
const [uploadImage] = useUploadImageMutation();
|
||||||
|
|
||||||
const fileRejectionCallback = useCallback(
|
const fileRejectionCallback = useCallback(
|
||||||
|
@ -74,14 +74,6 @@ export const useGlobalHotkeys = () => {
|
|||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'2',
|
'2',
|
||||||
() => {
|
|
||||||
dispatch(setActiveTab('canvas'));
|
|
||||||
},
|
|
||||||
[dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
useHotkeys(
|
|
||||||
'3',
|
|
||||||
() => {
|
() => {
|
||||||
dispatch(setActiveTab('workflows'));
|
dispatch(setActiveTab('workflows'));
|
||||||
},
|
},
|
||||||
@ -89,7 +81,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'4',
|
'3',
|
||||||
() => {
|
() => {
|
||||||
if (isModelManagerEnabled) {
|
if (isModelManagerEnabled) {
|
||||||
dispatch(setActiveTab('models'));
|
dispatch(setActiveTab('models'));
|
||||||
@ -99,7 +91,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
isModelManagerEnabled ? '5' : '4',
|
isModelManagerEnabled ? '4' : '3',
|
||||||
() => {
|
() => {
|
||||||
dispatch(setActiveTab('queue'));
|
dispatch(setActiveTab('queue'));
|
||||||
},
|
},
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import { selectControlAdaptersV2Slice } from 'features/controlLayers/store/controlAdaptersSlice';
|
||||||
selectControlAdapterAll,
|
|
||||||
selectControlAdaptersSlice,
|
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
|
||||||
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
|
||||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type { LayerData } from 'features/controlLayers/store/types';
|
import { selectIPAdaptersSlice } from 'features/controlLayers/store/ipAdaptersSlice';
|
||||||
|
import { selectLayersSlice } from 'features/controlLayers/store/layersSlice';
|
||||||
|
import { selectRegionalGuidanceSlice } from 'features/controlLayers/store/regionalGuidanceSlice';
|
||||||
|
import type { CanvasEntity } from 'features/controlLayers/store/types';
|
||||||
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||||
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
|
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
|
||||||
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
||||||
@ -24,43 +23,49 @@ import { forEach, upperFirst } from 'lodash-es';
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { getConnectedEdges } from 'reactflow';
|
import { getConnectedEdges } from 'reactflow';
|
||||||
|
|
||||||
const LAYER_TYPE_TO_TKEY: Record<LayerData['type'], string> = {
|
const LAYER_TYPE_TO_TKEY: Record<CanvasEntity['type'], string> = {
|
||||||
initial_image_layer: 'controlLayers.globalInitialImage',
|
control_adapter: 'controlLayers.globalControlAdapter',
|
||||||
control_adapter_layer: 'controlLayers.globalControlAdapter',
|
ip_adapter: 'controlLayers.globalIPAdapter',
|
||||||
ip_adapter_layer: 'controlLayers.globalIPAdapter',
|
regional_guidance: 'controlLayers.regionalGuidance',
|
||||||
regional_guidance_layer: 'controlLayers.regionalGuidance',
|
layer: 'controlLayers.raster',
|
||||||
raster_layer: 'controlLayers.raster',
|
inpaint_mask: 'controlLayers.inpaintMask',
|
||||||
};
|
};
|
||||||
|
|
||||||
const createSelector = (templates: Templates) =>
|
const createSelector = (templates: Templates) =>
|
||||||
createMemoizedSelector(
|
createMemoizedSelector(
|
||||||
[
|
[
|
||||||
selectControlAdaptersSlice,
|
|
||||||
selectGenerationSlice,
|
selectGenerationSlice,
|
||||||
selectSystemSlice,
|
selectSystemSlice,
|
||||||
selectNodesSlice,
|
selectNodesSlice,
|
||||||
selectWorkflowSettingsSlice,
|
selectWorkflowSettingsSlice,
|
||||||
selectDynamicPromptsSlice,
|
selectDynamicPromptsSlice,
|
||||||
selectCanvasV2Slice,
|
selectCanvasV2Slice,
|
||||||
|
selectLayersSlice,
|
||||||
|
selectControlAdaptersV2Slice,
|
||||||
|
selectRegionalGuidanceSlice,
|
||||||
|
selectIPAdaptersSlice,
|
||||||
activeTabNameSelector,
|
activeTabNameSelector,
|
||||||
selectUpscalelice,
|
selectUpscalelice,
|
||||||
selectConfigSlice,
|
selectConfigSlice,
|
||||||
],
|
],
|
||||||
(
|
(
|
||||||
controlAdapters,
|
|
||||||
generation,
|
generation,
|
||||||
system,
|
system,
|
||||||
nodes,
|
nodes,
|
||||||
workflowSettings,
|
workflowSettings,
|
||||||
dynamicPrompts,
|
dynamicPrompts,
|
||||||
controlLayers,
|
canvasV2,
|
||||||
|
layersState,
|
||||||
|
controlAdaptersState,
|
||||||
|
regionalGuidanceState,
|
||||||
|
ipAdaptersState,
|
||||||
activeTabName,
|
activeTabName,
|
||||||
upscale,
|
upscale,
|
||||||
config
|
config
|
||||||
) => {
|
) => {
|
||||||
const { model } = generation;
|
const { model } = generation;
|
||||||
const { size } = canvasV2;
|
const { size } = canvasV2;
|
||||||
const { positivePrompt } = canvasV2;
|
const { positivePrompt } = canvasV2.prompts;
|
||||||
|
|
||||||
const { isConnected } = system;
|
const { isConnected } = system;
|
||||||
|
|
||||||
@ -115,101 +120,6 @@ const createSelector = (templates: Templates) =>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (dynamicPrompts.prompts.length === 0 && getShouldProcessPrompt(positivePrompt)) {
|
|
||||||
reasons.push({ content: i18n.t('parameters.invoke.noPrompts') });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!model) {
|
|
||||||
reasons.push({ content: i18n.t('parameters.invoke.noModelSelected') });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeTabName === 'generation') {
|
|
||||||
// Handling for generation tab
|
|
||||||
canvasV2.layers
|
|
||||||
.filter((l) => l.isEnabled)
|
|
||||||
.forEach((l, i) => {
|
|
||||||
const layerLiteral = i18n.t('controlLayers.layers_one');
|
|
||||||
const layerNumber = i + 1;
|
|
||||||
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[l.type]);
|
|
||||||
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
|
||||||
const problems: string[] = [];
|
|
||||||
if (l.type === 'control_adapter_layer') {
|
|
||||||
// Must have model
|
|
||||||
if (!l.controlAdapter.model) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoModelSelected'));
|
|
||||||
}
|
|
||||||
// Model base must match
|
|
||||||
if (l.controlAdapter.model?.base !== model?.base) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterIncompatibleBaseModel'));
|
|
||||||
}
|
|
||||||
// Must have a control image OR, if it has a processor, it must have a processed image
|
|
||||||
if (!l.controlAdapter.image) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoImageSelected'));
|
|
||||||
} else if (l.controlAdapter.processorConfig && !l.controlAdapter.processedImage) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterImageNotProcessed'));
|
|
||||||
}
|
|
||||||
// T2I Adapters require images have dimensions that are multiples of 64 (SD1.5) or 32 (SDXL)
|
|
||||||
if (l.controlAdapter.type === 't2i_adapter') {
|
|
||||||
const multiple = model?.base === 'sdxl' ? 32 : 64;
|
|
||||||
if (size.width % multiple !== 0 || size.height % multiple !== 0) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleDimensions', { multiple }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (l.type === 'ip_adapter_layer') {
|
|
||||||
// Must have model
|
|
||||||
if (!l.ipAdapter.model) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
|
|
||||||
}
|
|
||||||
// Model base must match
|
|
||||||
if (l.ipAdapter.model?.base !== model?.base) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
|
|
||||||
}
|
|
||||||
// Must have an image
|
|
||||||
if (!l.ipAdapter.image) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (l.type === 'initial_image_layer') {
|
|
||||||
// Must have an image
|
|
||||||
if (!l.image) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.initialImageNoImageSelected'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (l.type === 'regional_guidance_layer') {
|
|
||||||
// Must have a region
|
|
||||||
if (l.objects.length === 0) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.rgNoRegion'));
|
|
||||||
}
|
|
||||||
// Must have at least 1 prompt or IP Adapter
|
|
||||||
if (l.positivePrompt === null && l.negativePrompt === null && l.ipAdapters.length === 0) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.rgNoPromptsOrIPAdapters'));
|
|
||||||
}
|
|
||||||
l.ipAdapters.forEach((ipAdapter) => {
|
|
||||||
// Must have model
|
|
||||||
if (!ipAdapter.model) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
|
|
||||||
}
|
|
||||||
// Model base must match
|
|
||||||
if (ipAdapter.model?.base !== model?.base) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
|
|
||||||
}
|
|
||||||
// Must have an image
|
|
||||||
if (!ipAdapter.image) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (problems.length) {
|
|
||||||
const content = upperFirst(problems.join(', '));
|
|
||||||
reasons.push({ prefix, content });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (activeTabName === 'upscaling') {
|
} else if (activeTabName === 'upscaling') {
|
||||||
if (!upscale.upscaleInitialImage) {
|
if (!upscale.upscaleInitialImage) {
|
||||||
reasons.push({ content: i18n.t('upscaling.missingUpscaleInitialImage') });
|
reasons.push({ content: i18n.t('upscaling.missingUpscaleInitialImage') });
|
||||||
@ -231,33 +141,136 @@ const createSelector = (templates: Templates) =>
|
|||||||
reasons.push({ content: i18n.t('upscaling.missingTileControlNetModel') });
|
reasons.push({ content: i18n.t('upscaling.missingTileControlNetModel') });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Handling for all other tabs
|
if (dynamicPrompts.prompts.length === 0 && getShouldProcessPrompt(positivePrompt)) {
|
||||||
selectControlAdapterAll(controlAdapters)
|
reasons.push({ content: i18n.t('parameters.invoke.noPrompts') });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!model) {
|
||||||
|
reasons.push({ content: i18n.t('parameters.invoke.noModelSelected') });
|
||||||
|
}
|
||||||
|
|
||||||
|
controlAdaptersState.controlAdapters
|
||||||
.filter((ca) => ca.isEnabled)
|
.filter((ca) => ca.isEnabled)
|
||||||
.forEach((ca, i) => {
|
.forEach((ca, i) => {
|
||||||
if (!ca.isEnabled) {
|
const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||||
return;
|
const layerNumber = i + 1;
|
||||||
}
|
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[ca.type]);
|
||||||
|
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
||||||
|
const problems: string[] = [];
|
||||||
|
// Must have model
|
||||||
if (!ca.model) {
|
if (!ca.model) {
|
||||||
reasons.push({ content: i18n.t('parameters.invoke.noModelForControlAdapter', { number: i + 1 }) });
|
problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoModelSelected'));
|
||||||
} else if (ca.model.base !== model?.base) {
|
}
|
||||||
// This should never happen, just a sanity check
|
// Model base must match
|
||||||
reasons.push({
|
if (ca.model?.base !== model?.base) {
|
||||||
content: i18n.t('parameters.invoke.incompatibleBaseModelForControlAdapter', { number: i + 1 }),
|
problems.push(i18n.t('parameters.invoke.layer.controlAdapterIncompatibleBaseModel'));
|
||||||
});
|
}
|
||||||
|
// Must have a control image OR, if it has a processor, it must have a processed image
|
||||||
|
if (!ca.image) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoImageSelected'));
|
||||||
|
} else if (ca.processorConfig && !ca.processedImage) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.controlAdapterImageNotProcessed'));
|
||||||
|
}
|
||||||
|
// T2I Adapters require images have dimensions that are multiples of 64 (SD1.5) or 32 (SDXL)
|
||||||
|
if (!ca.controlMode) {
|
||||||
|
const multiple = model?.base === 'sdxl' ? 32 : 64;
|
||||||
|
if (size.width % multiple !== 0 || size.height % multiple !== 0) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleDimensions', { multiple }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (problems.length) {
|
||||||
!ca.controlImage ||
|
const content = upperFirst(problems.join(', '));
|
||||||
(isControlNetOrT2IAdapter(ca) && !ca.processedControlImage && ca.processorType !== 'none')
|
reasons.push({ prefix, content });
|
||||||
) {
|
|
||||||
reasons.push({
|
|
||||||
content: i18n.t('parameters.invoke.noControlImageForControlAdapter', { number: i + 1 }),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipAdaptersState.ipAdapters
|
||||||
|
.filter((ipa) => ipa.isEnabled)
|
||||||
|
.forEach((ipa, i) => {
|
||||||
|
const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||||
|
const layerNumber = i + 1;
|
||||||
|
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[ipa.type]);
|
||||||
|
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
||||||
|
const problems: string[] = [];
|
||||||
|
|
||||||
|
// Must have model
|
||||||
|
if (!ipa.model) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
|
||||||
}
|
}
|
||||||
|
// Model base must match
|
||||||
|
if (ipa.model?.base !== model?.base) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
|
||||||
|
}
|
||||||
|
// Must have an image
|
||||||
|
if (!ipa.image) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (problems.length) {
|
||||||
|
const content = upperFirst(problems.join(', '));
|
||||||
|
reasons.push({ prefix, content });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
regionalGuidanceState.regions
|
||||||
|
.filter((rg) => rg.isEnabled)
|
||||||
|
.forEach((rg, i) => {
|
||||||
|
const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||||
|
const layerNumber = i + 1;
|
||||||
|
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[rg.type]);
|
||||||
|
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
||||||
|
const problems: string[] = [];
|
||||||
|
// Must have a region
|
||||||
|
if (rg.objects.length === 0) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.rgNoRegion'));
|
||||||
|
}
|
||||||
|
// Must have at least 1 prompt or IP Adapter
|
||||||
|
if (rg.positivePrompt === null && rg.negativePrompt === null && rg.ipAdapters.length === 0) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.rgNoPromptsOrIPAdapters'));
|
||||||
|
}
|
||||||
|
rg.ipAdapters.forEach((ipAdapter) => {
|
||||||
|
// Must have model
|
||||||
|
if (!ipAdapter.model) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
|
||||||
|
}
|
||||||
|
// Model base must match
|
||||||
|
if (ipAdapter.model?.base !== model?.base) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
|
||||||
|
}
|
||||||
|
// Must have an image
|
||||||
|
if (!ipAdapter.image) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (problems.length) {
|
||||||
|
const content = upperFirst(problems.join(', '));
|
||||||
|
reasons.push({ prefix, content });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
layersState.layers
|
||||||
|
.filter((l) => l.isEnabled)
|
||||||
|
.forEach((l, i) => {
|
||||||
|
const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||||
|
const layerNumber = i + 1;
|
||||||
|
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[l.type]);
|
||||||
|
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
||||||
|
const problems: string[] = [];
|
||||||
|
|
||||||
|
// if (l.type === 'initial_image_layer') {
|
||||||
|
// // Must have an image
|
||||||
|
// if (!l.image) {
|
||||||
|
// problems.push(i18n.t('parameters.invoke.layer.initialImageNoImageSelected'));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (problems.length) {
|
||||||
|
const content = upperFirst(problems.join(', '));
|
||||||
|
reasons.push({ prefix, content });
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { isReady: !reasons.length, reasons };
|
return { isReady: !reasons.length, reasons };
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { RgbaColor } from 'react-colorful';
|
import type { RgbaColor, RgbColor } from 'react-colorful';
|
||||||
|
|
||||||
export function rgbaToHex(color: RgbaColor, alpha: boolean = false): string {
|
export function rgbaToHex(color: RgbaColor, alpha: boolean = false): string {
|
||||||
const hex = ((1 << 24) + (color.r << 16) + (color.g << 8) + color.b).toString(16).slice(1);
|
const hex = ((1 << 24) + (color.r << 16) + (color.g << 8) + color.b).toString(16).slice(1);
|
||||||
@ -15,3 +15,13 @@ export function hexToRGBA(hex: string, alpha: number) {
|
|||||||
const b = parseInt(hex.substring(4, 6), 16);
|
const b = parseInt(hex.substring(4, 6), 16);
|
||||||
return { r, g, b, a: alpha };
|
return { r, g, b, a: alpha };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const rgbaColorToString = (color: RgbaColor): string => {
|
||||||
|
const { r, g, b, a } = color;
|
||||||
|
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rgbColorToString = (color: RgbColor): string => {
|
||||||
|
const { r, g, b } = color;
|
||||||
|
return `rgba(${r}, ${g}, ${b})`;
|
||||||
|
};
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { useAddCALayer, useAddIILayer, useAddIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
import { useAddCALayer, useAddIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||||
import { layerAdded, regionalGuidanceAdded } from 'features/controlLayers/store/controlLayersSlice';
|
import { layerAdded } from 'features/controlLayers/store/layersSlice';
|
||||||
|
import { rgAdded } from 'features/controlLayers/store/regionalGuidanceSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiPlusBold } from 'react-icons/pi';
|
import { PiPlusBold } from 'react-icons/pi';
|
||||||
@ -11,9 +12,8 @@ export const AddLayerButton = memo(() => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [addCALayer, isAddCALayerDisabled] = useAddCALayer();
|
const [addCALayer, isAddCALayerDisabled] = useAddCALayer();
|
||||||
const [addIPALayer, isAddIPALayerDisabled] = useAddIPALayer();
|
const [addIPALayer, isAddIPALayerDisabled] = useAddIPALayer();
|
||||||
const [addIILayer, isAddIILayerDisabled] = useAddIILayer();
|
|
||||||
const addRGLayer = useCallback(() => {
|
const addRGLayer = useCallback(() => {
|
||||||
dispatch(regionalGuidanceAdded());
|
dispatch(rgAdded());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
const addRasterLayer = useCallback(() => {
|
const addRasterLayer = useCallback(() => {
|
||||||
dispatch(layerAdded());
|
dispatch(layerAdded());
|
||||||
@ -42,9 +42,6 @@ export const AddLayerButton = memo(() => {
|
|||||||
<MenuItem icon={<PiPlusBold />} onClick={addIPALayer} isDisabled={isAddIPALayerDisabled}>
|
<MenuItem icon={<PiPlusBold />} onClick={addIPALayer} isDisabled={isAddIPALayerDisabled}>
|
||||||
{t('controlLayers.globalIPAdapterLayer')}
|
{t('controlLayers.globalIPAdapterLayer')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem icon={<PiPlusBold />} onClick={addIILayer} isDisabled={isAddIILayerDisabled}>
|
|
||||||
{t('controlLayers.globalInitialImageLayer')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
@ -1,44 +1,42 @@
|
|||||||
import { Button, Flex } from '@invoke-ai/ui-library';
|
import { Button, Flex } from '@invoke-ai/ui-library';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useAddIPAdapterToIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
import { useAddIPAdapterToRGLayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||||
import {
|
import {
|
||||||
regionalGuidanceNegativePromptChanged,
|
rgNegativePromptChanged,
|
||||||
regionalGuidancePositivePromptChanged,
|
rgPositivePromptChanged,
|
||||||
selectCanvasV2Slice,
|
selectRegionalGuidanceSlice,
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/regionalGuidanceSlice';
|
||||||
import { isRegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiPlusBold } from 'react-icons/pi';
|
import { PiPlusBold } from 'react-icons/pi';
|
||||||
import { assert } from 'tsafe';
|
|
||||||
type AddPromptButtonProps = {
|
type AddPromptButtonProps = {
|
||||||
layerId: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => {
|
export const AddPromptButtons = ({ id }: AddPromptButtonProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToIPALayer(layerId);
|
const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToRGLayer(id);
|
||||||
const selectValidActions = useMemo(
|
const selectValidActions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
createMemoizedSelector(selectRegionalGuidanceSlice, (regionalGuidanceState) => {
|
||||||
const layer = canvasV2.layers.find((l) => l.id === layerId);
|
const rg = regionalGuidanceState.regions.find((rg) => rg.id === id);
|
||||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
|
||||||
return {
|
return {
|
||||||
canAddPositivePrompt: layer.positivePrompt === null,
|
canAddPositivePrompt: rg?.positivePrompt === null,
|
||||||
canAddNegativePrompt: layer.negativePrompt === null,
|
canAddNegativePrompt: rg?.negativePrompt === null,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
[layerId]
|
[id]
|
||||||
);
|
);
|
||||||
const validActions = useAppSelector(selectValidActions);
|
const validActions = useAppSelector(selectValidActions);
|
||||||
const addPositivePrompt = useCallback(() => {
|
const addPositivePrompt = useCallback(() => {
|
||||||
dispatch(regionalGuidancePositivePromptChanged({ layerId, prompt: '' }));
|
dispatch(rgPositivePromptChanged({ id, prompt: '' }));
|
||||||
}, [dispatch, layerId]);
|
}, [dispatch, id]);
|
||||||
const addNegativePrompt = useCallback(() => {
|
const addNegativePrompt = useCallback(() => {
|
||||||
dispatch(regionalGuidanceNegativePromptChanged({ layerId, prompt: '' }));
|
dispatch(rgNegativePromptChanged({ id, prompt: '' }));
|
||||||
}, [dispatch, layerId]);
|
}, [dispatch, id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex w="full" p={2} justifyContent="space-between">
|
<Flex w="full" p={2} justifyContent="space-between">
|
||||||
|
@ -10,33 +10,33 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { brushSizeChanged, initialControlLayersState } from 'features/controlLayers/store/controlLayersSlice';
|
import { brushWidthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const marks = [0, 100, 200, 300];
|
const marks = [0, 100, 200, 300];
|
||||||
const formatPx = (v: number | string) => `${v} px`;
|
const formatPx = (v: number | string) => `${v} px`;
|
||||||
|
|
||||||
export const BrushSize = memo(() => {
|
export const BrushWidth = memo(() => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const brushSize = useAppSelector((s) => s.canvasV2.brushSize);
|
const width = useAppSelector((s) => s.canvasV2.tool.brush.width);
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
dispatch(brushSizeChanged(Math.round(v)));
|
dispatch(brushWidthChanged(Math.round(v)));
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<FormControl w="min-content" gap={2}>
|
<FormControl w="min-content" gap={2}>
|
||||||
<FormLabel m={0}>{t('controlLayers.brushSize')}</FormLabel>
|
<FormLabel m={0}>{t('controlLayers.brushWidth')}</FormLabel>
|
||||||
<Popover isLazy>
|
<Popover isLazy>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<CompositeNumberInput
|
<CompositeNumberInput
|
||||||
min={1}
|
min={1}
|
||||||
max={600}
|
max={600}
|
||||||
defaultValue={initialControlLayersState.brushSize}
|
defaultValue={50}
|
||||||
value={brushSize}
|
value={width}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
w={24}
|
w={24}
|
||||||
format={formatPx}
|
format={formatPx}
|
||||||
@ -45,14 +45,7 @@ export const BrushSize = memo(() => {
|
|||||||
<PopoverContent w={200} py={2} px={4}>
|
<PopoverContent w={200} py={2} px={4}>
|
||||||
<PopoverArrow />
|
<PopoverArrow />
|
||||||
<PopoverBody>
|
<PopoverBody>
|
||||||
<CompositeSlider
|
<CompositeSlider min={1} max={300} defaultValue={50} value={width} onChange={onChange} marks={marks} />
|
||||||
min={1}
|
|
||||||
max={300}
|
|
||||||
defaultValue={initialControlLayersState.brushSize}
|
|
||||||
value={brushSize}
|
|
||||||
onChange={onChange}
|
|
||||||
marks={marks}
|
|
||||||
/>
|
|
||||||
</PopoverBody>
|
</PopoverBody>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
@ -60,4 +53,4 @@ export const BrushSize = memo(() => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
BrushSize.displayName = 'BrushSize';
|
BrushWidth.displayName = 'BrushSize';
|
@ -1,48 +0,0 @@
|
|||||||
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { CALayerControlAdapterWrapper } from 'features/controlLayers/components/CALayer/CALayerControlAdapterWrapper';
|
|
||||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
|
||||||
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
|
||||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
|
||||||
import { LayerIsEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
|
||||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
|
||||||
import { layerSelected, selectLayerOrThrow } from 'features/controlLayers/store/controlLayersSlice';
|
|
||||||
import { isControlAdapterLayer } from 'features/controlLayers/store/types';
|
|
||||||
import { memo, useCallback } from 'react';
|
|
||||||
|
|
||||||
import CALayerOpacity from './CALayerOpacity';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
layerId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CALayer = memo(({ layerId }: Props) => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const isSelected = useAppSelector(
|
|
||||||
(s) => selectLayerOrThrow(s.canvasV2, layerId, isControlAdapterLayer).isSelected
|
|
||||||
);
|
|
||||||
const onClick = useCallback(() => {
|
|
||||||
dispatch(layerSelected(layerId));
|
|
||||||
}, [dispatch, layerId]);
|
|
||||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}>
|
|
||||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
|
||||||
<LayerIsEnabledToggle layerId={layerId} />
|
|
||||||
<LayerTitle type="control_adapter_layer" />
|
|
||||||
<Spacer />
|
|
||||||
<CALayerOpacity layerId={layerId} />
|
|
||||||
<LayerMenu layerId={layerId} />
|
|
||||||
<LayerDeleteButton layerId={layerId} />
|
|
||||||
</Flex>
|
|
||||||
{isOpen && (
|
|
||||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
|
||||||
<CALayerControlAdapterWrapper layerId={layerId} />
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</LayerWrapper>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
CALayer.displayName = 'CALayer';
|
|
@ -1,135 +0,0 @@
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { ControlAdapter } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapter';
|
|
||||||
import {
|
|
||||||
caOrIPALayerBeginEndStepPctChanged,
|
|
||||||
caOrIPALayerWeightChanged,
|
|
||||||
controlAdapterControlModeChanged,
|
|
||||||
controlAdapterImageChanged,
|
|
||||||
controlAdapterModelChanged,
|
|
||||||
controlAdapterProcessedImageChanged,
|
|
||||||
controlAdapterProcessorConfigChanged,
|
|
||||||
selectLayerOrThrow,
|
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
|
||||||
import { isControlAdapterLayer } from 'features/controlLayers/store/types';
|
|
||||||
import type { ControlModeV2, ProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
|
||||||
import type { CALayerImageDropData } from 'features/dnd/types';
|
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
|
||||||
import type {
|
|
||||||
CALayerImagePostUploadAction,
|
|
||||||
ControlNetModelConfig,
|
|
||||||
ImageDTO,
|
|
||||||
T2IAdapterModelConfig,
|
|
||||||
} from 'services/api/types';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
layerId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CALayerControlAdapterWrapper = memo(({ layerId }: Props) => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const controlAdapter = useAppSelector(
|
|
||||||
(s) => selectLayerOrThrow(s.canvasV2, layerId, isControlAdapterLayer).controlAdapter
|
|
||||||
);
|
|
||||||
|
|
||||||
const onChangeBeginEndStepPct = useCallback(
|
|
||||||
(beginEndStepPct: [number, number]) => {
|
|
||||||
dispatch(
|
|
||||||
caOrIPALayerBeginEndStepPctChanged({
|
|
||||||
layerId,
|
|
||||||
beginEndStepPct,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[dispatch, layerId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onChangeControlMode = useCallback(
|
|
||||||
(controlMode: ControlModeV2) => {
|
|
||||||
dispatch(
|
|
||||||
controlAdapterControlModeChanged({
|
|
||||||
layerId,
|
|
||||||
controlMode,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[dispatch, layerId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onChangeWeight = useCallback(
|
|
||||||
(weight: number) => {
|
|
||||||
dispatch(caOrIPALayerWeightChanged({ layerId, weight }));
|
|
||||||
},
|
|
||||||
[dispatch, layerId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onChangeProcessorConfig = useCallback(
|
|
||||||
(processorConfig: ProcessorConfig | null) => {
|
|
||||||
dispatch(controlAdapterProcessorConfigChanged({ layerId, processorConfig }));
|
|
||||||
},
|
|
||||||
[dispatch, layerId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onChangeModel = useCallback(
|
|
||||||
(modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => {
|
|
||||||
dispatch(
|
|
||||||
controlAdapterModelChanged({
|
|
||||||
layerId,
|
|
||||||
modelConfig,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[dispatch, layerId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onChangeImage = useCallback(
|
|
||||||
(imageDTO: ImageDTO | null) => {
|
|
||||||
dispatch(controlAdapterImageChanged({ layerId, imageDTO }));
|
|
||||||
},
|
|
||||||
[dispatch, layerId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onErrorLoadingImage = useCallback(() => {
|
|
||||||
dispatch(controlAdapterImageChanged({ layerId, imageDTO: null }));
|
|
||||||
}, [dispatch, layerId]);
|
|
||||||
|
|
||||||
const onErrorLoadingProcessedImage = useCallback(() => {
|
|
||||||
dispatch(controlAdapterProcessedImageChanged({ layerId, imageDTO: null }));
|
|
||||||
}, [dispatch, layerId]);
|
|
||||||
|
|
||||||
const droppableData = useMemo<CALayerImageDropData>(
|
|
||||||
() => ({
|
|
||||||
actionType: 'SET_CA_LAYER_IMAGE',
|
|
||||||
context: {
|
|
||||||
layerId,
|
|
||||||
},
|
|
||||||
id: layerId,
|
|
||||||
}),
|
|
||||||
[layerId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const postUploadAction = useMemo<CALayerImagePostUploadAction>(
|
|
||||||
() => ({
|
|
||||||
layerId,
|
|
||||||
type: 'SET_CA_LAYER_IMAGE',
|
|
||||||
}),
|
|
||||||
[layerId]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ControlAdapter
|
|
||||||
controlAdapter={controlAdapter}
|
|
||||||
onChangeBeginEndStepPct={onChangeBeginEndStepPct}
|
|
||||||
onChangeControlMode={onChangeControlMode}
|
|
||||||
onChangeWeight={onChangeWeight}
|
|
||||||
onChangeProcessorConfig={onChangeProcessorConfig}
|
|
||||||
onChangeModel={onChangeModel}
|
|
||||||
onChangeImage={onChangeImage}
|
|
||||||
droppableData={droppableData}
|
|
||||||
postUploadAction={postUploadAction}
|
|
||||||
onErrorLoadingImage={onErrorLoadingImage}
|
|
||||||
onErrorLoadingProcessedImage={onErrorLoadingProcessedImage}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
CALayerControlAdapterWrapper.displayName = 'CALayerControlAdapterWrapper';
|
|
@ -11,7 +11,7 @@ type Props = {
|
|||||||
const formatPct = (v: number) => `${Math.round(v * 100)}%`;
|
const formatPct = (v: number) => `${Math.round(v * 100)}%`;
|
||||||
const ariaLabel = ['Begin Step %', 'End Step %'];
|
const ariaLabel = ['Begin Step %', 'End Step %'];
|
||||||
|
|
||||||
export const ControlAdapterBeginEndStepPct = memo(({ beginEndStepPct, onChange }: Props) => {
|
export const BeginEndStepPct = memo(({ beginEndStepPct, onChange }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const onReset = useCallback(() => {
|
const onReset = useCallback(() => {
|
||||||
onChange([0, 1]);
|
onChange([0, 1]);
|
||||||
@ -40,4 +40,4 @@ export const ControlAdapterBeginEndStepPct = memo(({ beginEndStepPct, onChange }
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ControlAdapterBeginEndStepPct.displayName = 'ControlAdapterBeginEndStepPct';
|
BeginEndStepPct.displayName = 'BeginEndStepPct';
|
@ -12,7 +12,7 @@ type Props = {
|
|||||||
const formatValue = (v: number) => v.toFixed(2);
|
const formatValue = (v: number) => v.toFixed(2);
|
||||||
const marks = [0, 1, 2];
|
const marks = [0, 1, 2];
|
||||||
|
|
||||||
export const ControlAdapterWeight = memo(({ weight, onChange }: Props) => {
|
export const Weight = memo(({ weight, onChange }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const initial = useAppSelector((s) => s.config.sd.ca.weight.initial);
|
const initial = useAppSelector((s) => s.config.sd.ca.weight.initial);
|
||||||
const sliderMin = useAppSelector((s) => s.config.sd.ca.weight.sliderMin);
|
const sliderMin = useAppSelector((s) => s.config.sd.ca.weight.sliderMin);
|
||||||
@ -52,4 +52,4 @@ export const ControlAdapterWeight = memo(({ weight, onChange }: Props) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ControlAdapterWeight.displayName = 'ControlAdapterWeight';
|
Weight.displayName = 'Weight';
|
@ -1,8 +1,8 @@
|
|||||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
import type { ControlModeV2 } from 'features/controlLayers/util/controlAdapters';
|
import type { ControlModeV2} from 'features/controlLayers/store/types';
|
||||||
import { isControlModeV2 } from 'features/controlLayers/util/controlAdapters';
|
import { isControlModeV2 } from 'features/controlLayers/store/types';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
@ -12,7 +12,7 @@ type Props = {
|
|||||||
onChange: (controlMode: ControlModeV2) => void;
|
onChange: (controlMode: ControlModeV2) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ControlAdapterControlModeSelect = memo(({ controlMode, onChange }: Props) => {
|
export const CAControlModeSelect = memo(({ controlMode, onChange }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const CONTROL_MODE_DATA = useMemo(
|
const CONTROL_MODE_DATA = useMemo(
|
||||||
() => [
|
() => [
|
||||||
@ -57,4 +57,4 @@ export const ControlAdapterControlModeSelect = memo(({ controlMode, onChange }:
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ControlAdapterControlModeSelect.displayName = 'ControlAdapterControlModeSelect';
|
CAControlModeSelect.displayName = 'CAControlModeSelect';
|
@ -0,0 +1,35 @@
|
|||||||
|
import { Flex, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { CAHeaderItems } from 'features/controlLayers/components/ControlAdapter/CAHeaderItems';
|
||||||
|
import { CASettings } from 'features/controlLayers/components/ControlAdapter/CASettings';
|
||||||
|
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||||
|
import { entitySelected } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CAEntity = memo(({ id }: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === id);
|
||||||
|
const disclosure = useDisclosure({ defaultIsOpen: true });
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
dispatch(entitySelected({ id, type: 'control_adapter' }));
|
||||||
|
}, [dispatch, id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}>
|
||||||
|
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={disclosure.onToggle}>
|
||||||
|
<CAHeaderItems id={id} />
|
||||||
|
</Flex>
|
||||||
|
{disclosure.isOpen && (
|
||||||
|
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||||
|
<CASettings id={id} />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</LayerWrapper>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CAEntity.displayName = 'CAEntity';
|
@ -0,0 +1,109 @@
|
|||||||
|
import { Menu, MenuItem, MenuList, Spacer } from '@invoke-ai/ui-library';
|
||||||
|
import { createAppSelector } from 'app/store/createMemoizedSelector';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { CAOpacityAndFilter } from 'features/controlLayers/components/ControlAdapter/CAOpacityAndFilter';
|
||||||
|
import { EntityDeleteButton } from 'features/controlLayers/components/LayerCommon/EntityDeleteButton';
|
||||||
|
import { EntityEnabledToggle } from 'features/controlLayers/components/LayerCommon/EntityEnabledToggle';
|
||||||
|
import { EntityMenuButton } from 'features/controlLayers/components/LayerCommon/EntityMenuButton';
|
||||||
|
import { EntityTitle } from 'features/controlLayers/components/LayerCommon/EntityTitle';
|
||||||
|
import {
|
||||||
|
caDeleted,
|
||||||
|
caIsEnabledToggled,
|
||||||
|
caMovedBackwardOne,
|
||||||
|
caMovedForwardOne,
|
||||||
|
caMovedToBack,
|
||||||
|
caMovedToFront,
|
||||||
|
selectCA,
|
||||||
|
selectControlAdaptersV2Slice,
|
||||||
|
} from 'features/controlLayers/store/controlAdaptersSlice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
PiArrowDownBold,
|
||||||
|
PiArrowLineDownBold,
|
||||||
|
PiArrowLineUpBold,
|
||||||
|
PiArrowUpBold,
|
||||||
|
PiTrashSimpleBold,
|
||||||
|
} from 'react-icons/pi';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectValidActions = createAppSelector(
|
||||||
|
[selectControlAdaptersV2Slice, (caState, id: string) => id],
|
||||||
|
(caState, id) => {
|
||||||
|
const ca = selectCA(caState, id);
|
||||||
|
assert(ca, `CA with id ${id} not found`);
|
||||||
|
const caIndex = caState.controlAdapters.indexOf(ca);
|
||||||
|
const caCount = caState.controlAdapters.length;
|
||||||
|
return {
|
||||||
|
canMoveForward: caIndex < caCount - 1,
|
||||||
|
canMoveBackward: caIndex > 0,
|
||||||
|
canMoveToFront: caIndex < caCount - 1,
|
||||||
|
canMoveToBack: caIndex > 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const CAHeaderItems = memo(({ id }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const validActions = useAppSelector((s) => selectValidActions(s, id));
|
||||||
|
const isEnabled = useAppSelector((s) => {
|
||||||
|
const ca = selectCA(s.controlAdaptersV2, id);
|
||||||
|
assert(ca, `CA with id ${id} not found`);
|
||||||
|
return ca.isEnabled;
|
||||||
|
});
|
||||||
|
const onToggle = useCallback(() => {
|
||||||
|
dispatch(caIsEnabledToggled({ id }));
|
||||||
|
}, [dispatch, id]);
|
||||||
|
const onDelete = useCallback(() => {
|
||||||
|
dispatch(caDeleted({ id }));
|
||||||
|
}, [dispatch, id]);
|
||||||
|
const moveForwardOne = useCallback(() => {
|
||||||
|
dispatch(caMovedForwardOne({ id }));
|
||||||
|
}, [dispatch, id]);
|
||||||
|
const moveToFront = useCallback(() => {
|
||||||
|
dispatch(caMovedToFront({ id }));
|
||||||
|
}, [dispatch, id]);
|
||||||
|
const moveBackwardOne = useCallback(() => {
|
||||||
|
dispatch(caMovedBackwardOne({ id }));
|
||||||
|
}, [dispatch, id]);
|
||||||
|
const moveToBack = useCallback(() => {
|
||||||
|
dispatch(caMovedToBack({ id }));
|
||||||
|
}, [dispatch, id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EntityEnabledToggle isEnabled={isEnabled} onToggle={onToggle} />
|
||||||
|
<EntityTitle title={t('controlLayers.globalControlAdapter')} />
|
||||||
|
<Spacer />
|
||||||
|
<CAOpacityAndFilter id={id} />
|
||||||
|
<Menu>
|
||||||
|
<EntityMenuButton />
|
||||||
|
<MenuList>
|
||||||
|
<MenuItem onClick={moveToFront} isDisabled={!validActions.canMoveToFront} icon={<PiArrowLineUpBold />}>
|
||||||
|
{t('controlLayers.moveToFront')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={moveForwardOne} isDisabled={!validActions.canMoveForward} icon={<PiArrowUpBold />}>
|
||||||
|
{t('controlLayers.moveForward')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={moveBackwardOne} isDisabled={!validActions.canMoveBackward} icon={<PiArrowDownBold />}>
|
||||||
|
{t('controlLayers.moveBackward')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={moveToBack} isDisabled={!validActions.canMoveToBack} icon={<PiArrowLineDownBold />}>
|
||||||
|
{t('controlLayers.moveToBack')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={onDelete} icon={<PiTrashSimpleBold />} color="error.300">
|
||||||
|
{t('common.delete')}
|
||||||
|
</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
<EntityDeleteButton onDelete={onDelete} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CAHeaderItems.displayName = 'CAHeaderItems';
|
@ -3,13 +3,11 @@ import { skipToken } from '@reduxjs/toolkit/query';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||||
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
|
||||||
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type { ControlNetConfigV2, T2IAdapterConfigV2 } from 'features/controlLayers/util/controlAdapters';
|
import type { ControlAdapterData } from 'features/controlLayers/store/types';
|
||||||
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
|
||||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiArrowCounterClockwiseBold, PiFloppyDiskBold, PiRulerBold } from 'react-icons/pi';
|
import { PiArrowCounterClockwiseBold, PiFloppyDiskBold, PiRulerBold } from 'react-icons/pi';
|
||||||
@ -22,7 +20,7 @@ import {
|
|||||||
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2;
|
controlAdapter: ControlAdapterData;
|
||||||
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
||||||
droppableData: TypesafeDroppableData;
|
droppableData: TypesafeDroppableData;
|
||||||
postUploadAction: PostUploadAction;
|
postUploadAction: PostUploadAction;
|
||||||
@ -30,7 +28,7 @@ type Props = {
|
|||||||
onErrorLoadingProcessedImage: () => void;
|
onErrorLoadingProcessedImage: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ControlAdapterImagePreview = memo(
|
export const CAImagePreview = memo(
|
||||||
({
|
({
|
||||||
controlAdapter,
|
controlAdapter,
|
||||||
onChangeImage,
|
onChangeImage,
|
||||||
@ -43,7 +41,6 @@ export const ControlAdapterImagePreview = memo(
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
|
||||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||||
const shift = useShiftModifier();
|
const shift = useShiftModifier();
|
||||||
|
|
||||||
@ -88,11 +85,6 @@ export const ControlAdapterImagePreview = memo(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeTabName === 'canvas') {
|
|
||||||
dispatch(
|
|
||||||
setBoundingBoxDimensions({ width: controlImage.width, height: controlImage.height }, optimalDimension)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const options = { updateAspectRatio: true, clamp: true };
|
const options = { updateAspectRatio: true, clamp: true };
|
||||||
|
|
||||||
if (shift) {
|
if (shift) {
|
||||||
@ -107,8 +99,7 @@ export const ControlAdapterImagePreview = memo(
|
|||||||
dispatch(widthChanged({ width, ...options }));
|
dispatch(widthChanged({ width, ...options }));
|
||||||
dispatch(heightChanged({ height, ...options }));
|
dispatch(heightChanged({ height, ...options }));
|
||||||
}
|
}
|
||||||
}
|
}, [controlImage, dispatch, optimalDimension, shift]);
|
||||||
}, [controlImage, activeTabName, dispatch, optimalDimension, shift]);
|
|
||||||
|
|
||||||
const handleMouseEnter = useCallback(() => {
|
const handleMouseEnter = useCallback(() => {
|
||||||
setIsMouseOverImage(true);
|
setIsMouseOverImage(true);
|
||||||
@ -235,4 +226,4 @@ export const ControlAdapterImagePreview = memo(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
ControlAdapterImagePreview.displayName = 'ControlAdapterImagePreview';
|
CAImagePreview.displayName = 'CAImagePreview';
|
@ -11,7 +11,7 @@ type Props = {
|
|||||||
onChange: (modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => void;
|
onChange: (modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ControlAdapterModelCombobox = memo(({ modelKey, onChange: onChangeModel }: Props) => {
|
export const CAModelCombobox = memo(({ modelKey, onChange: onChangeModel }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const currentBaseModel = useAppSelector((s) => s.generation.model?.base);
|
const currentBaseModel = useAppSelector((s) => s.generation.model?.base);
|
||||||
const [modelConfigs, { isLoading }] = useControlNetAndT2IAdapterModels();
|
const [modelConfigs, { isLoading }] = useControlNetAndT2IAdapterModels();
|
||||||
@ -60,4 +60,4 @@ export const ControlAdapterModelCombobox = memo(({ modelKey, onChange: onChangeM
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ControlAdapterModelCombobox.displayName = 'ControlAdapterModelCombobox';
|
CAModelCombobox.displayName = 'CAModelCombobox';
|
@ -15,34 +15,34 @@ import {
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { stopPropagation } from 'common/util/stopPropagation';
|
import { stopPropagation } from 'common/util/stopPropagation';
|
||||||
import { useCALayerOpacity } from 'features/controlLayers/hooks/layerStateHooks';
|
import { useCALayerOpacity } from 'features/controlLayers/hooks/layerStateHooks';
|
||||||
import { caLayerIsFilterEnabledChanged, layerOpacityChanged } from 'features/controlLayers/store/controlLayersSlice';
|
import { caFilterChanged, caOpacityChanged } from 'features/controlLayers/store/controlAdaptersSlice';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiDropHalfFill } from 'react-icons/pi';
|
import { PiDropHalfFill } from 'react-icons/pi';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
layerId: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const marks = [0, 25, 50, 75, 100];
|
const marks = [0, 25, 50, 75, 100];
|
||||||
const formatPct = (v: number | string) => `${v} %`;
|
const formatPct = (v: number | string) => `${v} %`;
|
||||||
|
|
||||||
const CALayerOpacity = ({ layerId }: Props) => {
|
export const CAOpacityAndFilter = memo(({ id }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { opacity, isFilterEnabled } = useCALayerOpacity(layerId);
|
const { opacity, isFilterEnabled } = useCALayerOpacity(id);
|
||||||
const onChangeOpacity = useCallback(
|
const onChangeOpacity = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
dispatch(layerOpacityChanged({ layerId, opacity: v / 100 }));
|
dispatch(caOpacityChanged({ id, opacity: v / 100 }));
|
||||||
},
|
},
|
||||||
[dispatch, layerId]
|
[dispatch, id]
|
||||||
);
|
);
|
||||||
const onChangeFilter = useCallback(
|
const onChangeFilter = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
dispatch(caLayerIsFilterEnabledChanged({ layerId, isFilterEnabled: e.target.checked }));
|
dispatch(caFilterChanged({ id, filter: e.target.checked ? 'LightnessToAlphaFilter' : 'none' }));
|
||||||
},
|
},
|
||||||
[dispatch, layerId]
|
[dispatch, id]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Popover isLazy>
|
<Popover isLazy>
|
||||||
@ -93,6 +93,6 @@ const CALayerOpacity = ({ layerId }: Props) => {
|
|||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default memo(CALayerOpacity);
|
CAOpacityAndFilter.displayName = 'CAOpacityAndFilter';
|
@ -1,24 +1,23 @@
|
|||||||
import type { ProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
import { CannyProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/CannyProcessor';
|
||||||
|
import { ColorMapProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/ColorMapProcessor';
|
||||||
|
import { ContentShuffleProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/ContentShuffleProcessor';
|
||||||
|
import { DepthAnythingProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/DepthAnythingProcessor';
|
||||||
|
import { DWOpenposeProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/DWOpenposeProcessor';
|
||||||
|
import { HedProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/HedProcessor';
|
||||||
|
import { LineartProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/LineartProcessor';
|
||||||
|
import { MediapipeFaceProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/MediapipeFaceProcessor';
|
||||||
|
import { MidasDepthProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/MidasDepthProcessor';
|
||||||
|
import { MlsdImageProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/MlsdImageProcessor';
|
||||||
|
import { PidiProcessor } from 'features/controlLayers/components/ControlAndIPAdapter/processors/PidiProcessor';
|
||||||
|
import type { ProcessorConfig } from 'features/controlLayers/store/types';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
import { CannyProcessor } from './processors/CannyProcessor';
|
|
||||||
import { ColorMapProcessor } from './processors/ColorMapProcessor';
|
|
||||||
import { ContentShuffleProcessor } from './processors/ContentShuffleProcessor';
|
|
||||||
import { DepthAnythingProcessor } from './processors/DepthAnythingProcessor';
|
|
||||||
import { DWOpenposeProcessor } from './processors/DWOpenposeProcessor';
|
|
||||||
import { HedProcessor } from './processors/HedProcessor';
|
|
||||||
import { LineartProcessor } from './processors/LineartProcessor';
|
|
||||||
import { MediapipeFaceProcessor } from './processors/MediapipeFaceProcessor';
|
|
||||||
import { MidasDepthProcessor } from './processors/MidasDepthProcessor';
|
|
||||||
import { MlsdImageProcessor } from './processors/MlsdImageProcessor';
|
|
||||||
import { PidiProcessor } from './processors/PidiProcessor';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
config: ProcessorConfig | null;
|
config: ProcessorConfig | null;
|
||||||
onChange: (config: ProcessorConfig | null) => void;
|
onChange: (config: ProcessorConfig | null) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ControlAdapterProcessorConfig = memo(({ config, onChange }: Props) => {
|
export const CAProcessorConfig = memo(({ config, onChange }: Props) => {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -82,4 +81,4 @@ export const ControlAdapterProcessorConfig = memo(({ config, onChange }: Props)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ControlAdapterProcessorConfig.displayName = 'ControlAdapterProcessorConfig';
|
CAProcessorConfig.displayName = 'CAProcessorConfig';
|
@ -3,8 +3,8 @@ import { Combobox, Flex, FormControl, FormLabel, IconButton } from '@invoke-ai/u
|
|||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
import type { ProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
import type {ProcessorConfig } from 'features/controlLayers/store/types';
|
||||||
import { CA_PROCESSOR_DATA, isProcessorTypeV2 } from 'features/controlLayers/util/controlAdapters';
|
import { CA_PROCESSOR_DATA, isProcessorTypeV2 } from 'features/controlLayers/store/types';
|
||||||
import { configSelector } from 'features/system/store/configSelectors';
|
import { configSelector } from 'features/system/store/configSelectors';
|
||||||
import { includes, map } from 'lodash-es';
|
import { includes, map } from 'lodash-es';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
@ -22,7 +22,7 @@ const selectDisabledProcessors = createMemoizedSelector(
|
|||||||
(config) => config.sd.disabledControlNetProcessors
|
(config) => config.sd.disabledControlNetProcessors
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ControlAdapterProcessorTypeSelect = memo(({ config, onChange }: Props) => {
|
export const CAProcessorTypeSelect = memo(({ config, onChange }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const disabledProcessors = useAppSelector(selectDisabledProcessors);
|
const disabledProcessors = useAppSelector(selectDisabledProcessors);
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
@ -67,4 +67,4 @@ export const ControlAdapterProcessorTypeSelect = memo(({ config, onChange }: Pro
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ControlAdapterProcessorTypeSelect.displayName = 'ControlAdapterProcessorTypeSelect';
|
CAProcessorTypeSelect.displayName = 'CAProcessorTypeSelect';
|
@ -0,0 +1,157 @@
|
|||||||
|
import { Box, Divider, Flex, Icon, IconButton } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { BeginEndStepPct } from 'features/controlLayers/components/Common/BeginEndStepPct';
|
||||||
|
import { Weight } from 'features/controlLayers/components/Common/Weight';
|
||||||
|
import { CAControlModeSelect } from 'features/controlLayers/components/ControlAdapter/CAControlModeSelect';
|
||||||
|
import { CAImagePreview } from 'features/controlLayers/components/ControlAdapter/CAImagePreview';
|
||||||
|
import { CAModelCombobox } from 'features/controlLayers/components/ControlAdapter/CAModelCombobox';
|
||||||
|
import { CAProcessorConfig } from 'features/controlLayers/components/ControlAdapter/CAProcessorConfig';
|
||||||
|
import { CAProcessorTypeSelect } from 'features/controlLayers/components/ControlAdapter/CAProcessorTypeSelect';
|
||||||
|
import {
|
||||||
|
caBeginEndStepPctChanged,
|
||||||
|
caControlModeChanged,
|
||||||
|
caImageChanged,
|
||||||
|
caModelChanged,
|
||||||
|
caProcessedImageChanged,
|
||||||
|
caProcessorConfigChanged,
|
||||||
|
caWeightChanged,
|
||||||
|
} from 'features/controlLayers/store/controlAdaptersSlice';
|
||||||
|
import type { ControlModeV2, ProcessorConfig } from 'features/controlLayers/store/types';
|
||||||
|
import type { CAImageDropData } from 'features/dnd/types';
|
||||||
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiCaretUpBold } from 'react-icons/pi';
|
||||||
|
import { useToggle } from 'react-use';
|
||||||
|
import type {
|
||||||
|
CAImagePostUploadAction,
|
||||||
|
ControlNetModelConfig,
|
||||||
|
ImageDTO,
|
||||||
|
T2IAdapterModelConfig,
|
||||||
|
} from 'services/api/types';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CASettings = memo(({ id }: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [isExpanded, toggleIsExpanded] = useToggle(false);
|
||||||
|
|
||||||
|
const controlAdapter = useAppSelector((s) => {
|
||||||
|
const ca = s.controlAdaptersV2.controlAdapters.find((ca) => ca.id === id);
|
||||||
|
assert(ca, `ControlAdapter with id ${id} not found`);
|
||||||
|
return ca;
|
||||||
|
});
|
||||||
|
|
||||||
|
const onChangeBeginEndStepPct = useCallback(
|
||||||
|
(beginEndStepPct: [number, number]) => {
|
||||||
|
dispatch(caBeginEndStepPctChanged({ id, beginEndStepPct }));
|
||||||
|
},
|
||||||
|
[dispatch, id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChangeControlMode = useCallback(
|
||||||
|
(controlMode: ControlModeV2) => {
|
||||||
|
dispatch(caControlModeChanged({ id, controlMode }));
|
||||||
|
},
|
||||||
|
[dispatch, id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChangeWeight = useCallback(
|
||||||
|
(weight: number) => {
|
||||||
|
dispatch(caWeightChanged({ id, weight }));
|
||||||
|
},
|
||||||
|
[dispatch, id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChangeProcessorConfig = useCallback(
|
||||||
|
(processorConfig: ProcessorConfig | null) => {
|
||||||
|
dispatch(caProcessorConfigChanged({ id, processorConfig }));
|
||||||
|
},
|
||||||
|
[dispatch, id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChangeModel = useCallback(
|
||||||
|
(modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => {
|
||||||
|
dispatch(caModelChanged({ id, modelConfig }));
|
||||||
|
},
|
||||||
|
[dispatch, id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChangeImage = useCallback(
|
||||||
|
(imageDTO: ImageDTO | null) => {
|
||||||
|
dispatch(caImageChanged({ id, imageDTO }));
|
||||||
|
},
|
||||||
|
[dispatch, id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onErrorLoadingImage = useCallback(() => {
|
||||||
|
dispatch(caImageChanged({ id, imageDTO: null }));
|
||||||
|
}, [dispatch, id]);
|
||||||
|
|
||||||
|
const onErrorLoadingProcessedImage = useCallback(() => {
|
||||||
|
dispatch(caProcessedImageChanged({ id, imageDTO: null }));
|
||||||
|
}, [dispatch, id]);
|
||||||
|
|
||||||
|
const droppableData = useMemo<CAImageDropData>(() => ({ actionType: 'SET_CA_IMAGE', context: { id }, id }), [id]);
|
||||||
|
const postUploadAction = useMemo<CAImagePostUploadAction>(() => ({ id, type: 'SET_CA_IMAGE' }), [id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex flexDir="column" gap={3} position="relative" w="full">
|
||||||
|
<Flex gap={3} alignItems="center" w="full">
|
||||||
|
<Box minW={0} w="full" transitionProperty="common" transitionDuration="0.1s">
|
||||||
|
<CAModelCombobox modelKey={controlAdapter.model?.key ?? null} onChange={onChangeModel} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
size="sm"
|
||||||
|
tooltip={isExpanded ? t('controlnet.hideAdvanced') : t('controlnet.showAdvanced')}
|
||||||
|
aria-label={isExpanded ? t('controlnet.hideAdvanced') : t('controlnet.showAdvanced')}
|
||||||
|
onClick={toggleIsExpanded}
|
||||||
|
variant="ghost"
|
||||||
|
icon={
|
||||||
|
<Icon
|
||||||
|
boxSize={4}
|
||||||
|
as={PiCaretUpBold}
|
||||||
|
transform={isExpanded ? 'rotate(0deg)' : 'rotate(180deg)'}
|
||||||
|
transitionProperty="common"
|
||||||
|
transitionDuration="normal"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Flex gap={3} w="full">
|
||||||
|
<Flex flexDir="column" gap={3} w="full" h="full">
|
||||||
|
{controlAdapter.controlMode && (
|
||||||
|
<CAControlModeSelect controlMode={controlAdapter.controlMode} onChange={onChangeControlMode} />
|
||||||
|
)}
|
||||||
|
<Weight weight={controlAdapter.weight} onChange={onChangeWeight} />
|
||||||
|
<BeginEndStepPct beginEndStepPct={controlAdapter.beginEndStepPct} onChange={onChangeBeginEndStepPct} />
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems="center" justifyContent="center" h={36} w={36} aspectRatio="1/1">
|
||||||
|
<CAImagePreview
|
||||||
|
controlAdapter={controlAdapter}
|
||||||
|
onChangeImage={onChangeImage}
|
||||||
|
droppableData={droppableData}
|
||||||
|
postUploadAction={postUploadAction}
|
||||||
|
onErrorLoadingImage={onErrorLoadingImage}
|
||||||
|
onErrorLoadingProcessedImage={onErrorLoadingProcessedImage}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
{isExpanded && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<Flex flexDir="column" gap={3} w="full">
|
||||||
|
<CAProcessorTypeSelect config={controlAdapter.processorConfig} onChange={onChangeProcessorConfig} />
|
||||||
|
<CAProcessorConfig config={controlAdapter.processorConfig} onChange={onChangeProcessorConfig} />
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CASettings.displayName = 'CASettings';
|
@ -1,123 +0,0 @@
|
|||||||
import { Box, Divider, Flex, Icon, IconButton } from '@invoke-ai/ui-library';
|
|
||||||
import { ControlAdapterModelCombobox } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapterModelCombobox';
|
|
||||||
import type {
|
|
||||||
ControlModeV2,
|
|
||||||
ControlNetConfigV2,
|
|
||||||
ProcessorConfig,
|
|
||||||
T2IAdapterConfigV2,
|
|
||||||
} from 'features/controlLayers/util/controlAdapters';
|
|
||||||
import type { TypesafeDroppableData } from 'features/dnd/types';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { PiCaretUpBold } from 'react-icons/pi';
|
|
||||||
import { useToggle } from 'react-use';
|
|
||||||
import type { ControlNetModelConfig, ImageDTO, PostUploadAction, T2IAdapterModelConfig } from 'services/api/types';
|
|
||||||
|
|
||||||
import { ControlAdapterBeginEndStepPct } from './ControlAdapterBeginEndStepPct';
|
|
||||||
import { ControlAdapterControlModeSelect } from './ControlAdapterControlModeSelect';
|
|
||||||
import { ControlAdapterImagePreview } from './ControlAdapterImagePreview';
|
|
||||||
import { ControlAdapterProcessorConfig } from './ControlAdapterProcessorConfig';
|
|
||||||
import { ControlAdapterProcessorTypeSelect } from './ControlAdapterProcessorTypeSelect';
|
|
||||||
import { ControlAdapterWeight } from './ControlAdapterWeight';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2;
|
|
||||||
onChangeBeginEndStepPct: (beginEndStepPct: [number, number]) => void;
|
|
||||||
onChangeControlMode: (controlMode: ControlModeV2) => void;
|
|
||||||
onChangeWeight: (weight: number) => void;
|
|
||||||
onChangeProcessorConfig: (processorConfig: ProcessorConfig | null) => void;
|
|
||||||
onChangeModel: (modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => void;
|
|
||||||
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
|
||||||
onErrorLoadingImage: () => void;
|
|
||||||
onErrorLoadingProcessedImage: () => void;
|
|
||||||
droppableData: TypesafeDroppableData;
|
|
||||||
postUploadAction: PostUploadAction;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ControlAdapter = memo(
|
|
||||||
({
|
|
||||||
controlAdapter,
|
|
||||||
onChangeBeginEndStepPct,
|
|
||||||
onChangeControlMode,
|
|
||||||
onChangeWeight,
|
|
||||||
onChangeProcessorConfig,
|
|
||||||
onChangeModel,
|
|
||||||
onChangeImage,
|
|
||||||
onErrorLoadingImage,
|
|
||||||
onErrorLoadingProcessedImage,
|
|
||||||
droppableData,
|
|
||||||
postUploadAction,
|
|
||||||
}: Props) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [isExpanded, toggleIsExpanded] = useToggle(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex flexDir="column" gap={3} position="relative" w="full">
|
|
||||||
<Flex gap={3} alignItems="center" w="full">
|
|
||||||
<Box minW={0} w="full" transitionProperty="common" transitionDuration="0.1s">
|
|
||||||
<ControlAdapterModelCombobox modelKey={controlAdapter.model?.key ?? null} onChange={onChangeModel} />
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<IconButton
|
|
||||||
size="sm"
|
|
||||||
tooltip={isExpanded ? t('controlnet.hideAdvanced') : t('controlnet.showAdvanced')}
|
|
||||||
aria-label={isExpanded ? t('controlnet.hideAdvanced') : t('controlnet.showAdvanced')}
|
|
||||||
onClick={toggleIsExpanded}
|
|
||||||
variant="ghost"
|
|
||||||
icon={
|
|
||||||
<Icon
|
|
||||||
boxSize={4}
|
|
||||||
as={PiCaretUpBold}
|
|
||||||
transform={isExpanded ? 'rotate(0deg)' : 'rotate(180deg)'}
|
|
||||||
transitionProperty="common"
|
|
||||||
transitionDuration="normal"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
<Flex gap={3} w="full">
|
|
||||||
<Flex flexDir="column" gap={3} w="full" h="full">
|
|
||||||
{controlAdapter.type === 'controlnet' && (
|
|
||||||
<ControlAdapterControlModeSelect
|
|
||||||
controlMode={controlAdapter.controlMode}
|
|
||||||
onChange={onChangeControlMode}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<ControlAdapterWeight weight={controlAdapter.weight} onChange={onChangeWeight} />
|
|
||||||
<ControlAdapterBeginEndStepPct
|
|
||||||
beginEndStepPct={controlAdapter.beginEndStepPct}
|
|
||||||
onChange={onChangeBeginEndStepPct}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
<Flex alignItems="center" justifyContent="center" h={36} w={36} aspectRatio="1/1">
|
|
||||||
<ControlAdapterImagePreview
|
|
||||||
controlAdapter={controlAdapter}
|
|
||||||
onChangeImage={onChangeImage}
|
|
||||||
droppableData={droppableData}
|
|
||||||
postUploadAction={postUploadAction}
|
|
||||||
onErrorLoadingImage={onErrorLoadingImage}
|
|
||||||
onErrorLoadingProcessedImage={onErrorLoadingProcessedImage}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
{isExpanded && (
|
|
||||||
<>
|
|
||||||
<Divider />
|
|
||||||
<Flex flexDir="column" gap={3} w="full">
|
|
||||||
<ControlAdapterProcessorTypeSelect
|
|
||||||
config={controlAdapter.processorConfig}
|
|
||||||
onChange={onChangeProcessorConfig}
|
|
||||||
/>
|
|
||||||
<ControlAdapterProcessorConfig
|
|
||||||
config={controlAdapter.processorConfig}
|
|
||||||
onChange={onChangeProcessorConfig}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
ControlAdapter.displayName = 'ControlAdapter';
|
|
@ -1,5 +1,5 @@
|
|||||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||||
import { ControlAdapterBeginEndStepPct } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapterBeginEndStepPct';
|
import { BeginEndStepPct } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapterBeginEndStepPct';
|
||||||
import { ControlAdapterWeight } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapterWeight';
|
import { ControlAdapterWeight } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapterWeight';
|
||||||
import { IPAdapterImagePreview } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterImagePreview';
|
import { IPAdapterImagePreview } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterImagePreview';
|
||||||
import { IPAdapterMethod } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterMethod';
|
import { IPAdapterMethod } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterMethod';
|
||||||
@ -49,7 +49,7 @@ export const IPAdapter = memo(
|
|||||||
<Flex flexDir="column" gap={3} w="full">
|
<Flex flexDir="column" gap={3} w="full">
|
||||||
<IPAdapterMethod method={ipAdapter.method} onChange={onChangeIPMethod} />
|
<IPAdapterMethod method={ipAdapter.method} onChange={onChangeIPMethod} />
|
||||||
<ControlAdapterWeight weight={ipAdapter.weight} onChange={onChangeWeight} />
|
<ControlAdapterWeight weight={ipAdapter.weight} onChange={onChangeWeight} />
|
||||||
<ControlAdapterBeginEndStepPct
|
<BeginEndStepPct
|
||||||
beginEndStepPct={ipAdapter.beginEndStepPct}
|
beginEndStepPct={ipAdapter.beginEndStepPct}
|
||||||
onChange={onChangeBeginEndStepPct}
|
onChange={onChangeBeginEndStepPct}
|
||||||
/>
|
/>
|
||||||
|
@ -8,7 +8,7 @@ import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton
|
|||||||
import { CALayer } from 'features/controlLayers/components/CALayer/CALayer';
|
import { CALayer } from 'features/controlLayers/components/CALayer/CALayer';
|
||||||
import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton';
|
import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton';
|
||||||
import { IILayer } from 'features/controlLayers/components/IILayer/IILayer';
|
import { IILayer } from 'features/controlLayers/components/IILayer/IILayer';
|
||||||
import { IPALayer } from 'features/controlLayers/components/IPALayer/IPALayer';
|
import { IPAEntity } from 'features/controlLayers/components/IPALayer/IPALayer';
|
||||||
import { RasterLayer } from 'features/controlLayers/components/RasterLayer/RasterLayer';
|
import { RasterLayer } from 'features/controlLayers/components/RasterLayer/RasterLayer';
|
||||||
import { RGLayer } from 'features/controlLayers/components/RGLayer/RGLayer';
|
import { RGLayer } from 'features/controlLayers/components/RGLayer/RGLayer';
|
||||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
@ -58,10 +58,10 @@ const LayerWrapper = memo(({ id, type }: LayerWrapperProps) => {
|
|||||||
return <RGLayer key={id} layerId={id} />;
|
return <RGLayer key={id} layerId={id} />;
|
||||||
}
|
}
|
||||||
if (type === 'control_adapter_layer') {
|
if (type === 'control_adapter_layer') {
|
||||||
return <CALayer key={id} layerId={id} />;
|
return <CALayer key={id} id={id} />;
|
||||||
}
|
}
|
||||||
if (type === 'ip_adapter_layer') {
|
if (type === 'ip_adapter_layer') {
|
||||||
return <IPALayer key={id} layerId={id} />;
|
return <IPAEntity key={id} layerId={id} />;
|
||||||
}
|
}
|
||||||
if (type === 'initial_image_layer') {
|
if (type === 'initial_image_layer') {
|
||||||
return <IILayer key={id} layerId={id} />;
|
return <IILayer key={id} layerId={id} />;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { Flex } from '@invoke-ai/ui-library';
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { BrushColorPicker } from 'features/controlLayers/components/BrushColorPicker';
|
import { BrushColorPicker } from 'features/controlLayers/components/BrushColorPicker';
|
||||||
import { BrushSize } from 'features/controlLayers/components/BrushSize';
|
import { BrushWidth } from 'features/controlLayers/components/BrushSize';
|
||||||
import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover';
|
import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover';
|
||||||
import { ToolChooser } from 'features/controlLayers/components/ToolChooser';
|
import { ToolChooser } from 'features/controlLayers/components/ToolChooser';
|
||||||
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
|
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
|
||||||
@ -28,7 +28,7 @@ export const ControlLayersToolbar = memo(() => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex flex={1} gap={2} justifyContent="center" alignItems="center">
|
<Flex flex={1} gap={2} justifyContent="center" alignItems="center">
|
||||||
{withBrushSize && <BrushSize />}
|
{withBrushSize && <BrushWidth />}
|
||||||
{withBrushColor && <BrushColorPicker />}
|
{withBrushColor && <BrushColorPicker />}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex flex={1} justifyContent="center">
|
<Flex flex={1} justifyContent="center">
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
import {
|
||||||
|
CompositeNumberInput,
|
||||||
|
CompositeSlider,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
Popover,
|
||||||
|
PopoverArrow,
|
||||||
|
PopoverBody,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { eraserWidthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const marks = [0, 100, 200, 300];
|
||||||
|
const formatPx = (v: number | string) => `${v} px`;
|
||||||
|
|
||||||
|
export const EraserWidth = memo(() => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const width = useAppSelector((s) => s.canvasV2.tool.eraser.width);
|
||||||
|
const onChange = useCallback(
|
||||||
|
(v: number) => {
|
||||||
|
dispatch(eraserWidthChanged(Math.round(v)));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<FormControl w="min-content" gap={2}>
|
||||||
|
<FormLabel m={0}>{t('controlLayers.eraserWidth')}</FormLabel>
|
||||||
|
<Popover isLazy>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<CompositeNumberInput
|
||||||
|
min={1}
|
||||||
|
max={600}
|
||||||
|
defaultValue={50}
|
||||||
|
value={width}
|
||||||
|
onChange={onChange}
|
||||||
|
w={24}
|
||||||
|
format={formatPx}
|
||||||
|
/>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent w={200} py={2} px={4}>
|
||||||
|
<PopoverArrow />
|
||||||
|
<PopoverBody>
|
||||||
|
<CompositeSlider min={1} max={300} defaultValue={50} value={width} onChange={onChange} marks={marks} />
|
||||||
|
</PopoverBody>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
EraserWidth.displayName = 'EraserWidth';
|
@ -1,19 +1,19 @@
|
|||||||
import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library';
|
import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIColorPicker from 'common/components/IAIColorPicker';
|
import IAIColorPicker from 'common/components/IAIColorPicker';
|
||||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||||
import { brushColorChanged } from 'features/controlLayers/store/controlLayersSlice';
|
import { fillChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type { RgbaColor } from 'features/controlLayers/store/types';
|
import type { RgbaColor } from 'features/controlLayers/store/types';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const BrushColorPicker = memo(() => {
|
export const FillColorPicker = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const brushColor = useAppSelector((s) => s.canvasV2.brushColor);
|
const fill = useAppSelector((s) => s.canvasV2.tool.fill);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(color: RgbaColor) => {
|
(color: RgbaColor) => {
|
||||||
dispatch(brushColorChanged(color));
|
dispatch(fillChanged(color));
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
@ -25,7 +25,7 @@ export const BrushColorPicker = memo(() => {
|
|||||||
aria-label={t('controlLayers.brushColor')}
|
aria-label={t('controlLayers.brushColor')}
|
||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
borderWidth={1}
|
borderWidth={1}
|
||||||
bg={rgbaColorToString(brushColor)}
|
bg={rgbaColorToString(fill)}
|
||||||
w={8}
|
w={8}
|
||||||
h={8}
|
h={8}
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
@ -34,11 +34,11 @@ export const BrushColorPicker = memo(() => {
|
|||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent>
|
<PopoverContent>
|
||||||
<PopoverBody minH={64}>
|
<PopoverBody minH={64}>
|
||||||
<IAIColorPicker color={brushColor} onChange={onChange} withNumberInput />
|
<IAIColorPicker color={fill} onChange={onChange} withNumberInput />
|
||||||
</PopoverBody>
|
</PopoverBody>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
BrushColorPicker.displayName = 'BrushColorPicker';
|
FillColorPicker.displayName = 'BrushColorPicker';
|
@ -2,10 +2,10 @@ import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InitialImagePreview } from 'features/controlLayers/components/IILayer/InitialImagePreview';
|
import { InitialImagePreview } from 'features/controlLayers/components/IILayer/InitialImagePreview';
|
||||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||||
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
import { EntityMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
||||||
import { LayerOpacity } from 'features/controlLayers/components/LayerCommon/LayerOpacity';
|
import { LayerOpacity } from 'features/controlLayers/components/LayerCommon/LayerOpacity';
|
||||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
import { EntityTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||||
import { LayerIsEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
import { EntityEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||||
import {
|
import {
|
||||||
iiLayerDenoisingStrengthChanged,
|
iiLayerDenoisingStrengthChanged,
|
||||||
@ -67,11 +67,11 @@ export const IILayer = memo(({ layerId }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<LayerWrapper onClick={onClick} borderColor={layer.isSelected ? 'base.400' : 'base.800'}>
|
<LayerWrapper onClick={onClick} borderColor={layer.isSelected ? 'base.400' : 'base.800'}>
|
||||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||||
<LayerIsEnabledToggle layerId={layerId} />
|
<EntityEnabledToggle layerId={layerId} />
|
||||||
<LayerTitle type="initial_image_layer" />
|
<EntityTitle type="initial_image_layer" />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<LayerOpacity layerId={layerId} />
|
<LayerOpacity layerId={layerId} />
|
||||||
<LayerMenu layerId={layerId} />
|
<EntityMenu layerId={layerId} />
|
||||||
<LayerDeleteButton layerId={layerId} />
|
<LayerDeleteButton layerId={layerId} />
|
||||||
</Flex>
|
</Flex>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
|
@ -2,41 +2,39 @@ import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { IPALayerIPAdapterWrapper } from 'features/controlLayers/components/IPALayer/IPALayerIPAdapterWrapper';
|
import { IPALayerIPAdapterWrapper } from 'features/controlLayers/components/IPALayer/IPALayerIPAdapterWrapper';
|
||||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
import { EntityTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||||
import { LayerIsEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
import { EntityEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||||
import { layerSelected, selectLayerOrThrow } from 'features/controlLayers/store/controlLayersSlice';
|
import { entitySelected } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { isIPAdapterLayer } from 'features/controlLayers/store/types';
|
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
layerId: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IPALayer = memo(({ layerId }: Props) => {
|
export const IPAEntity = memo(({ id }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isSelected = useAppSelector(
|
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === id);
|
||||||
(s) => selectLayerOrThrow(s.canvasV2, layerId, isIPAdapterLayer).isSelected
|
|
||||||
);
|
|
||||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
dispatch(layerSelected(layerId));
|
dispatch(entitySelected({ id, type: 'ip_adapter' }));
|
||||||
}, [dispatch, layerId]);
|
}, [dispatch, id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}>
|
<LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}>
|
||||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||||
<LayerIsEnabledToggle layerId={layerId} />
|
<EntityEnabledToggle id={id} />
|
||||||
<LayerTitle type="ip_adapter_layer" />
|
<EntityTitle type="ip_adapter" />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<LayerDeleteButton layerId={layerId} />
|
<LayerDeleteButton id={id} />
|
||||||
</Flex>
|
</Flex>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||||
<IPALayerIPAdapterWrapper layerId={layerId} />
|
<IPALayerIPAdapterWrapper id={id} />
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</LayerWrapper>
|
</LayerWrapper>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
IPALayer.displayName = 'IPALayer';
|
IPAEntity.displayName = 'IPAEntity';
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { isIPAdapterLayer } from 'features/controlLayers/store/types';
|
import { isIPAdapterLayer } from 'features/controlLayers/store/types';
|
||||||
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||||
import type { IPALayerImageDropData } from 'features/dnd/types';
|
import type { IPAImageDropData } from 'features/dnd/types';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import type { ImageDTO, IPAdapterModelConfig, IPALayerImagePostUploadAction } from 'services/api/types';
|
import type { ImageDTO, IPAdapterModelConfig, IPALayerImagePostUploadAction } from 'services/api/types';
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ export const IPALayerIPAdapterWrapper = memo(({ layerId }: Props) => {
|
|||||||
[dispatch, layerId]
|
[dispatch, layerId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const droppableData = useMemo<IPALayerImageDropData>(
|
const droppableData = useMemo<IPAImageDropData>(
|
||||||
() => ({
|
() => ({
|
||||||
actionType: 'SET_IPA_LAYER_IMAGE',
|
actionType: 'SET_IPA_LAYER_IMAGE',
|
||||||
context: {
|
context: {
|
||||||
|
@ -1,19 +1,13 @@
|
|||||||
import { IconButton } from '@invoke-ai/ui-library';
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
|
||||||
import { stopPropagation } from 'common/util/stopPropagation';
|
import { stopPropagation } from 'common/util/stopPropagation';
|
||||||
import { layerDeleted } from 'features/controlLayers/store/controlLayersSlice';
|
import { memo } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||||
|
|
||||||
type Props = { layerId: string };
|
type Props = { onDelete: () => void };
|
||||||
|
|
||||||
export const LayerDeleteButton = memo(({ layerId }: Props) => {
|
export const EntityDeleteButton = memo(({ onDelete }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const deleteLayer = useCallback(() => {
|
|
||||||
dispatch(layerDeleted(layerId));
|
|
||||||
}, [dispatch, layerId]);
|
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -21,10 +15,10 @@ export const LayerDeleteButton = memo(({ layerId }: Props) => {
|
|||||||
aria-label={t('common.delete')}
|
aria-label={t('common.delete')}
|
||||||
tooltip={t('common.delete')}
|
tooltip={t('common.delete')}
|
||||||
icon={<PiTrashSimpleBold />}
|
icon={<PiTrashSimpleBold />}
|
||||||
onClick={deleteLayer}
|
onClick={onDelete}
|
||||||
onDoubleClick={stopPropagation} // double click expands the layer
|
onDoubleClick={stopPropagation} // double click expands the layer
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
LayerDeleteButton.displayName = 'LayerDeleteButton';
|
EntityDeleteButton.displayName = 'EntityDeleteButton';
|
@ -1,23 +1,16 @@
|
|||||||
import { IconButton } from '@invoke-ai/ui-library';
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
|
||||||
import { stopPropagation } from 'common/util/stopPropagation';
|
import { stopPropagation } from 'common/util/stopPropagation';
|
||||||
import { useLayerIsEnabled } from 'features/controlLayers/hooks/layerStateHooks';
|
import { memo } from 'react';
|
||||||
import { layerIsEnabledToggled } from 'features/controlLayers/store/controlLayersSlice';
|
|
||||||
import { memo, useCallback } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiCheckBold } from 'react-icons/pi';
|
import { PiCheckBold } from 'react-icons/pi';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
layerId: string;
|
isEnabled: boolean;
|
||||||
|
onToggle: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LayerIsEnabledToggle = memo(({ layerId }: Props) => {
|
export const EntityEnabledToggle = memo(({ isEnabled, onToggle }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const isEnabled = useLayerIsEnabled(layerId);
|
|
||||||
const onClick = useCallback(() => {
|
|
||||||
dispatch(layerIsEnabledToggled(layerId));
|
|
||||||
}, [dispatch, layerId]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
@ -26,11 +19,11 @@ export const LayerIsEnabledToggle = memo(({ layerId }: Props) => {
|
|||||||
tooltip={t(isEnabled ? 'common.enabled' : 'common.disabled')}
|
tooltip={t(isEnabled ? 'common.enabled' : 'common.disabled')}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
icon={isEnabled ? <PiCheckBold /> : undefined}
|
icon={isEnabled ? <PiCheckBold /> : undefined}
|
||||||
onClick={onClick}
|
onClick={onToggle}
|
||||||
colorScheme="base"
|
colorScheme="base"
|
||||||
onDoubleClick={stopPropagation} // double click expands the layer
|
onDoubleClick={stopPropagation} // double click expands the layer
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
LayerIsEnabledToggle.displayName = 'LayerVisibilityToggle';
|
EntityEnabledToggle.displayName = 'EntityEnabledToggle';
|
@ -11,7 +11,7 @@ import { PiArrowCounterClockwiseBold, PiDotsThreeVerticalBold, PiTrashSimpleBold
|
|||||||
|
|
||||||
type Props = { layerId: string };
|
type Props = { layerId: string };
|
||||||
|
|
||||||
export const LayerMenu = memo(({ layerId }: Props) => {
|
export const EntityMenu = memo(({ layerId }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const layerType = useLayerType(layerId);
|
const layerType = useLayerType(layerId);
|
||||||
@ -68,4 +68,4 @@ export const LayerMenu = memo(({ layerId }: Props) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
LayerMenu.displayName = 'LayerMenu';
|
EntityMenu.displayName = 'EntityMenu';
|
@ -0,0 +1,18 @@
|
|||||||
|
import { IconButton, MenuButton } from '@invoke-ai/ui-library';
|
||||||
|
import { stopPropagation } from 'common/util/stopPropagation';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { PiDotsThreeVerticalBold } from 'react-icons/pi';
|
||||||
|
|
||||||
|
export const EntityMenuButton = memo(() => {
|
||||||
|
return (
|
||||||
|
<MenuButton
|
||||||
|
as={IconButton}
|
||||||
|
aria-label="Layer menu"
|
||||||
|
size="sm"
|
||||||
|
icon={<PiDotsThreeVerticalBold />}
|
||||||
|
onDoubleClick={stopPropagation} // double click expands the layer
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
EntityMenuButton.displayName = 'EntityMenuButton';
|
@ -0,0 +1,16 @@
|
|||||||
|
import { Text } from '@invoke-ai/ui-library';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EntityTitle = memo(({ title }: Props) => {
|
||||||
|
return (
|
||||||
|
<Text size="sm" fontWeight="semibold" userSelect="none" color="base.300">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
EntityTitle.displayName = 'EntityTitle';
|
@ -1,7 +1,7 @@
|
|||||||
import { MenuItem } from '@invoke-ai/ui-library';
|
import { MenuItem } from '@invoke-ai/ui-library';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useAddIPAdapterToIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
import { useAddIPAdapterToRGLayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||||
import {
|
import {
|
||||||
regionalGuidanceNegativePromptChanged,
|
regionalGuidanceNegativePromptChanged,
|
||||||
regionalGuidancePositivePromptChanged,
|
regionalGuidancePositivePromptChanged,
|
||||||
@ -18,7 +18,7 @@ type Props = { layerId: string };
|
|||||||
export const LayerMenuRGActions = memo(({ layerId }: Props) => {
|
export const LayerMenuRGActions = memo(({ layerId }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToIPALayer(layerId);
|
const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToRGLayer(layerId);
|
||||||
const selectValidActions = useMemo(
|
const selectValidActions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
import { Text } from '@invoke-ai/ui-library';
|
|
||||||
import type { LayerData } from 'features/controlLayers/store/types';
|
|
||||||
import { memo, useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
type: LayerData['type'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LayerTitle = memo(({ type }: Props) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const title = useMemo(() => {
|
|
||||||
if (type === 'regional_guidance_layer') {
|
|
||||||
return t('controlLayers.regionalGuidance');
|
|
||||||
} else if (type === 'control_adapter_layer') {
|
|
||||||
return t('controlLayers.globalControlAdapter');
|
|
||||||
} else if (type === 'ip_adapter_layer') {
|
|
||||||
return t('controlLayers.globalIPAdapter');
|
|
||||||
} else if (type === 'initial_image_layer') {
|
|
||||||
return t('controlLayers.globalInitialImage');
|
|
||||||
} else if (type === 'raster_layer') {
|
|
||||||
return t('controlLayers.rasterLayer');
|
|
||||||
}
|
|
||||||
}, [t, type]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Text size="sm" fontWeight="semibold" userSelect="none" color="base.300">
|
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
LayerTitle.displayName = 'LayerTitle';
|
|
@ -4,9 +4,9 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { rgbColorToString } from 'features/canvas/util/colorToString';
|
import { rgbColorToString } from 'features/canvas/util/colorToString';
|
||||||
import { AddPromptButtons } from 'features/controlLayers/components/AddPromptButtons';
|
import { AddPromptButtons } from 'features/controlLayers/components/AddPromptButtons';
|
||||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||||
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
import { EntityMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
||||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
import { EntityTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||||
import { LayerIsEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
import { EntityEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||||
import { layerSelected, selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
import { layerSelected, selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { isRegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
import { isRegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
||||||
@ -52,8 +52,8 @@ export const RGLayer = memo(({ layerId }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<LayerWrapper onClick={onClick} borderColor={isSelected ? color : 'base.800'}>
|
<LayerWrapper onClick={onClick} borderColor={isSelected ? color : 'base.800'}>
|
||||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||||
<LayerIsEnabledToggle layerId={layerId} />
|
<EntityEnabledToggle layerId={layerId} />
|
||||||
<LayerTitle type="regional_guidance_layer" />
|
<EntityTitle type="regional_guidance_layer" />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
{autoNegative === 'invert' && (
|
{autoNegative === 'invert' && (
|
||||||
<Badge color="base.300" bg="transparent" borderWidth={1} userSelect="none">
|
<Badge color="base.300" bg="transparent" borderWidth={1} userSelect="none">
|
||||||
@ -62,12 +62,12 @@ export const RGLayer = memo(({ layerId }: Props) => {
|
|||||||
)}
|
)}
|
||||||
<RGLayerColorPicker layerId={layerId} />
|
<RGLayerColorPicker layerId={layerId} />
|
||||||
<RGLayerSettingsPopover layerId={layerId} />
|
<RGLayerSettingsPopover layerId={layerId} />
|
||||||
<LayerMenu layerId={layerId} />
|
<EntityMenu layerId={layerId} />
|
||||||
<LayerDeleteButton layerId={layerId} />
|
<LayerDeleteButton layerId={layerId} />
|
||||||
</Flex>
|
</Flex>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||||
{!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && <AddPromptButtons layerId={layerId} />}
|
{!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && <AddPromptButtons id={layerId} />}
|
||||||
{hasPositivePrompt && <RGLayerPositivePrompt layerId={layerId} />}
|
{hasPositivePrompt && <RGLayerPositivePrompt layerId={layerId} />}
|
||||||
{hasNegativePrompt && <RGLayerNegativePrompt layerId={layerId} />}
|
{hasNegativePrompt && <RGLayerNegativePrompt layerId={layerId} />}
|
||||||
{hasIPAdapters && <RGLayerIPAdapterList layerId={layerId} />}
|
{hasIPAdapters && <RGLayerIPAdapterList layerId={layerId} />}
|
||||||
|
@ -12,10 +12,10 @@ import {
|
|||||||
selectRGLayerIPAdapterOrThrow,
|
selectRGLayerIPAdapterOrThrow,
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||||
import type { RGLayerIPAdapterImageDropData } from 'features/dnd/types';
|
import type { RGIPAdapterImageDropData } from 'features/dnd/types';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||||
import type { ImageDTO, IPAdapterModelConfig, RGLayerIPAdapterImagePostUploadAction } from 'services/api/types';
|
import type { ImageDTO, IPAdapterModelConfig, RGIPAdapterImagePostUploadAction } from 'services/api/types';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
layerId: string;
|
layerId: string;
|
||||||
@ -78,7 +78,7 @@ export const RGLayerIPAdapterWrapper = memo(({ layerId, ipAdapterId, ipAdapterNu
|
|||||||
[dispatch, ipAdapterId, layerId]
|
[dispatch, ipAdapterId, layerId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const droppableData = useMemo<RGLayerIPAdapterImageDropData>(
|
const droppableData = useMemo<RGIPAdapterImageDropData>(
|
||||||
() => ({
|
() => ({
|
||||||
actionType: 'SET_RG_LAYER_IP_ADAPTER_IMAGE',
|
actionType: 'SET_RG_LAYER_IP_ADAPTER_IMAGE',
|
||||||
context: {
|
context: {
|
||||||
@ -90,7 +90,7 @@ export const RGLayerIPAdapterWrapper = memo(({ layerId, ipAdapterId, ipAdapterNu
|
|||||||
[ipAdapterId, layerId]
|
[ipAdapterId, layerId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const postUploadAction = useMemo<RGLayerIPAdapterImagePostUploadAction>(
|
const postUploadAction = useMemo<RGIPAdapterImagePostUploadAction>(
|
||||||
() => ({
|
() => ({
|
||||||
type: 'SET_RG_LAYER_IP_ADAPTER_IMAGE',
|
type: 'SET_RG_LAYER_IP_ADAPTER_IMAGE',
|
||||||
layerId,
|
layerId,
|
||||||
|
@ -2,14 +2,14 @@ import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIDroppable from 'common/components/IAIDroppable';
|
import IAIDroppable from 'common/components/IAIDroppable';
|
||||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||||
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
import { EntityMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
||||||
import { LayerOpacity } from 'features/controlLayers/components/LayerCommon/LayerOpacity';
|
import { LayerOpacity } from 'features/controlLayers/components/LayerCommon/LayerOpacity';
|
||||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
import { EntityTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||||
import { LayerIsEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
import { EntityEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||||
import { layerSelected, selectLayerOrThrow } from 'features/controlLayers/store/controlLayersSlice';
|
import { layerSelected, selectLayerOrThrow } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { isRasterLayer } from 'features/controlLayers/store/types';
|
import { isRasterLayer } from 'features/controlLayers/store/types';
|
||||||
import type { RasterLayerImageDropData } from 'features/dnd/types';
|
import type { LayerImageDropData } from 'features/dnd/types';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -27,7 +27,7 @@ export const RasterLayer = memo(({ layerId }: Props) => {
|
|||||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||||
|
|
||||||
const droppableData = useMemo(() => {
|
const droppableData = useMemo(() => {
|
||||||
const _droppableData: RasterLayerImageDropData = {
|
const _droppableData: LayerImageDropData = {
|
||||||
id: layerId,
|
id: layerId,
|
||||||
actionType: 'ADD_RASTER_LAYER_IMAGE',
|
actionType: 'ADD_RASTER_LAYER_IMAGE',
|
||||||
context: { layerId },
|
context: { layerId },
|
||||||
@ -38,11 +38,11 @@ export const RasterLayer = memo(({ layerId }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}>
|
<LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}>
|
||||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||||
<LayerIsEnabledToggle layerId={layerId} />
|
<EntityEnabledToggle layerId={layerId} />
|
||||||
<LayerTitle type="raster_layer" />
|
<EntityTitle type="raster_layer" />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<LayerOpacity layerId={layerId} />
|
<LayerOpacity layerId={layerId} />
|
||||||
<LayerMenu layerId={layerId} />
|
<EntityMenu layerId={layerId} />
|
||||||
<LayerDeleteButton layerId={layerId} />
|
<LayerDeleteButton layerId={layerId} />
|
||||||
</Flex>
|
</Flex>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import { caAdded } from 'features/controlLayers/store/controlAdaptersSlice';
|
||||||
controlAdapterAdded,
|
import { ipaAdded } from 'features/controlLayers/store/ipAdaptersSlice';
|
||||||
iiLayerAdded,
|
import { rgIPAdapterAdded } from 'features/controlLayers/store/regionalGuidanceSlice';
|
||||||
ipAdapterAdded,
|
|
||||||
regionalGuidanceIPAdapterAdded,
|
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
|
||||||
import { isInitialImageLayer } from 'features/controlLayers/store/types';
|
|
||||||
import {
|
import {
|
||||||
buildControlNet,
|
buildControlNet,
|
||||||
buildIPAdapter,
|
buildIPAdapter,
|
||||||
buildT2IAdapter,
|
buildT2IAdapter,
|
||||||
CA_PROCESSOR_DATA,
|
CA_PROCESSOR_DATA,
|
||||||
isProcessorTypeV2,
|
isProcessorTypeV2,
|
||||||
} from 'features/controlLayers/util/controlAdapters';
|
} from 'features/controlLayers/store/types';
|
||||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useControlNetAndT2IAdapterModels, useIPAdapterModels } from 'services/api/hooks/modelsByType';
|
import { useControlNetAndT2IAdapterModels, useIPAdapterModels } from 'services/api/hooks/modelsByType';
|
||||||
@ -46,7 +42,7 @@ export const useAddCALayer = () => {
|
|||||||
processorConfig,
|
processorConfig,
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(controlAdapterAdded(controlAdapter));
|
dispatch(caAdded(controlAdapter));
|
||||||
}, [dispatch, model, baseModel]);
|
}, [dispatch, model, baseModel]);
|
||||||
|
|
||||||
return [addCALayer, isDisabled] as const;
|
return [addCALayer, isDisabled] as const;
|
||||||
@ -70,13 +66,13 @@ export const useAddIPALayer = () => {
|
|||||||
const ipAdapter = buildIPAdapter(id, {
|
const ipAdapter = buildIPAdapter(id, {
|
||||||
model: zModelIdentifierField.parse(model),
|
model: zModelIdentifierField.parse(model),
|
||||||
});
|
});
|
||||||
dispatch(ipAdapterAdded(ipAdapter));
|
dispatch(ipaAdded(ipAdapter));
|
||||||
}, [dispatch, model]);
|
}, [dispatch, model]);
|
||||||
|
|
||||||
return [addIPALayer, isDisabled] as const;
|
return [addIPALayer, isDisabled] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAddIPAdapterToIPALayer = (layerId: string) => {
|
export const useAddIPAdapterToRGLayer = (id: string) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const baseModel = useAppSelector((s) => s.generation.model?.base);
|
const baseModel = useAppSelector((s) => s.generation.model?.base);
|
||||||
const [modelConfigs] = useIPAdapterModels();
|
const [modelConfigs] = useIPAdapterModels();
|
||||||
@ -90,22 +86,11 @@ export const useAddIPAdapterToIPALayer = (layerId: string) => {
|
|||||||
if (!model) {
|
if (!model) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const id = uuidv4();
|
const ipAdapter = buildIPAdapter(uuidv4(), {
|
||||||
const ipAdapter = buildIPAdapter(id, {
|
|
||||||
model: zModelIdentifierField.parse(model),
|
model: zModelIdentifierField.parse(model),
|
||||||
});
|
});
|
||||||
dispatch(regionalGuidanceIPAdapterAdded({ layerId, ipAdapter }));
|
dispatch(rgIPAdapterAdded({ id, ipAdapter: { ...ipAdapter, id: uuidv4(), type: 'ip_adapter', isEnabled: true } }));
|
||||||
}, [dispatch, model, layerId]);
|
}, [model, dispatch, id]);
|
||||||
|
|
||||||
return [addIPAdapter, isDisabled] as const;
|
return [addIPAdapter, isDisabled] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAddIILayer = () => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const isDisabled = useAppSelector((s) => Boolean(s.canvasV2.layers.find(isInitialImageLayer)));
|
|
||||||
const addIILayer = useCallback(() => {
|
|
||||||
dispatch(iiLayerAdded(null));
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
return [addIILayer, isDisabled] as const;
|
|
||||||
};
|
|
||||||
|
@ -108,7 +108,7 @@ const updateCALayerImageAttrs = (stage: Konva.Stage, konvaImage: Konva.Image, ca
|
|||||||
scaleX: 1,
|
scaleX: 1,
|
||||||
scaleY: 1,
|
scaleY: 1,
|
||||||
visible: ca.isEnabled,
|
visible: ca.isEnabled,
|
||||||
filters: ca.filter === LightnessToAlphaFilter.name ? [LightnessToAlphaFilter] : [],
|
filters: ca.filter === 'LightnessToAlphaFilter' ? [LightnessToAlphaFilter] : [],
|
||||||
});
|
});
|
||||||
needsCache = true;
|
needsCache = true;
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,14 @@ import type { PayloadAction } from '@reduxjs/toolkit';
|
|||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import type { PersistConfig, RootState } from 'app/store/store';
|
import type { PersistConfig, RootState } from 'app/store/store';
|
||||||
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
||||||
import type { ControlModeV2, ProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
|
||||||
import { buildControlAdapterProcessorV2, imageDTOToImageWithDims } from 'features/controlLayers/util/controlAdapters';
|
|
||||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import type { IRect } from 'konva/lib/types';
|
import type { IRect } from 'konva/lib/types';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import type { ControlNetModelConfig, ImageDTO, T2IAdapterModelConfig } from 'services/api/types';
|
import type { ControlNetModelConfig, ImageDTO, T2IAdapterModelConfig } from 'services/api/types';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import type { ControlAdapterConfig, ControlAdapterData, Filter } from './types';
|
import type { ControlAdapterConfig, ControlAdapterData, ControlModeV2, Filter, ProcessorConfig } from './types';
|
||||||
|
import { buildControlAdapterProcessorV2, imageDTOToImageWithDims } from './types';
|
||||||
|
|
||||||
type ControlAdaptersV2State = {
|
type ControlAdaptersV2State = {
|
||||||
_version: 1;
|
_version: 1;
|
||||||
@ -22,7 +21,7 @@ const initialState: ControlAdaptersV2State = {
|
|||||||
controlAdapters: [],
|
controlAdapters: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectCa = (state: ControlAdaptersV2State, id: string) => state.controlAdapters.find((ca) => ca.id === id);
|
export const selectCA = (state: ControlAdaptersV2State, id: string) => state.controlAdapters.find((ca) => ca.id === id);
|
||||||
|
|
||||||
export const controlAdaptersV2Slice = createSlice({
|
export const controlAdaptersV2Slice = createSlice({
|
||||||
name: 'controlAdaptersV2',
|
name: 'controlAdaptersV2',
|
||||||
@ -40,7 +39,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
bboxNeedsUpdate: false,
|
bboxNeedsUpdate: false,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
filter: 'lightness_to_alpha',
|
filter: 'LightnessToAlphaFilter',
|
||||||
processorPendingBatchId: null,
|
processorPendingBatchId: null,
|
||||||
...config,
|
...config,
|
||||||
});
|
});
|
||||||
@ -52,17 +51,17 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
caRecalled: (state, action: PayloadAction<{ data: ControlAdapterData }>) => {
|
caRecalled: (state, action: PayloadAction<{ data: ControlAdapterData }>) => {
|
||||||
state.controlAdapters.push(action.payload.data);
|
state.controlAdapters.push(action.payload.data);
|
||||||
},
|
},
|
||||||
caIsEnabledChanged: (state, action: PayloadAction<{ id: string; isEnabled: boolean }>) => {
|
caIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => {
|
||||||
const { id, isEnabled } = action.payload;
|
const { id } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ca.isEnabled = isEnabled;
|
ca.isEnabled = !ca.isEnabled;
|
||||||
},
|
},
|
||||||
caTranslated: (state, action: PayloadAction<{ id: string; x: number; y: number }>) => {
|
caTranslated: (state, action: PayloadAction<{ id: string; x: number; y: number }>) => {
|
||||||
const { id, x, y } = action.payload;
|
const { id, x, y } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -71,7 +70,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
},
|
},
|
||||||
caBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => {
|
caBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => {
|
||||||
const { id, bbox } = action.payload;
|
const { id, bbox } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -84,7 +83,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
},
|
},
|
||||||
caOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => {
|
caOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => {
|
||||||
const { id, opacity } = action.payload;
|
const { id, opacity } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -92,7 +91,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
},
|
},
|
||||||
caMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
caMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||||
const { id } = action.payload;
|
const { id } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -100,7 +99,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
},
|
},
|
||||||
caMovedToFront: (state, action: PayloadAction<{ id: string }>) => {
|
caMovedToFront: (state, action: PayloadAction<{ id: string }>) => {
|
||||||
const { id } = action.payload;
|
const { id } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -108,7 +107,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
},
|
},
|
||||||
caMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
caMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||||
const { id } = action.payload;
|
const { id } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -116,7 +115,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
},
|
},
|
||||||
caMovedToBack: (state, action: PayloadAction<{ id: string }>) => {
|
caMovedToBack: (state, action: PayloadAction<{ id: string }>) => {
|
||||||
const { id } = action.payload;
|
const { id } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -124,7 +123,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
},
|
},
|
||||||
caImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => {
|
caImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => {
|
||||||
const { id, imageDTO } = action.payload;
|
const { id, imageDTO } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -145,7 +144,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
},
|
},
|
||||||
caProcessedImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => {
|
caProcessedImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => {
|
||||||
const { id, imageDTO } = action.payload;
|
const { id, imageDTO } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -162,7 +161,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
}>
|
}>
|
||||||
) => {
|
) => {
|
||||||
const { id, modelConfig } = action.payload;
|
const { id, modelConfig } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -189,7 +188,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
},
|
},
|
||||||
caControlModeChanged: (state, action: PayloadAction<{ id: string; controlMode: ControlModeV2 }>) => {
|
caControlModeChanged: (state, action: PayloadAction<{ id: string; controlMode: ControlModeV2 }>) => {
|
||||||
const { id, controlMode } = action.payload;
|
const { id, controlMode } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -200,7 +199,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
action: PayloadAction<{ id: string; processorConfig: ProcessorConfig | null }>
|
action: PayloadAction<{ id: string; processorConfig: ProcessorConfig | null }>
|
||||||
) => {
|
) => {
|
||||||
const { id, processorConfig } = action.payload;
|
const { id, processorConfig } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -211,7 +210,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
},
|
},
|
||||||
caFilterChanged: (state, action: PayloadAction<{ id: string; filter: Filter }>) => {
|
caFilterChanged: (state, action: PayloadAction<{ id: string; filter: Filter }>) => {
|
||||||
const { id, filter } = action.payload;
|
const { id, filter } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -219,7 +218,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
},
|
},
|
||||||
caProcessorPendingBatchIdChanged: (state, action: PayloadAction<{ id: string; batchId: string | null }>) => {
|
caProcessorPendingBatchIdChanged: (state, action: PayloadAction<{ id: string; batchId: string | null }>) => {
|
||||||
const { id, batchId } = action.payload;
|
const { id, batchId } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -227,7 +226,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
},
|
},
|
||||||
caWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => {
|
caWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => {
|
||||||
const { id, weight } = action.payload;
|
const { id, weight } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -235,7 +234,7 @@ export const controlAdaptersV2Slice = createSlice({
|
|||||||
},
|
},
|
||||||
caBeginEndStepPctChanged: (state, action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }>) => {
|
caBeginEndStepPctChanged: (state, action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }>) => {
|
||||||
const { id, beginEndStepPct } = action.payload;
|
const { id, beginEndStepPct } = action.payload;
|
||||||
const ca = selectCa(state, id);
|
const ca = selectCA(state, id);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -248,7 +247,7 @@ export const {
|
|||||||
caAdded,
|
caAdded,
|
||||||
caBboxChanged,
|
caBboxChanged,
|
||||||
caDeleted,
|
caDeleted,
|
||||||
caIsEnabledChanged,
|
caIsEnabledToggled,
|
||||||
caMovedBackwardOne,
|
caMovedBackwardOne,
|
||||||
caMovedForwardOne,
|
caMovedForwardOne,
|
||||||
caMovedToBack,
|
caMovedToBack,
|
||||||
|
@ -11,7 +11,7 @@ import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/
|
|||||||
import type { IRect, Vector2d } from 'konva/lib/types';
|
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||||
import { atom } from 'nanostores';
|
import { atom } from 'nanostores';
|
||||||
|
|
||||||
import type { CanvasEntity, CanvasV2State, RgbaColor, StageAttrs, Tool } from './types';
|
import type { CanvasEntity, CanvasEntityIdentifier, CanvasV2State, RgbaColor, StageAttrs, Tool } from './types';
|
||||||
import { DEFAULT_RGBA_COLOR } from './types';
|
import { DEFAULT_RGBA_COLOR } from './types';
|
||||||
|
|
||||||
const initialState: CanvasV2State = {
|
const initialState: CanvasV2State = {
|
||||||
@ -110,6 +110,9 @@ export const canvasV2Slice = createSlice({
|
|||||||
toolBufferChanged: (state, action: PayloadAction<Tool | null>) => {
|
toolBufferChanged: (state, action: PayloadAction<Tool | null>) => {
|
||||||
state.tool.selectedBuffer = action.payload;
|
state.tool.selectedBuffer = action.payload;
|
||||||
},
|
},
|
||||||
|
entitySelected: (state, action: PayloadAction<CanvasEntityIdentifier>) => {
|
||||||
|
state.selectedEntityIdentifier = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers(builder) {
|
extraReducers(builder) {
|
||||||
builder.addCase(modelChanged, (state, action) => {
|
builder.addCase(modelChanged, (state, action) => {
|
||||||
@ -145,6 +148,7 @@ export const {
|
|||||||
invertScrollChanged,
|
invertScrollChanged,
|
||||||
toolChanged,
|
toolChanged,
|
||||||
toolBufferChanged,
|
toolBufferChanged,
|
||||||
|
entitySelected,
|
||||||
} = canvasV2Slice.actions;
|
} = canvasV2Slice.actions;
|
||||||
|
|
||||||
export const selectCanvasV2Slice = (state: RootState) => state.canvasV2;
|
export const selectCanvasV2Slice = (state: RootState) => state.canvasV2;
|
||||||
|
@ -3,8 +3,8 @@ import { createSlice } from '@reduxjs/toolkit';
|
|||||||
import type { PersistConfig, RootState } from 'app/store/store';
|
import type { PersistConfig, RootState } from 'app/store/store';
|
||||||
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
||||||
import { getBrushLineId, getEraserLineId, getRectShapeId } from 'features/controlLayers/konva/naming';
|
import { getBrushLineId, getEraserLineId, getRectShapeId } from 'features/controlLayers/konva/naming';
|
||||||
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
|
||||||
import { imageDTOToImageWithDims } from 'features/controlLayers/util/controlAdapters';
|
import { imageDTOToImageWithDims } from 'features/controlLayers/store/types';
|
||||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas';
|
import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas';
|
||||||
import type { IRect } from 'konva/lib/types';
|
import type { IRect } from 'konva/lib/types';
|
||||||
|
@ -24,7 +24,7 @@ import type {
|
|||||||
ProcessorConfig,
|
ProcessorConfig,
|
||||||
ProcessorTypeV2,
|
ProcessorTypeV2,
|
||||||
ZoeDepthProcessorConfig,
|
ZoeDepthProcessorConfig,
|
||||||
} from './controlAdapters';
|
} from './types';
|
||||||
|
|
||||||
describe('Control Adapter Types', () => {
|
describe('Control Adapter Types', () => {
|
||||||
test('ProcessorType', () => {
|
test('ProcessorType', () => {
|
@ -1,13 +1,4 @@
|
|||||||
import { LightnessToAlphaFilter } from 'features/controlLayers/konva/filters';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
import {
|
|
||||||
zBeginEndStepPct,
|
|
||||||
zCLIPVisionModelV2,
|
|
||||||
zControlModeV2,
|
|
||||||
zId,
|
|
||||||
zImageWithDims,
|
|
||||||
zIPMethodV2,
|
|
||||||
zProcessorConfig,
|
|
||||||
} from 'features/controlLayers/util/controlAdapters';
|
|
||||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||||
import type {
|
import type {
|
||||||
@ -24,11 +15,442 @@ import {
|
|||||||
zParameterPositivePrompt,
|
zParameterPositivePrompt,
|
||||||
} from 'features/parameters/types/parameterSchemas';
|
} from 'features/parameters/types/parameterSchemas';
|
||||||
import type { IRect } from 'konva/lib/types';
|
import type { IRect } from 'konva/lib/types';
|
||||||
import type { ImageDTO } from 'services/api/types';
|
import { merge } from 'lodash-es';
|
||||||
|
import type {
|
||||||
|
AnyInvocation,
|
||||||
|
BaseModelType,
|
||||||
|
ControlNetModelConfig,
|
||||||
|
ImageDTO,
|
||||||
|
T2IAdapterModelConfig,
|
||||||
|
} from 'services/api/types';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const zId = z.string().min(1);
|
||||||
|
|
||||||
|
export const zImageWithDims = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
width: z.number().int().positive(),
|
||||||
|
height: z.number().int().positive(),
|
||||||
|
});
|
||||||
|
export type ImageWithDims = z.infer<typeof zImageWithDims>;
|
||||||
|
|
||||||
|
export const zBeginEndStepPct = z
|
||||||
|
.tuple([z.number().gte(0).lte(1), z.number().gte(0).lte(1)])
|
||||||
|
.refine(([begin, end]) => begin < end, {
|
||||||
|
message: 'Begin must be less than end',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const zControlModeV2 = z.enum(['balanced', 'more_prompt', 'more_control', 'unbalanced']);
|
||||||
|
export type ControlModeV2 = z.infer<typeof zControlModeV2>;
|
||||||
|
export const isControlModeV2 = (v: unknown): v is ControlModeV2 => zControlModeV2.safeParse(v).success;
|
||||||
|
|
||||||
|
export const zCLIPVisionModelV2 = z.enum(['ViT-H', 'ViT-G']);
|
||||||
|
export type CLIPVisionModelV2 = z.infer<typeof zCLIPVisionModelV2>;
|
||||||
|
export const isCLIPVisionModelV2 = (v: unknown): v is CLIPVisionModelV2 => zCLIPVisionModelV2.safeParse(v).success;
|
||||||
|
|
||||||
|
export const zIPMethodV2 = z.enum(['full', 'style', 'composition']);
|
||||||
|
export type IPMethodV2 = z.infer<typeof zIPMethodV2>;
|
||||||
|
export const isIPMethodV2 = (v: unknown): v is IPMethodV2 => zIPMethodV2.safeParse(v).success;
|
||||||
|
|
||||||
|
const zCannyProcessorConfig = z.object({
|
||||||
|
id: zId,
|
||||||
|
type: z.literal('canny_image_processor'),
|
||||||
|
low_threshold: z.number().int().gte(0).lte(255),
|
||||||
|
high_threshold: z.number().int().gte(0).lte(255),
|
||||||
|
});
|
||||||
|
export type CannyProcessorConfig = z.infer<typeof zCannyProcessorConfig>;
|
||||||
|
|
||||||
|
const zColorMapProcessorConfig = z.object({
|
||||||
|
id: zId,
|
||||||
|
type: z.literal('color_map_image_processor'),
|
||||||
|
color_map_tile_size: z.number().int().gte(1),
|
||||||
|
});
|
||||||
|
export type ColorMapProcessorConfig = z.infer<typeof zColorMapProcessorConfig>;
|
||||||
|
|
||||||
|
const zContentShuffleProcessorConfig = z.object({
|
||||||
|
id: zId,
|
||||||
|
type: z.literal('content_shuffle_image_processor'),
|
||||||
|
w: z.number().int().gte(0),
|
||||||
|
h: z.number().int().gte(0),
|
||||||
|
f: z.number().int().gte(0),
|
||||||
|
});
|
||||||
|
export type ContentShuffleProcessorConfig = z.infer<typeof zContentShuffleProcessorConfig>;
|
||||||
|
|
||||||
|
const zDepthAnythingModelSize = z.enum(['large', 'base', 'small']);
|
||||||
|
export type DepthAnythingModelSize = z.infer<typeof zDepthAnythingModelSize>;
|
||||||
|
export const isDepthAnythingModelSize = (v: unknown): v is DepthAnythingModelSize =>
|
||||||
|
zDepthAnythingModelSize.safeParse(v).success;
|
||||||
|
const zDepthAnythingProcessorConfig = z.object({
|
||||||
|
id: zId,
|
||||||
|
type: z.literal('depth_anything_image_processor'),
|
||||||
|
model_size: zDepthAnythingModelSize,
|
||||||
|
});
|
||||||
|
export type DepthAnythingProcessorConfig = z.infer<typeof zDepthAnythingProcessorConfig>;
|
||||||
|
|
||||||
|
const zHedProcessorConfig = z.object({
|
||||||
|
id: zId,
|
||||||
|
type: z.literal('hed_image_processor'),
|
||||||
|
scribble: z.boolean(),
|
||||||
|
});
|
||||||
|
export type HedProcessorConfig = z.infer<typeof zHedProcessorConfig>;
|
||||||
|
|
||||||
|
const zLineartAnimeProcessorConfig = z.object({
|
||||||
|
id: zId,
|
||||||
|
type: z.literal('lineart_anime_image_processor'),
|
||||||
|
});
|
||||||
|
export type LineartAnimeProcessorConfig = z.infer<typeof zLineartAnimeProcessorConfig>;
|
||||||
|
|
||||||
|
const zLineartProcessorConfig = z.object({
|
||||||
|
id: zId,
|
||||||
|
type: z.literal('lineart_image_processor'),
|
||||||
|
coarse: z.boolean(),
|
||||||
|
});
|
||||||
|
export type LineartProcessorConfig = z.infer<typeof zLineartProcessorConfig>;
|
||||||
|
|
||||||
|
const zMediapipeFaceProcessorConfig = z.object({
|
||||||
|
id: zId,
|
||||||
|
type: z.literal('mediapipe_face_processor'),
|
||||||
|
max_faces: z.number().int().gte(1),
|
||||||
|
min_confidence: z.number().gte(0).lte(1),
|
||||||
|
});
|
||||||
|
export type MediapipeFaceProcessorConfig = z.infer<typeof zMediapipeFaceProcessorConfig>;
|
||||||
|
|
||||||
|
const zMidasDepthProcessorConfig = z.object({
|
||||||
|
id: zId,
|
||||||
|
type: z.literal('midas_depth_image_processor'),
|
||||||
|
a_mult: z.number().gte(0),
|
||||||
|
bg_th: z.number().gte(0),
|
||||||
|
});
|
||||||
|
export type MidasDepthProcessorConfig = z.infer<typeof zMidasDepthProcessorConfig>;
|
||||||
|
|
||||||
|
const zMlsdProcessorConfig = z.object({
|
||||||
|
id: zId,
|
||||||
|
type: z.literal('mlsd_image_processor'),
|
||||||
|
thr_v: z.number().gte(0),
|
||||||
|
thr_d: z.number().gte(0),
|
||||||
|
});
|
||||||
|
export type MlsdProcessorConfig = z.infer<typeof zMlsdProcessorConfig>;
|
||||||
|
|
||||||
|
const zNormalbaeProcessorConfig = z.object({
|
||||||
|
id: zId,
|
||||||
|
type: z.literal('normalbae_image_processor'),
|
||||||
|
});
|
||||||
|
export type NormalbaeProcessorConfig = z.infer<typeof zNormalbaeProcessorConfig>;
|
||||||
|
|
||||||
|
const zDWOpenposeProcessorConfig = z.object({
|
||||||
|
id: zId,
|
||||||
|
type: z.literal('dw_openpose_image_processor'),
|
||||||
|
draw_body: z.boolean(),
|
||||||
|
draw_face: z.boolean(),
|
||||||
|
draw_hands: z.boolean(),
|
||||||
|
});
|
||||||
|
export type DWOpenposeProcessorConfig = z.infer<typeof zDWOpenposeProcessorConfig>;
|
||||||
|
|
||||||
|
const zPidiProcessorConfig = z.object({
|
||||||
|
id: zId,
|
||||||
|
type: z.literal('pidi_image_processor'),
|
||||||
|
safe: z.boolean(),
|
||||||
|
scribble: z.boolean(),
|
||||||
|
});
|
||||||
|
export type PidiProcessorConfig = z.infer<typeof zPidiProcessorConfig>;
|
||||||
|
|
||||||
|
const zZoeDepthProcessorConfig = z.object({
|
||||||
|
id: zId,
|
||||||
|
type: z.literal('zoe_depth_image_processor'),
|
||||||
|
});
|
||||||
|
export type ZoeDepthProcessorConfig = z.infer<typeof zZoeDepthProcessorConfig>;
|
||||||
|
|
||||||
|
export const zProcessorConfig = z.discriminatedUnion('type', [
|
||||||
|
zCannyProcessorConfig,
|
||||||
|
zColorMapProcessorConfig,
|
||||||
|
zContentShuffleProcessorConfig,
|
||||||
|
zDepthAnythingProcessorConfig,
|
||||||
|
zHedProcessorConfig,
|
||||||
|
zLineartAnimeProcessorConfig,
|
||||||
|
zLineartProcessorConfig,
|
||||||
|
zMediapipeFaceProcessorConfig,
|
||||||
|
zMidasDepthProcessorConfig,
|
||||||
|
zMlsdProcessorConfig,
|
||||||
|
zNormalbaeProcessorConfig,
|
||||||
|
zDWOpenposeProcessorConfig,
|
||||||
|
zPidiProcessorConfig,
|
||||||
|
zZoeDepthProcessorConfig,
|
||||||
|
]);
|
||||||
|
export type ProcessorConfig = z.infer<typeof zProcessorConfig>;
|
||||||
|
|
||||||
|
const zProcessorTypeV2 = z.enum([
|
||||||
|
'canny_image_processor',
|
||||||
|
'color_map_image_processor',
|
||||||
|
'content_shuffle_image_processor',
|
||||||
|
'depth_anything_image_processor',
|
||||||
|
'hed_image_processor',
|
||||||
|
'lineart_anime_image_processor',
|
||||||
|
'lineart_image_processor',
|
||||||
|
'mediapipe_face_processor',
|
||||||
|
'midas_depth_image_processor',
|
||||||
|
'mlsd_image_processor',
|
||||||
|
'normalbae_image_processor',
|
||||||
|
'dw_openpose_image_processor',
|
||||||
|
'pidi_image_processor',
|
||||||
|
'zoe_depth_image_processor',
|
||||||
|
]);
|
||||||
|
export type ProcessorTypeV2 = z.infer<typeof zProcessorTypeV2>;
|
||||||
|
export const isProcessorTypeV2 = (v: unknown): v is ProcessorTypeV2 => zProcessorTypeV2.safeParse(v).success;
|
||||||
|
|
||||||
|
type ProcessorData<T extends ProcessorTypeV2> = {
|
||||||
|
type: T;
|
||||||
|
labelTKey: string;
|
||||||
|
descriptionTKey: string;
|
||||||
|
buildDefaults(baseModel?: BaseModelType): Extract<ProcessorConfig, { type: T }>;
|
||||||
|
buildNode(image: ImageWithDims, config: Extract<ProcessorConfig, { type: T }>): Extract<AnyInvocation, { type: T }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const minDim = (image: ImageWithDims): number => Math.min(image.width, image.height);
|
||||||
|
|
||||||
|
type CAProcessorsData = {
|
||||||
|
[key in ProcessorTypeV2]: ProcessorData<key>;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* A dict of ControlNet processors, including:
|
||||||
|
* - label translation key
|
||||||
|
* - description translation key
|
||||||
|
* - a builder to create default values for the config
|
||||||
|
* - a builder to create the node for the config
|
||||||
|
*
|
||||||
|
* TODO: Generate from the OpenAPI schema
|
||||||
|
*/
|
||||||
|
export const CA_PROCESSOR_DATA: CAProcessorsData = {
|
||||||
|
canny_image_processor: {
|
||||||
|
type: 'canny_image_processor',
|
||||||
|
labelTKey: 'controlnet.canny',
|
||||||
|
descriptionTKey: 'controlnet.cannyDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: 'canny_image_processor',
|
||||||
|
type: 'canny_image_processor',
|
||||||
|
low_threshold: 100,
|
||||||
|
high_threshold: 200,
|
||||||
|
}),
|
||||||
|
buildNode: (image, config) => ({
|
||||||
|
...config,
|
||||||
|
type: 'canny_image_processor',
|
||||||
|
image: { image_name: image.name },
|
||||||
|
detect_resolution: minDim(image),
|
||||||
|
image_resolution: minDim(image),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
color_map_image_processor: {
|
||||||
|
type: 'color_map_image_processor',
|
||||||
|
labelTKey: 'controlnet.colorMap',
|
||||||
|
descriptionTKey: 'controlnet.colorMapDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: 'color_map_image_processor',
|
||||||
|
type: 'color_map_image_processor',
|
||||||
|
color_map_tile_size: 64,
|
||||||
|
}),
|
||||||
|
buildNode: (image, config) => ({
|
||||||
|
...config,
|
||||||
|
type: 'color_map_image_processor',
|
||||||
|
image: { image_name: image.name },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
content_shuffle_image_processor: {
|
||||||
|
type: 'content_shuffle_image_processor',
|
||||||
|
labelTKey: 'controlnet.contentShuffle',
|
||||||
|
descriptionTKey: 'controlnet.contentShuffleDescription',
|
||||||
|
buildDefaults: (baseModel) => ({
|
||||||
|
id: 'content_shuffle_image_processor',
|
||||||
|
type: 'content_shuffle_image_processor',
|
||||||
|
h: baseModel === 'sdxl' ? 1024 : 512,
|
||||||
|
w: baseModel === 'sdxl' ? 1024 : 512,
|
||||||
|
f: baseModel === 'sdxl' ? 512 : 256,
|
||||||
|
}),
|
||||||
|
buildNode: (image, config) => ({
|
||||||
|
...config,
|
||||||
|
image: { image_name: image.name },
|
||||||
|
detect_resolution: minDim(image),
|
||||||
|
image_resolution: minDim(image),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
depth_anything_image_processor: {
|
||||||
|
type: 'depth_anything_image_processor',
|
||||||
|
labelTKey: 'controlnet.depthAnything',
|
||||||
|
descriptionTKey: 'controlnet.depthAnythingDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: 'depth_anything_image_processor',
|
||||||
|
type: 'depth_anything_image_processor',
|
||||||
|
model_size: 'small',
|
||||||
|
}),
|
||||||
|
buildNode: (image, config) => ({
|
||||||
|
...config,
|
||||||
|
image: { image_name: image.name },
|
||||||
|
resolution: minDim(image),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
hed_image_processor: {
|
||||||
|
type: 'hed_image_processor',
|
||||||
|
labelTKey: 'controlnet.hed',
|
||||||
|
descriptionTKey: 'controlnet.hedDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: 'hed_image_processor',
|
||||||
|
type: 'hed_image_processor',
|
||||||
|
scribble: false,
|
||||||
|
}),
|
||||||
|
buildNode: (image, config) => ({
|
||||||
|
...config,
|
||||||
|
image: { image_name: image.name },
|
||||||
|
detect_resolution: minDim(image),
|
||||||
|
image_resolution: minDim(image),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
lineart_anime_image_processor: {
|
||||||
|
type: 'lineart_anime_image_processor',
|
||||||
|
labelTKey: 'controlnet.lineartAnime',
|
||||||
|
descriptionTKey: 'controlnet.lineartAnimeDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: 'lineart_anime_image_processor',
|
||||||
|
type: 'lineart_anime_image_processor',
|
||||||
|
}),
|
||||||
|
buildNode: (image, config) => ({
|
||||||
|
...config,
|
||||||
|
image: { image_name: image.name },
|
||||||
|
detect_resolution: minDim(image),
|
||||||
|
image_resolution: minDim(image),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
lineart_image_processor: {
|
||||||
|
type: 'lineart_image_processor',
|
||||||
|
labelTKey: 'controlnet.lineart',
|
||||||
|
descriptionTKey: 'controlnet.lineartDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: 'lineart_image_processor',
|
||||||
|
type: 'lineart_image_processor',
|
||||||
|
coarse: false,
|
||||||
|
}),
|
||||||
|
buildNode: (image, config) => ({
|
||||||
|
...config,
|
||||||
|
image: { image_name: image.name },
|
||||||
|
detect_resolution: minDim(image),
|
||||||
|
image_resolution: minDim(image),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
mediapipe_face_processor: {
|
||||||
|
type: 'mediapipe_face_processor',
|
||||||
|
labelTKey: 'controlnet.mediapipeFace',
|
||||||
|
descriptionTKey: 'controlnet.mediapipeFaceDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: 'mediapipe_face_processor',
|
||||||
|
type: 'mediapipe_face_processor',
|
||||||
|
max_faces: 1,
|
||||||
|
min_confidence: 0.5,
|
||||||
|
}),
|
||||||
|
buildNode: (image, config) => ({
|
||||||
|
...config,
|
||||||
|
image: { image_name: image.name },
|
||||||
|
detect_resolution: minDim(image),
|
||||||
|
image_resolution: minDim(image),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
midas_depth_image_processor: {
|
||||||
|
type: 'midas_depth_image_processor',
|
||||||
|
labelTKey: 'controlnet.depthMidas',
|
||||||
|
descriptionTKey: 'controlnet.depthMidasDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: 'midas_depth_image_processor',
|
||||||
|
type: 'midas_depth_image_processor',
|
||||||
|
a_mult: 2,
|
||||||
|
bg_th: 0.1,
|
||||||
|
}),
|
||||||
|
buildNode: (image, config) => ({
|
||||||
|
...config,
|
||||||
|
image: { image_name: image.name },
|
||||||
|
detect_resolution: minDim(image),
|
||||||
|
image_resolution: minDim(image),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
mlsd_image_processor: {
|
||||||
|
type: 'mlsd_image_processor',
|
||||||
|
labelTKey: 'controlnet.mlsd',
|
||||||
|
descriptionTKey: 'controlnet.mlsdDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: 'mlsd_image_processor',
|
||||||
|
type: 'mlsd_image_processor',
|
||||||
|
thr_d: 0.1,
|
||||||
|
thr_v: 0.1,
|
||||||
|
}),
|
||||||
|
buildNode: (image, config) => ({
|
||||||
|
...config,
|
||||||
|
image: { image_name: image.name },
|
||||||
|
detect_resolution: minDim(image),
|
||||||
|
image_resolution: minDim(image),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
normalbae_image_processor: {
|
||||||
|
type: 'normalbae_image_processor',
|
||||||
|
labelTKey: 'controlnet.normalBae',
|
||||||
|
descriptionTKey: 'controlnet.normalBaeDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: 'normalbae_image_processor',
|
||||||
|
type: 'normalbae_image_processor',
|
||||||
|
}),
|
||||||
|
buildNode: (image, config) => ({
|
||||||
|
...config,
|
||||||
|
image: { image_name: image.name },
|
||||||
|
detect_resolution: minDim(image),
|
||||||
|
image_resolution: minDim(image),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
dw_openpose_image_processor: {
|
||||||
|
type: 'dw_openpose_image_processor',
|
||||||
|
labelTKey: 'controlnet.dwOpenpose',
|
||||||
|
descriptionTKey: 'controlnet.dwOpenposeDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: 'dw_openpose_image_processor',
|
||||||
|
type: 'dw_openpose_image_processor',
|
||||||
|
draw_body: true,
|
||||||
|
draw_face: false,
|
||||||
|
draw_hands: false,
|
||||||
|
}),
|
||||||
|
buildNode: (image, config) => ({
|
||||||
|
...config,
|
||||||
|
image: { image_name: image.name },
|
||||||
|
image_resolution: minDim(image),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
pidi_image_processor: {
|
||||||
|
type: 'pidi_image_processor',
|
||||||
|
labelTKey: 'controlnet.pidi',
|
||||||
|
descriptionTKey: 'controlnet.pidiDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: 'pidi_image_processor',
|
||||||
|
type: 'pidi_image_processor',
|
||||||
|
scribble: false,
|
||||||
|
safe: false,
|
||||||
|
}),
|
||||||
|
buildNode: (image, config) => ({
|
||||||
|
...config,
|
||||||
|
image: { image_name: image.name },
|
||||||
|
detect_resolution: minDim(image),
|
||||||
|
image_resolution: minDim(image),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
zoe_depth_image_processor: {
|
||||||
|
type: 'zoe_depth_image_processor',
|
||||||
|
labelTKey: 'controlnet.depthZoe',
|
||||||
|
descriptionTKey: 'controlnet.depthZoeDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: 'zoe_depth_image_processor',
|
||||||
|
type: 'zoe_depth_image_processor',
|
||||||
|
}),
|
||||||
|
buildNode: (image, config) => ({
|
||||||
|
...config,
|
||||||
|
image: { image_name: image.name },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const zTool = z.enum(['brush', 'eraser', 'move', 'rect', 'view', 'bbox']);
|
const zTool = z.enum(['brush', 'eraser', 'move', 'rect', 'view', 'bbox']);
|
||||||
export type Tool = z.infer<typeof zTool>;
|
export type Tool = z.infer<typeof zTool>;
|
||||||
|
|
||||||
const zDrawingTool = zTool.extract(['brush', 'eraser']);
|
const zDrawingTool = zTool.extract(['brush', 'eraser']);
|
||||||
|
|
||||||
const zPoints = z.array(z.number()).refine((points) => points.length % 2 === 0, {
|
const zPoints = z.array(z.number()).refine((points) => points.length % 2 === 0, {
|
||||||
@ -244,7 +666,7 @@ const zInpaintMaskData = z.object({
|
|||||||
});
|
});
|
||||||
export type InpaintMaskData = z.infer<typeof zInpaintMaskData>;
|
export type InpaintMaskData = z.infer<typeof zInpaintMaskData>;
|
||||||
|
|
||||||
const zFilter = z.enum(['none', LightnessToAlphaFilter.name]);
|
const zFilter = z.enum(['none', 'LightnessToAlphaFilter']);
|
||||||
export type Filter = z.infer<typeof zFilter>;
|
export type Filter = z.infer<typeof zFilter>;
|
||||||
|
|
||||||
const zControlAdapterData = z.object({
|
const zControlAdapterData = z.object({
|
||||||
@ -272,6 +694,64 @@ export type ControlAdapterConfig = Pick<
|
|||||||
'weight' | 'image' | 'processedImage' | 'processorConfig' | 'beginEndStepPct' | 'model' | 'controlMode'
|
'weight' | 'image' | 'processedImage' | 'processorConfig' | 'beginEndStepPct' | 'model' | 'controlMode'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export const initialControlNetV2: ControlAdapterConfig = {
|
||||||
|
model: null,
|
||||||
|
weight: 1,
|
||||||
|
beginEndStepPct: [0, 1],
|
||||||
|
controlMode: 'balanced',
|
||||||
|
image: null,
|
||||||
|
processedImage: null,
|
||||||
|
processorConfig: CA_PROCESSOR_DATA.canny_image_processor.buildDefaults(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initialT2IAdapterV2: ControlAdapterConfig = {
|
||||||
|
model: null,
|
||||||
|
weight: 1,
|
||||||
|
beginEndStepPct: [0, 1],
|
||||||
|
controlMode: null,
|
||||||
|
image: null,
|
||||||
|
processedImage: null,
|
||||||
|
processorConfig: CA_PROCESSOR_DATA.canny_image_processor.buildDefaults(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initialIPAdapterV2: IPAdapterConfig = {
|
||||||
|
image: null,
|
||||||
|
model: null,
|
||||||
|
beginEndStepPct: [0, 1],
|
||||||
|
method: 'full',
|
||||||
|
clipVisionModel: 'ViT-H',
|
||||||
|
weight: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildControlNet = (id: string, overrides?: Partial<ControlAdapterConfig>): ControlAdapterConfig => {
|
||||||
|
return merge(deepClone(initialControlNetV2), { id, ...overrides });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildT2IAdapter = (id: string, overrides?: Partial<ControlAdapterConfig>): ControlAdapterConfig => {
|
||||||
|
return merge(deepClone(initialT2IAdapterV2), { id, ...overrides });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildIPAdapter = (id: string, overrides?: Partial<IPAdapterConfig>): IPAdapterConfig => {
|
||||||
|
return merge(deepClone(initialIPAdapterV2), { id, ...overrides });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildControlAdapterProcessorV2 = (
|
||||||
|
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig
|
||||||
|
): ProcessorConfig | null => {
|
||||||
|
const defaultPreprocessor = modelConfig.default_settings?.preprocessor;
|
||||||
|
if (!isProcessorTypeV2(defaultPreprocessor)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const processorConfig = CA_PROCESSOR_DATA[defaultPreprocessor].buildDefaults(modelConfig.base);
|
||||||
|
return processorConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const imageDTOToImageWithDims = ({ image_name, width, height }: ImageDTO): ImageWithDims => ({
|
||||||
|
name: image_name,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
});
|
||||||
|
|
||||||
export type CanvasEntity = LayerData | IPAdapterData | ControlAdapterData | RegionalGuidanceData | InpaintMaskData;
|
export type CanvasEntity = LayerData | IPAdapterData | ControlAdapterData | RegionalGuidanceData | InpaintMaskData;
|
||||||
export type CanvasEntityIdentifier = Pick<CanvasEntity, 'id' | 'type'>;
|
export type CanvasEntityIdentifier = Pick<CanvasEntity, 'id' | 'type'>;
|
||||||
|
|
||||||
|
@ -1,546 +0,0 @@
|
|||||||
import { deepClone } from 'common/util/deepClone';
|
|
||||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
|
||||||
import { merge, omit } from 'lodash-es';
|
|
||||||
import type {
|
|
||||||
AnyInvocation,
|
|
||||||
BaseModelType,
|
|
||||||
ControlNetModelConfig,
|
|
||||||
ImageDTO,
|
|
||||||
T2IAdapterModelConfig,
|
|
||||||
} from 'services/api/types';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
export const zId = z.string().min(1);
|
|
||||||
|
|
||||||
const zCannyProcessorConfig = z.object({
|
|
||||||
id: zId,
|
|
||||||
type: z.literal('canny_image_processor'),
|
|
||||||
low_threshold: z.number().int().gte(0).lte(255),
|
|
||||||
high_threshold: z.number().int().gte(0).lte(255),
|
|
||||||
});
|
|
||||||
export type CannyProcessorConfig = z.infer<typeof zCannyProcessorConfig>;
|
|
||||||
|
|
||||||
const zColorMapProcessorConfig = z.object({
|
|
||||||
id: zId,
|
|
||||||
type: z.literal('color_map_image_processor'),
|
|
||||||
color_map_tile_size: z.number().int().gte(1),
|
|
||||||
});
|
|
||||||
export type ColorMapProcessorConfig = z.infer<typeof zColorMapProcessorConfig>;
|
|
||||||
|
|
||||||
const zContentShuffleProcessorConfig = z.object({
|
|
||||||
id: zId,
|
|
||||||
type: z.literal('content_shuffle_image_processor'),
|
|
||||||
w: z.number().int().gte(0),
|
|
||||||
h: z.number().int().gte(0),
|
|
||||||
f: z.number().int().gte(0),
|
|
||||||
});
|
|
||||||
export type ContentShuffleProcessorConfig = z.infer<typeof zContentShuffleProcessorConfig>;
|
|
||||||
|
|
||||||
const zDepthAnythingModelSize = z.enum(['large', 'base', 'small', 'small_v2']);
|
|
||||||
export type DepthAnythingModelSize = z.infer<typeof zDepthAnythingModelSize>;
|
|
||||||
export const isDepthAnythingModelSize = (v: unknown): v is DepthAnythingModelSize =>
|
|
||||||
zDepthAnythingModelSize.safeParse(v).success;
|
|
||||||
const zDepthAnythingProcessorConfig = z.object({
|
|
||||||
id: zId,
|
|
||||||
type: z.literal('depth_anything_image_processor'),
|
|
||||||
model_size: zDepthAnythingModelSize,
|
|
||||||
});
|
|
||||||
export type DepthAnythingProcessorConfig = z.infer<typeof zDepthAnythingProcessorConfig>;
|
|
||||||
|
|
||||||
const zHedProcessorConfig = z.object({
|
|
||||||
id: zId,
|
|
||||||
type: z.literal('hed_image_processor'),
|
|
||||||
scribble: z.boolean(),
|
|
||||||
});
|
|
||||||
export type HedProcessorConfig = z.infer<typeof zHedProcessorConfig>;
|
|
||||||
|
|
||||||
const zLineartAnimeProcessorConfig = z.object({
|
|
||||||
id: zId,
|
|
||||||
type: z.literal('lineart_anime_image_processor'),
|
|
||||||
});
|
|
||||||
export type LineartAnimeProcessorConfig = z.infer<typeof zLineartAnimeProcessorConfig>;
|
|
||||||
|
|
||||||
const zLineartProcessorConfig = z.object({
|
|
||||||
id: zId,
|
|
||||||
type: z.literal('lineart_image_processor'),
|
|
||||||
coarse: z.boolean(),
|
|
||||||
});
|
|
||||||
export type LineartProcessorConfig = z.infer<typeof zLineartProcessorConfig>;
|
|
||||||
|
|
||||||
const zMediapipeFaceProcessorConfig = z.object({
|
|
||||||
id: zId,
|
|
||||||
type: z.literal('mediapipe_face_processor'),
|
|
||||||
max_faces: z.number().int().gte(1),
|
|
||||||
min_confidence: z.number().gte(0).lte(1),
|
|
||||||
});
|
|
||||||
export type MediapipeFaceProcessorConfig = z.infer<typeof zMediapipeFaceProcessorConfig>;
|
|
||||||
|
|
||||||
const zMidasDepthProcessorConfig = z.object({
|
|
||||||
id: zId,
|
|
||||||
type: z.literal('midas_depth_image_processor'),
|
|
||||||
a_mult: z.number().gte(0),
|
|
||||||
bg_th: z.number().gte(0),
|
|
||||||
});
|
|
||||||
export type MidasDepthProcessorConfig = z.infer<typeof zMidasDepthProcessorConfig>;
|
|
||||||
|
|
||||||
const zMlsdProcessorConfig = z.object({
|
|
||||||
id: zId,
|
|
||||||
type: z.literal('mlsd_image_processor'),
|
|
||||||
thr_v: z.number().gte(0),
|
|
||||||
thr_d: z.number().gte(0),
|
|
||||||
});
|
|
||||||
export type MlsdProcessorConfig = z.infer<typeof zMlsdProcessorConfig>;
|
|
||||||
|
|
||||||
const zNormalbaeProcessorConfig = z.object({
|
|
||||||
id: zId,
|
|
||||||
type: z.literal('normalbae_image_processor'),
|
|
||||||
});
|
|
||||||
export type NormalbaeProcessorConfig = z.infer<typeof zNormalbaeProcessorConfig>;
|
|
||||||
|
|
||||||
const zDWOpenposeProcessorConfig = z.object({
|
|
||||||
id: zId,
|
|
||||||
type: z.literal('dw_openpose_image_processor'),
|
|
||||||
draw_body: z.boolean(),
|
|
||||||
draw_face: z.boolean(),
|
|
||||||
draw_hands: z.boolean(),
|
|
||||||
});
|
|
||||||
export type DWOpenposeProcessorConfig = z.infer<typeof zDWOpenposeProcessorConfig>;
|
|
||||||
|
|
||||||
const zPidiProcessorConfig = z.object({
|
|
||||||
id: zId,
|
|
||||||
type: z.literal('pidi_image_processor'),
|
|
||||||
safe: z.boolean(),
|
|
||||||
scribble: z.boolean(),
|
|
||||||
});
|
|
||||||
export type PidiProcessorConfig = z.infer<typeof zPidiProcessorConfig>;
|
|
||||||
|
|
||||||
const zZoeDepthProcessorConfig = z.object({
|
|
||||||
id: zId,
|
|
||||||
type: z.literal('zoe_depth_image_processor'),
|
|
||||||
});
|
|
||||||
export type ZoeDepthProcessorConfig = z.infer<typeof zZoeDepthProcessorConfig>;
|
|
||||||
|
|
||||||
export const zProcessorConfig = z.discriminatedUnion('type', [
|
|
||||||
zCannyProcessorConfig,
|
|
||||||
zColorMapProcessorConfig,
|
|
||||||
zContentShuffleProcessorConfig,
|
|
||||||
zDepthAnythingProcessorConfig,
|
|
||||||
zHedProcessorConfig,
|
|
||||||
zLineartAnimeProcessorConfig,
|
|
||||||
zLineartProcessorConfig,
|
|
||||||
zMediapipeFaceProcessorConfig,
|
|
||||||
zMidasDepthProcessorConfig,
|
|
||||||
zMlsdProcessorConfig,
|
|
||||||
zNormalbaeProcessorConfig,
|
|
||||||
zDWOpenposeProcessorConfig,
|
|
||||||
zPidiProcessorConfig,
|
|
||||||
zZoeDepthProcessorConfig,
|
|
||||||
]);
|
|
||||||
export type ProcessorConfig = z.infer<typeof zProcessorConfig>;
|
|
||||||
|
|
||||||
export const zImageWithDims = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
width: z.number().int().positive(),
|
|
||||||
height: z.number().int().positive(),
|
|
||||||
});
|
|
||||||
export type ImageWithDims = z.infer<typeof zImageWithDims>;
|
|
||||||
|
|
||||||
export const zBeginEndStepPct = z
|
|
||||||
.tuple([z.number().gte(0).lte(1), z.number().gte(0).lte(1)])
|
|
||||||
.refine(([begin, end]) => begin < end, {
|
|
||||||
message: 'Begin must be less than end',
|
|
||||||
});
|
|
||||||
|
|
||||||
const zControlAdapterBase = z.object({
|
|
||||||
id: zId,
|
|
||||||
weight: z.number().gte(-1).lte(2),
|
|
||||||
image: zImageWithDims.nullable(),
|
|
||||||
processedImage: zImageWithDims.nullable(),
|
|
||||||
processorConfig: zProcessorConfig.nullable(),
|
|
||||||
processorPendingBatchId: z.string().nullable().default(null),
|
|
||||||
beginEndStepPct: zBeginEndStepPct,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const zControlModeV2 = z.enum(['balanced', 'more_prompt', 'more_control', 'unbalanced']);
|
|
||||||
export type ControlModeV2 = z.infer<typeof zControlModeV2>;
|
|
||||||
export const isControlModeV2 = (v: unknown): v is ControlModeV2 => zControlModeV2.safeParse(v).success;
|
|
||||||
|
|
||||||
export const zControlNetConfigV2 = zControlAdapterBase.extend({
|
|
||||||
type: z.literal('controlnet'),
|
|
||||||
model: zModelIdentifierField.nullable(),
|
|
||||||
controlMode: zControlModeV2,
|
|
||||||
});
|
|
||||||
export type ControlNetConfigV2 = z.infer<typeof zControlNetConfigV2>;
|
|
||||||
|
|
||||||
export const zT2IAdapterConfigV2 = zControlAdapterBase.extend({
|
|
||||||
type: z.literal('t2i_adapter'),
|
|
||||||
model: zModelIdentifierField.nullable(),
|
|
||||||
});
|
|
||||||
export type T2IAdapterConfigV2 = z.infer<typeof zT2IAdapterConfigV2>;
|
|
||||||
|
|
||||||
export const zCLIPVisionModelV2 = z.enum(['ViT-H', 'ViT-G']);
|
|
||||||
export type CLIPVisionModelV2 = z.infer<typeof zCLIPVisionModelV2>;
|
|
||||||
export const isCLIPVisionModelV2 = (v: unknown): v is CLIPVisionModelV2 => zCLIPVisionModelV2.safeParse(v).success;
|
|
||||||
|
|
||||||
export const zIPMethodV2 = z.enum(['full', 'style', 'composition']);
|
|
||||||
export type IPMethodV2 = z.infer<typeof zIPMethodV2>;
|
|
||||||
export const isIPMethodV2 = (v: unknown): v is IPMethodV2 => zIPMethodV2.safeParse(v).success;
|
|
||||||
|
|
||||||
export const zIPAdapterConfigV2 = z.object({
|
|
||||||
id: zId,
|
|
||||||
type: z.literal('ip_adapter'),
|
|
||||||
weight: z.number().gte(-1).lte(2),
|
|
||||||
method: zIPMethodV2,
|
|
||||||
image: zImageWithDims.nullable(),
|
|
||||||
model: zModelIdentifierField.nullable(),
|
|
||||||
clipVisionModel: zCLIPVisionModelV2,
|
|
||||||
beginEndStepPct: zBeginEndStepPct,
|
|
||||||
});
|
|
||||||
export type IPAdapterConfigV2 = z.infer<typeof zIPAdapterConfigV2>;
|
|
||||||
|
|
||||||
const zProcessorTypeV2 = z.enum([
|
|
||||||
'canny_image_processor',
|
|
||||||
'color_map_image_processor',
|
|
||||||
'content_shuffle_image_processor',
|
|
||||||
'depth_anything_image_processor',
|
|
||||||
'hed_image_processor',
|
|
||||||
'lineart_anime_image_processor',
|
|
||||||
'lineart_image_processor',
|
|
||||||
'mediapipe_face_processor',
|
|
||||||
'midas_depth_image_processor',
|
|
||||||
'mlsd_image_processor',
|
|
||||||
'normalbae_image_processor',
|
|
||||||
'dw_openpose_image_processor',
|
|
||||||
'pidi_image_processor',
|
|
||||||
'zoe_depth_image_processor',
|
|
||||||
]);
|
|
||||||
export type ProcessorTypeV2 = z.infer<typeof zProcessorTypeV2>;
|
|
||||||
export const isProcessorTypeV2 = (v: unknown): v is ProcessorTypeV2 => zProcessorTypeV2.safeParse(v).success;
|
|
||||||
|
|
||||||
type ProcessorData<T extends ProcessorTypeV2> = {
|
|
||||||
type: T;
|
|
||||||
labelTKey: string;
|
|
||||||
descriptionTKey: string;
|
|
||||||
buildDefaults(baseModel?: BaseModelType): Extract<ProcessorConfig, { type: T }>;
|
|
||||||
buildNode(image: ImageWithDims, config: Extract<ProcessorConfig, { type: T }>): Extract<AnyInvocation, { type: T }>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const minDim = (image: ImageWithDims): number => Math.min(image.width, image.height);
|
|
||||||
|
|
||||||
type CAProcessorsData = {
|
|
||||||
[key in ProcessorTypeV2]: ProcessorData<key>;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* A dict of ControlNet processors, including:
|
|
||||||
* - label translation key
|
|
||||||
* - description translation key
|
|
||||||
* - a builder to create default values for the config
|
|
||||||
* - a builder to create the node for the config
|
|
||||||
*
|
|
||||||
* TODO: Generate from the OpenAPI schema
|
|
||||||
*/
|
|
||||||
export const CA_PROCESSOR_DATA: CAProcessorsData = {
|
|
||||||
canny_image_processor: {
|
|
||||||
type: 'canny_image_processor',
|
|
||||||
labelTKey: 'controlnet.canny',
|
|
||||||
descriptionTKey: 'controlnet.cannyDescription',
|
|
||||||
buildDefaults: () => ({
|
|
||||||
id: 'canny_image_processor',
|
|
||||||
type: 'canny_image_processor',
|
|
||||||
low_threshold: 100,
|
|
||||||
high_threshold: 200,
|
|
||||||
}),
|
|
||||||
buildNode: (image, config) => ({
|
|
||||||
...config,
|
|
||||||
type: 'canny_image_processor',
|
|
||||||
image: { image_name: image.name },
|
|
||||||
detect_resolution: minDim(image),
|
|
||||||
image_resolution: minDim(image),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
color_map_image_processor: {
|
|
||||||
type: 'color_map_image_processor',
|
|
||||||
labelTKey: 'controlnet.colorMap',
|
|
||||||
descriptionTKey: 'controlnet.colorMapDescription',
|
|
||||||
buildDefaults: () => ({
|
|
||||||
id: 'color_map_image_processor',
|
|
||||||
type: 'color_map_image_processor',
|
|
||||||
color_map_tile_size: 64,
|
|
||||||
}),
|
|
||||||
buildNode: (image, config) => ({
|
|
||||||
...config,
|
|
||||||
type: 'color_map_image_processor',
|
|
||||||
image: { image_name: image.name },
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
content_shuffle_image_processor: {
|
|
||||||
type: 'content_shuffle_image_processor',
|
|
||||||
labelTKey: 'controlnet.contentShuffle',
|
|
||||||
descriptionTKey: 'controlnet.contentShuffleDescription',
|
|
||||||
buildDefaults: (baseModel) => ({
|
|
||||||
id: 'content_shuffle_image_processor',
|
|
||||||
type: 'content_shuffle_image_processor',
|
|
||||||
h: baseModel === 'sdxl' ? 1024 : 512,
|
|
||||||
w: baseModel === 'sdxl' ? 1024 : 512,
|
|
||||||
f: baseModel === 'sdxl' ? 512 : 256,
|
|
||||||
}),
|
|
||||||
buildNode: (image, config) => ({
|
|
||||||
...config,
|
|
||||||
image: { image_name: image.name },
|
|
||||||
detect_resolution: minDim(image),
|
|
||||||
image_resolution: minDim(image),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
depth_anything_image_processor: {
|
|
||||||
type: 'depth_anything_image_processor',
|
|
||||||
labelTKey: 'controlnet.depthAnything',
|
|
||||||
descriptionTKey: 'controlnet.depthAnythingDescription',
|
|
||||||
buildDefaults: () => ({
|
|
||||||
id: 'depth_anything_image_processor',
|
|
||||||
type: 'depth_anything_image_processor',
|
|
||||||
model_size: 'small_v2',
|
|
||||||
}),
|
|
||||||
buildNode: (image, config) => ({
|
|
||||||
...config,
|
|
||||||
image: { image_name: image.name },
|
|
||||||
resolution: minDim(image),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
hed_image_processor: {
|
|
||||||
type: 'hed_image_processor',
|
|
||||||
labelTKey: 'controlnet.hed',
|
|
||||||
descriptionTKey: 'controlnet.hedDescription',
|
|
||||||
buildDefaults: () => ({
|
|
||||||
id: 'hed_image_processor',
|
|
||||||
type: 'hed_image_processor',
|
|
||||||
scribble: false,
|
|
||||||
}),
|
|
||||||
buildNode: (image, config) => ({
|
|
||||||
...config,
|
|
||||||
image: { image_name: image.name },
|
|
||||||
detect_resolution: minDim(image),
|
|
||||||
image_resolution: minDim(image),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
lineart_anime_image_processor: {
|
|
||||||
type: 'lineart_anime_image_processor',
|
|
||||||
labelTKey: 'controlnet.lineartAnime',
|
|
||||||
descriptionTKey: 'controlnet.lineartAnimeDescription',
|
|
||||||
buildDefaults: () => ({
|
|
||||||
id: 'lineart_anime_image_processor',
|
|
||||||
type: 'lineart_anime_image_processor',
|
|
||||||
}),
|
|
||||||
buildNode: (image, config) => ({
|
|
||||||
...config,
|
|
||||||
image: { image_name: image.name },
|
|
||||||
detect_resolution: minDim(image),
|
|
||||||
image_resolution: minDim(image),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
lineart_image_processor: {
|
|
||||||
type: 'lineart_image_processor',
|
|
||||||
labelTKey: 'controlnet.lineart',
|
|
||||||
descriptionTKey: 'controlnet.lineartDescription',
|
|
||||||
buildDefaults: () => ({
|
|
||||||
id: 'lineart_image_processor',
|
|
||||||
type: 'lineart_image_processor',
|
|
||||||
coarse: false,
|
|
||||||
}),
|
|
||||||
buildNode: (image, config) => ({
|
|
||||||
...config,
|
|
||||||
image: { image_name: image.name },
|
|
||||||
detect_resolution: minDim(image),
|
|
||||||
image_resolution: minDim(image),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
mediapipe_face_processor: {
|
|
||||||
type: 'mediapipe_face_processor',
|
|
||||||
labelTKey: 'controlnet.mediapipeFace',
|
|
||||||
descriptionTKey: 'controlnet.mediapipeFaceDescription',
|
|
||||||
buildDefaults: () => ({
|
|
||||||
id: 'mediapipe_face_processor',
|
|
||||||
type: 'mediapipe_face_processor',
|
|
||||||
max_faces: 1,
|
|
||||||
min_confidence: 0.5,
|
|
||||||
}),
|
|
||||||
buildNode: (image, config) => ({
|
|
||||||
...config,
|
|
||||||
image: { image_name: image.name },
|
|
||||||
detect_resolution: minDim(image),
|
|
||||||
image_resolution: minDim(image),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
midas_depth_image_processor: {
|
|
||||||
type: 'midas_depth_image_processor',
|
|
||||||
labelTKey: 'controlnet.depthMidas',
|
|
||||||
descriptionTKey: 'controlnet.depthMidasDescription',
|
|
||||||
buildDefaults: () => ({
|
|
||||||
id: 'midas_depth_image_processor',
|
|
||||||
type: 'midas_depth_image_processor',
|
|
||||||
a_mult: 2,
|
|
||||||
bg_th: 0.1,
|
|
||||||
}),
|
|
||||||
buildNode: (image, config) => ({
|
|
||||||
...config,
|
|
||||||
image: { image_name: image.name },
|
|
||||||
detect_resolution: minDim(image),
|
|
||||||
image_resolution: minDim(image),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
mlsd_image_processor: {
|
|
||||||
type: 'mlsd_image_processor',
|
|
||||||
labelTKey: 'controlnet.mlsd',
|
|
||||||
descriptionTKey: 'controlnet.mlsdDescription',
|
|
||||||
buildDefaults: () => ({
|
|
||||||
id: 'mlsd_image_processor',
|
|
||||||
type: 'mlsd_image_processor',
|
|
||||||
thr_d: 0.1,
|
|
||||||
thr_v: 0.1,
|
|
||||||
}),
|
|
||||||
buildNode: (image, config) => ({
|
|
||||||
...config,
|
|
||||||
image: { image_name: image.name },
|
|
||||||
detect_resolution: minDim(image),
|
|
||||||
image_resolution: minDim(image),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
normalbae_image_processor: {
|
|
||||||
type: 'normalbae_image_processor',
|
|
||||||
labelTKey: 'controlnet.normalBae',
|
|
||||||
descriptionTKey: 'controlnet.normalBaeDescription',
|
|
||||||
buildDefaults: () => ({
|
|
||||||
id: 'normalbae_image_processor',
|
|
||||||
type: 'normalbae_image_processor',
|
|
||||||
}),
|
|
||||||
buildNode: (image, config) => ({
|
|
||||||
...config,
|
|
||||||
image: { image_name: image.name },
|
|
||||||
detect_resolution: minDim(image),
|
|
||||||
image_resolution: minDim(image),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
dw_openpose_image_processor: {
|
|
||||||
type: 'dw_openpose_image_processor',
|
|
||||||
labelTKey: 'controlnet.dwOpenpose',
|
|
||||||
descriptionTKey: 'controlnet.dwOpenposeDescription',
|
|
||||||
buildDefaults: () => ({
|
|
||||||
id: 'dw_openpose_image_processor',
|
|
||||||
type: 'dw_openpose_image_processor',
|
|
||||||
draw_body: true,
|
|
||||||
draw_face: false,
|
|
||||||
draw_hands: false,
|
|
||||||
}),
|
|
||||||
buildNode: (image, config) => ({
|
|
||||||
...config,
|
|
||||||
image: { image_name: image.name },
|
|
||||||
image_resolution: minDim(image),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
pidi_image_processor: {
|
|
||||||
type: 'pidi_image_processor',
|
|
||||||
labelTKey: 'controlnet.pidi',
|
|
||||||
descriptionTKey: 'controlnet.pidiDescription',
|
|
||||||
buildDefaults: () => ({
|
|
||||||
id: 'pidi_image_processor',
|
|
||||||
type: 'pidi_image_processor',
|
|
||||||
scribble: false,
|
|
||||||
safe: false,
|
|
||||||
}),
|
|
||||||
buildNode: (image, config) => ({
|
|
||||||
...config,
|
|
||||||
image: { image_name: image.name },
|
|
||||||
detect_resolution: minDim(image),
|
|
||||||
image_resolution: minDim(image),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
zoe_depth_image_processor: {
|
|
||||||
type: 'zoe_depth_image_processor',
|
|
||||||
labelTKey: 'controlnet.depthZoe',
|
|
||||||
descriptionTKey: 'controlnet.depthZoeDescription',
|
|
||||||
buildDefaults: () => ({
|
|
||||||
id: 'zoe_depth_image_processor',
|
|
||||||
type: 'zoe_depth_image_processor',
|
|
||||||
}),
|
|
||||||
buildNode: (image, config) => ({
|
|
||||||
...config,
|
|
||||||
image: { image_name: image.name },
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialControlNetV2: Omit<ControlNetConfigV2, 'id'> = {
|
|
||||||
type: 'controlnet',
|
|
||||||
model: null,
|
|
||||||
weight: 1,
|
|
||||||
beginEndStepPct: [0, 1],
|
|
||||||
controlMode: 'balanced',
|
|
||||||
image: null,
|
|
||||||
processedImage: null,
|
|
||||||
processorConfig: CA_PROCESSOR_DATA.canny_image_processor.buildDefaults(),
|
|
||||||
processorPendingBatchId: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialT2IAdapterV2: Omit<T2IAdapterConfigV2, 'id'> = {
|
|
||||||
type: 't2i_adapter',
|
|
||||||
model: null,
|
|
||||||
weight: 1,
|
|
||||||
beginEndStepPct: [0, 1],
|
|
||||||
image: null,
|
|
||||||
processedImage: null,
|
|
||||||
processorConfig: CA_PROCESSOR_DATA.canny_image_processor.buildDefaults(),
|
|
||||||
processorPendingBatchId: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialIPAdapterV2: Omit<IPAdapterConfigV2, 'id'> = {
|
|
||||||
type: 'ip_adapter',
|
|
||||||
image: null,
|
|
||||||
model: null,
|
|
||||||
beginEndStepPct: [0, 1],
|
|
||||||
method: 'full',
|
|
||||||
clipVisionModel: 'ViT-H',
|
|
||||||
weight: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildControlNet = (id: string, overrides?: Partial<ControlNetConfigV2>): ControlNetConfigV2 => {
|
|
||||||
return merge(deepClone(initialControlNetV2), { id, ...overrides });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildT2IAdapter = (id: string, overrides?: Partial<T2IAdapterConfigV2>): T2IAdapterConfigV2 => {
|
|
||||||
return merge(deepClone(initialT2IAdapterV2), { id, ...overrides });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildIPAdapter = (id: string, overrides?: Partial<IPAdapterConfigV2>): IPAdapterConfigV2 => {
|
|
||||||
return merge(deepClone(initialIPAdapterV2), { id, ...overrides });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildControlAdapterProcessorV2 = (
|
|
||||||
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig
|
|
||||||
): ProcessorConfig | null => {
|
|
||||||
const defaultPreprocessor = modelConfig.default_settings?.preprocessor;
|
|
||||||
if (!isProcessorTypeV2(defaultPreprocessor)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const processorConfig = CA_PROCESSOR_DATA[defaultPreprocessor].buildDefaults(modelConfig.base);
|
|
||||||
return processorConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const imageDTOToImageWithDims = ({ image_name, width, height }: ImageDTO): ImageWithDims => ({
|
|
||||||
name: image_name,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const t2iAdapterToControlNet = (t2iAdapter: T2IAdapterConfigV2): ControlNetConfigV2 => {
|
|
||||||
return {
|
|
||||||
...deepClone(t2iAdapter),
|
|
||||||
type: 'controlnet',
|
|
||||||
controlMode: initialControlNetV2.controlMode,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const controlNetToT2IAdapter = (controlNet: ControlNetConfigV2): T2IAdapterConfigV2 => {
|
|
||||||
return {
|
|
||||||
...omit(deepClone(controlNet), 'controlMode'),
|
|
||||||
type: 't2i_adapter',
|
|
||||||
};
|
|
||||||
};
|
|
@ -22,39 +22,32 @@ export type CurrentImageDropData = BaseDropData & {
|
|||||||
actionType: 'SET_CURRENT_IMAGE';
|
actionType: 'SET_CURRENT_IMAGE';
|
||||||
};
|
};
|
||||||
|
|
||||||
type ControlAdapterDropData = BaseDropData & {
|
export type CAImageDropData = BaseDropData & {
|
||||||
actionType: 'SET_CONTROL_ADAPTER_IMAGE';
|
actionType: 'SET_CA_IMAGE';
|
||||||
context: {
|
context: {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CALayerImageDropData = BaseDropData & {
|
export type IPAImageDropData = BaseDropData & {
|
||||||
actionType: 'SET_CA_LAYER_IMAGE';
|
actionType: 'SET_IPA_IMAGE';
|
||||||
context: {
|
context: {
|
||||||
layerId: string;
|
id: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IPALayerImageDropData = BaseDropData & {
|
export type RGIPAdapterImageDropData = BaseDropData & {
|
||||||
actionType: 'SET_IPA_LAYER_IMAGE';
|
actionType: 'SET_RG_IP_ADAPTER_IMAGE';
|
||||||
context: {
|
context: {
|
||||||
layerId: string;
|
id: string;
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RGLayerIPAdapterImageDropData = BaseDropData & {
|
|
||||||
actionType: 'SET_RG_LAYER_IP_ADAPTER_IMAGE';
|
|
||||||
context: {
|
|
||||||
layerId: string;
|
|
||||||
ipAdapterId: string;
|
ipAdapterId: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IILayerImageDropData = BaseDropData & {
|
export type LayerImageDropData = BaseDropData & {
|
||||||
actionType: 'SET_II_LAYER_IMAGE';
|
actionType: 'ADD_LAYER_IMAGE';
|
||||||
context: {
|
context: {
|
||||||
layerId: string;
|
id: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -100,16 +93,14 @@ export type SelectForCompareDropData = BaseDropData & {
|
|||||||
|
|
||||||
export type TypesafeDroppableData =
|
export type TypesafeDroppableData =
|
||||||
| CurrentImageDropData
|
| CurrentImageDropData
|
||||||
| ControlAdapterDropData
|
|
||||||
| CanvasInitialImageDropData
|
|
||||||
| NodesImageDropData
|
| NodesImageDropData
|
||||||
| AddToBoardDropData
|
| AddToBoardDropData
|
||||||
| RemoveFromBoardDropData
|
| RemoveFromBoardDropData
|
||||||
| CALayerImageDropData
|
| CAImageDropData
|
||||||
| IPALayerImageDropData
|
| IPAImageDropData
|
||||||
| RGLayerIPAdapterImageDropData
|
| RGIPAdapterImageDropData
|
||||||
| IILayerImageDropData
|
|
||||||
| SelectForCompareDropData
|
| SelectForCompareDropData
|
||||||
|
| RasterLayerImageDropData
|
||||||
| UpscaleInitialImageDropData;
|
| UpscaleInitialImageDropData;
|
||||||
|
|
||||||
type BaseDragData = {
|
type BaseDragData = {
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react';
|
|
||||||
|
|
||||||
import { ControlSettingsAccordion } from './ControlSettingsAccordion';
|
|
||||||
|
|
||||||
const meta: Meta<typeof ControlSettingsAccordion> = {
|
|
||||||
title: 'Feature/ControlSettingsAccordion',
|
|
||||||
tags: ['autodocs'],
|
|
||||||
component: ControlSettingsAccordion,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof ControlSettingsAccordion>;
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
render: () => <ControlSettingsAccordion />,
|
|
||||||
};
|
|
@ -1,125 +0,0 @@
|
|||||||
import { Button, ButtonGroup, Divider, Flex, StandaloneAccordion } from '@invoke-ai/ui-library';
|
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import ControlAdapterConfig from 'features/controlAdapters/components/ControlAdapterConfig';
|
|
||||||
import { useAddControlAdapter } from 'features/controlAdapters/hooks/useAddControlAdapter';
|
|
||||||
import {
|
|
||||||
selectAllControlNets,
|
|
||||||
selectAllIPAdapters,
|
|
||||||
selectAllT2IAdapters,
|
|
||||||
selectControlAdapterIds,
|
|
||||||
selectControlAdaptersSlice,
|
|
||||||
selectValidControlNets,
|
|
||||||
selectValidIPAdapters,
|
|
||||||
selectValidT2IAdapters,
|
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
|
||||||
import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle';
|
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
|
||||||
import { Fragment, memo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { PiPlusBold } from 'react-icons/pi';
|
|
||||||
|
|
||||||
const selector = createMemoizedSelector([selectControlAdaptersSlice], (controlAdapters) => {
|
|
||||||
const badges: string[] = [];
|
|
||||||
let isError = false;
|
|
||||||
|
|
||||||
const enabledNonRegionalIPAdapterCount = selectAllIPAdapters(controlAdapters).filter((ca) => ca.isEnabled).length;
|
|
||||||
|
|
||||||
const validIPAdapterCount = selectValidIPAdapters(controlAdapters).length;
|
|
||||||
if (enabledNonRegionalIPAdapterCount > 0) {
|
|
||||||
badges.push(`${enabledNonRegionalIPAdapterCount} IP`);
|
|
||||||
}
|
|
||||||
if (enabledNonRegionalIPAdapterCount > validIPAdapterCount) {
|
|
||||||
isError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const enabledControlNetCount = selectAllControlNets(controlAdapters).filter((ca) => ca.isEnabled).length;
|
|
||||||
const validControlNetCount = selectValidControlNets(controlAdapters).length;
|
|
||||||
if (enabledControlNetCount > 0) {
|
|
||||||
badges.push(`${enabledControlNetCount} ControlNet`);
|
|
||||||
}
|
|
||||||
if (enabledControlNetCount > validControlNetCount) {
|
|
||||||
isError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const enabledT2IAdapterCount = selectAllT2IAdapters(controlAdapters).filter((ca) => ca.isEnabled).length;
|
|
||||||
const validT2IAdapterCount = selectValidT2IAdapters(controlAdapters).length;
|
|
||||||
if (enabledT2IAdapterCount > 0) {
|
|
||||||
badges.push(`${enabledT2IAdapterCount} T2I`);
|
|
||||||
}
|
|
||||||
if (enabledT2IAdapterCount > validT2IAdapterCount) {
|
|
||||||
isError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const controlAdapterIds = selectControlAdapterIds(controlAdapters);
|
|
||||||
|
|
||||||
return {
|
|
||||||
controlAdapterIds,
|
|
||||||
badges,
|
|
||||||
isError, // TODO: Add some visual indicator that the control adapters are in an error state
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ControlSettingsAccordion: React.FC = memo(() => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { controlAdapterIds, badges } = useAppSelector(selector);
|
|
||||||
const isControlNetEnabled = useFeatureStatus('controlNet');
|
|
||||||
const { isOpen, onToggle } = useStandaloneAccordionToggle({
|
|
||||||
id: 'control-settings',
|
|
||||||
defaultIsOpen: true,
|
|
||||||
});
|
|
||||||
const [addControlNet, isAddControlNetDisabled] = useAddControlAdapter('controlnet');
|
|
||||||
const [addIPAdapter, isAddIPAdapterDisabled] = useAddControlAdapter('ip_adapter');
|
|
||||||
const [addT2IAdapter, isAddT2IAdapterDisabled] = useAddControlAdapter('t2i_adapter');
|
|
||||||
|
|
||||||
if (!isControlNetEnabled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StandaloneAccordion label={t('accordions.control.title')} badges={badges} isOpen={isOpen} onToggle={onToggle}>
|
|
||||||
<Flex gap={2} p={4} flexDir="column" data-testid="control-accordion">
|
|
||||||
<ButtonGroup size="sm" w="full" justifyContent="space-between" variant="ghost" isAttached={false}>
|
|
||||||
<Button
|
|
||||||
tooltip={t('controlnet.addControlNet')}
|
|
||||||
leftIcon={<PiPlusBold />}
|
|
||||||
onClick={addControlNet}
|
|
||||||
data-testid="add controlnet"
|
|
||||||
flexGrow={1}
|
|
||||||
isDisabled={isAddControlNetDisabled}
|
|
||||||
>
|
|
||||||
{t('common.controlNet')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
tooltip={t('controlnet.addIPAdapter')}
|
|
||||||
leftIcon={<PiPlusBold />}
|
|
||||||
onClick={addIPAdapter}
|
|
||||||
data-testid="add ip adapter"
|
|
||||||
flexGrow={1}
|
|
||||||
isDisabled={isAddIPAdapterDisabled}
|
|
||||||
>
|
|
||||||
{t('common.ipAdapter')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
tooltip={t('controlnet.addT2IAdapter')}
|
|
||||||
leftIcon={<PiPlusBold />}
|
|
||||||
onClick={addT2IAdapter}
|
|
||||||
data-testid="add t2i adapter"
|
|
||||||
flexGrow={1}
|
|
||||||
isDisabled={isAddT2IAdapterDisabled}
|
|
||||||
>
|
|
||||||
{t('common.t2iAdapter')}
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
{controlAdapterIds.map((id, i) => (
|
|
||||||
<Fragment key={id}>
|
|
||||||
<Divider />
|
|
||||||
<ControlAdapterConfig id={id} number={i + 1} />
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</Flex>
|
|
||||||
</StandaloneAccordion>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
ControlSettingsAccordion.displayName = 'ControlAdaptersSettingsAccordion';
|
|
@ -2,7 +2,6 @@ import type { FormLabelProps } from '@invoke-ai/ui-library';
|
|||||||
import { Expander, Flex, FormControlGroup, StandaloneAccordion } from '@invoke-ai/ui-library';
|
import { Expander, Flex, FormControlGroup, StandaloneAccordion } from '@invoke-ai/ui-library';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
|
|
||||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { HrfSettings } from 'features/hrf/components/HrfSettings';
|
import { HrfSettings } from 'features/hrf/components/HrfSettings';
|
||||||
import { selectHrfSlice } from 'features/hrf/store/hrfSlice';
|
import { selectHrfSlice } from 'features/hrf/store/hrfSlice';
|
||||||
@ -20,35 +19,23 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { ImageSizeCanvas } from './ImageSizeCanvas';
|
|
||||||
import { ImageSizeLinear } from './ImageSizeLinear';
|
import { ImageSizeLinear } from './ImageSizeLinear';
|
||||||
|
|
||||||
const selector = createMemoizedSelector(
|
const selector = createMemoizedSelector(
|
||||||
[selectGenerationSlice, selectCanvasSlice, selectHrfSlice, selectCanvasV2Slice, activeTabNameSelector],
|
[selectGenerationSlice, selectHrfSlice, selectCanvasV2Slice, activeTabNameSelector],
|
||||||
(generation, canvas, hrf, controlLayers, activeTabName) => {
|
(generation, hrf, canvasV2, activeTabName) => {
|
||||||
const { shouldRandomizeSeed, model } = generation;
|
const { shouldRandomizeSeed, model } = generation;
|
||||||
const { hrfEnabled } = hrf;
|
const { hrfEnabled } = hrf;
|
||||||
const badges: string[] = [];
|
const badges: string[] = [];
|
||||||
const isSDXL = model?.base === 'sdxl';
|
const isSDXL = model?.base === 'sdxl';
|
||||||
|
|
||||||
if (activeTabName === 'canvas') {
|
|
||||||
const {
|
|
||||||
aspectRatio,
|
|
||||||
boundingBoxDimensions: { width, height },
|
|
||||||
} = canvas;
|
|
||||||
badges.push(`${width}×${height}`);
|
|
||||||
badges.push(aspectRatio.id);
|
|
||||||
if (aspectRatio.isLocked) {
|
|
||||||
badges.push('locked');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const { aspectRatio, width, height } = canvasV2.size;
|
const { aspectRatio, width, height } = canvasV2.size;
|
||||||
badges.push(`${width}×${height}`);
|
badges.push(`${width}×${height}`);
|
||||||
badges.push(aspectRatio.id);
|
badges.push(aspectRatio.id);
|
||||||
|
|
||||||
if (aspectRatio.isLocked) {
|
if (aspectRatio.isLocked) {
|
||||||
badges.push('locked');
|
badges.push('locked');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!shouldRandomizeSeed) {
|
if (!shouldRandomizeSeed) {
|
||||||
badges.push('Manual Seed');
|
badges.push('Manual Seed');
|
||||||
@ -86,8 +73,8 @@ export const ImageSettingsAccordion = memo(() => {
|
|||||||
>
|
>
|
||||||
<Flex px={4} pt={4} w="full" h="full" flexDir="column" data-testid="image-settings-accordion">
|
<Flex px={4} pt={4} w="full" h="full" flexDir="column" data-testid="image-settings-accordion">
|
||||||
<Flex flexDir="column" gap={4}>
|
<Flex flexDir="column" gap={4}>
|
||||||
{activeTabName === 'canvas' ? <ImageSizeCanvas /> : <ImageSizeLinear />}
|
<ImageSizeLinear />
|
||||||
{activeTabName === 'canvas' && <ParamImageToImageStrength />}
|
<ParamImageToImageStrength />
|
||||||
</Flex>
|
</Flex>
|
||||||
<Expander label={t('accordions.advanced.options')} isOpen={isOpenExpander} onToggle={onToggleExpander}>
|
<Expander label={t('accordions.advanced.options')} isOpen={isOpenExpander} onToggle={onToggleExpander}>
|
||||||
<Flex gap={4} pb={4} flexDir="column">
|
<Flex gap={4} pb={4} flexDir="column">
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { aspectRatioChanged, setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
|
||||||
import ParamBoundingBoxHeight from 'features/parameters/components/Canvas/BoundingBox/ParamBoundingBoxHeight';
|
|
||||||
import ParamBoundingBoxWidth from 'features/parameters/components/Canvas/BoundingBox/ParamBoundingBoxWidth';
|
|
||||||
import { AspectRatioIconPreview } from 'features/parameters/components/ImageSize/AspectRatioIconPreview';
|
|
||||||
import { ImageSize } from 'features/parameters/components/ImageSize/ImageSize';
|
|
||||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
|
||||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
|
||||||
import { memo, useCallback } from 'react';
|
|
||||||
|
|
||||||
export const ImageSizeCanvas = memo(() => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const { width, height } = useAppSelector((s) => s.canvas.boundingBoxDimensions);
|
|
||||||
const aspectRatioState = useAppSelector((s) => s.canvas.aspectRatio);
|
|
||||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
|
||||||
|
|
||||||
const onChangeWidth = useCallback(
|
|
||||||
(width: number) => {
|
|
||||||
if (width === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(setBoundingBoxDimensions({ width }, optimalDimension));
|
|
||||||
},
|
|
||||||
[dispatch, optimalDimension]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onChangeHeight = useCallback(
|
|
||||||
(height: number) => {
|
|
||||||
if (height === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(setBoundingBoxDimensions({ height }, optimalDimension));
|
|
||||||
},
|
|
||||||
[dispatch, optimalDimension]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onChangeAspectRatioState = useCallback(
|
|
||||||
(aspectRatioState: AspectRatioState) => {
|
|
||||||
dispatch(aspectRatioChanged(aspectRatioState));
|
|
||||||
},
|
|
||||||
[dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ImageSize
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
aspectRatioState={aspectRatioState}
|
|
||||||
heightComponent={<ParamBoundingBoxHeight />}
|
|
||||||
widthComponent={<ParamBoundingBoxWidth />}
|
|
||||||
previewComponent={<AspectRatioIconPreview />}
|
|
||||||
onChangeAspectRatioState={onChangeAspectRatioState}
|
|
||||||
onChangeWidth={onChangeWidth}
|
|
||||||
onChangeHeight={onChangeHeight}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
ImageSizeCanvas.displayName = 'ImageSizeCanvas';
|
|
@ -1,6 +1,4 @@
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
|
||||||
import { controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice';
|
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -40,8 +38,8 @@ export const useClearIntermediates = (shouldShowClearIntermediates: boolean): Us
|
|||||||
_clearIntermediates()
|
_clearIntermediates()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.then((clearedCount) => {
|
.then((clearedCount) => {
|
||||||
dispatch(controlAdaptersReset());
|
// dispatch(controlAdaptersReset());
|
||||||
dispatch(resetCanvas());
|
// dispatch(resetCanvas());
|
||||||
toast({
|
toast({
|
||||||
id: 'INTERMEDIATES_CLEARED',
|
id: 'INTERMEDIATES_CLEARED',
|
||||||
title: t('settings.intermediatesCleared', { count: clearedCount }),
|
title: t('settings.intermediatesCleared', { count: clearedCount }),
|
||||||
|
@ -16,7 +16,6 @@ import ModelManagerTab from 'features/ui/components/tabs/ModelManagerTab';
|
|||||||
import NodesTab from 'features/ui/components/tabs/NodesTab';
|
import NodesTab from 'features/ui/components/tabs/NodesTab';
|
||||||
import QueueTab from 'features/ui/components/tabs/QueueTab';
|
import QueueTab from 'features/ui/components/tabs/QueueTab';
|
||||||
import TextToImageTab from 'features/ui/components/tabs/TextToImageTab';
|
import TextToImageTab from 'features/ui/components/tabs/TextToImageTab';
|
||||||
import UnifiedCanvasTab from 'features/ui/components/tabs/UnifiedCanvasTab';
|
|
||||||
import type { UsePanelOptions } from 'features/ui/hooks/usePanel';
|
import type { UsePanelOptions } from 'features/ui/hooks/usePanel';
|
||||||
import { usePanel } from 'features/ui/hooks/usePanel';
|
import { usePanel } from 'features/ui/hooks/usePanel';
|
||||||
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
|
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
|
||||||
@ -30,11 +29,10 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { MdZoomOutMap } from 'react-icons/md';
|
import { MdZoomOutMap } from 'react-icons/md';
|
||||||
import { PiFlowArrowBold } from 'react-icons/pi';
|
import { PiFlowArrowBold } from 'react-icons/pi';
|
||||||
import { RiBox2Line, RiBrushLine, RiInputMethodLine, RiPlayList2Fill } from 'react-icons/ri';
|
import { RiBox2Line, RiInputMethodLine, RiPlayList2Fill } from 'react-icons/ri';
|
||||||
import type { ImperativePanelGroupHandle } from 'react-resizable-panels';
|
import type { ImperativePanelGroupHandle } from 'react-resizable-panels';
|
||||||
import { Panel, PanelGroup } from 'react-resizable-panels';
|
import { Panel, PanelGroup } from 'react-resizable-panels';
|
||||||
|
|
||||||
import ParametersPanelCanvas from './ParametersPanels/ParametersPanelCanvas';
|
|
||||||
import ParametersPanelUpscale from './ParametersPanels/ParametersPanelUpscale';
|
import ParametersPanelUpscale from './ParametersPanels/ParametersPanelUpscale';
|
||||||
import ResizeHandle from './tabs/ResizeHandle';
|
import ResizeHandle from './tabs/ResizeHandle';
|
||||||
import UpscalingTab from './tabs/UpscalingTab';
|
import UpscalingTab from './tabs/UpscalingTab';
|
||||||
@ -55,13 +53,6 @@ const TAB_DATA: Record<InvokeTabName, TabData> = {
|
|||||||
content: <TextToImageTab />,
|
content: <TextToImageTab />,
|
||||||
parametersPanel: <ParametersPanelTextToImage />,
|
parametersPanel: <ParametersPanelTextToImage />,
|
||||||
},
|
},
|
||||||
canvas: {
|
|
||||||
id: 'canvas',
|
|
||||||
translationKey: 'ui.tabs.canvas',
|
|
||||||
icon: <RiBrushLine />,
|
|
||||||
content: <UnifiedCanvasTab />,
|
|
||||||
parametersPanel: <ParametersPanelCanvas />,
|
|
||||||
},
|
|
||||||
upscaling: {
|
upscaling: {
|
||||||
id: 'upscaling',
|
id: 'upscaling',
|
||||||
translationKey: 'ui.tabs.upscaling',
|
translationKey: 'ui.tabs.upscaling',
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
|
||||||
import { Prompts } from 'features/parameters/components/Prompts/Prompts';
|
|
||||||
import QueueControls from 'features/queue/components/QueueControls';
|
|
||||||
import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion';
|
|
||||||
import { CompositingSettingsAccordion } from 'features/settingsAccordions/components/CompositingSettingsAccordion/CompositingSettingsAccordion';
|
|
||||||
import { ControlSettingsAccordion } from 'features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion';
|
|
||||||
import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion';
|
|
||||||
import { ImageSettingsAccordion } from 'features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion';
|
|
||||||
import { RefinerSettingsAccordion } from 'features/settingsAccordions/components/RefinerSettingsAccordion/RefinerSettingsAccordion';
|
|
||||||
import { StylePresetMenu } from 'features/stylePresets/components/StylePresetMenu';
|
|
||||||
import { StylePresetMenuTrigger } from 'features/stylePresets/components/StylePresetMenuTrigger';
|
|
||||||
import { $isMenuOpen } from 'features/stylePresets/store/isMenuOpen';
|
|
||||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
|
||||||
import type { CSSProperties } from 'react';
|
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
const overlayScrollbarsStyles: CSSProperties = {
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
};
|
|
||||||
|
|
||||||
const ParametersPanelCanvas = () => {
|
|
||||||
const isSDXL = useAppSelector((s) => s.generation.model?.base === 'sdxl');
|
|
||||||
const isMenuOpen = useStore($isMenuOpen);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex w="full" h="full" flexDir="column" gap={2}>
|
|
||||||
<QueueControls />
|
|
||||||
<StylePresetMenuTrigger />
|
|
||||||
<Flex w="full" h="full" position="relative">
|
|
||||||
<Box position="absolute" top={0} left={0} right={0} bottom={0}>
|
|
||||||
{isMenuOpen && (
|
|
||||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
|
||||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
|
||||||
<StylePresetMenu />
|
|
||||||
</Flex>
|
|
||||||
</OverlayScrollbarsComponent>
|
|
||||||
)}
|
|
||||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
|
||||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
|
||||||
<Prompts />
|
|
||||||
<ImageSettingsAccordion />
|
|
||||||
<GenerationSettingsAccordion />
|
|
||||||
<ControlSettingsAccordion />
|
|
||||||
<CompositingSettingsAccordion />
|
|
||||||
{isSDXL && <RefinerSettingsAccordion />}
|
|
||||||
<AdvancedSettingsAccordion />
|
|
||||||
</Flex>
|
|
||||||
</OverlayScrollbarsComponent>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(ParametersPanelCanvas);
|
|
@ -10,7 +10,6 @@ import { Prompts } from 'features/parameters/components/Prompts/Prompts';
|
|||||||
import QueueControls from 'features/queue/components/QueueControls';
|
import QueueControls from 'features/queue/components/QueueControls';
|
||||||
import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion';
|
import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion';
|
||||||
import { CompositingSettingsAccordion } from 'features/settingsAccordions/components/CompositingSettingsAccordion/CompositingSettingsAccordion';
|
import { CompositingSettingsAccordion } from 'features/settingsAccordions/components/CompositingSettingsAccordion/CompositingSettingsAccordion';
|
||||||
import { ControlSettingsAccordion } from 'features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion';
|
|
||||||
import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion';
|
import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion';
|
||||||
import { ImageSettingsAccordion } from 'features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion';
|
import { ImageSettingsAccordion } from 'features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion';
|
||||||
import { RefinerSettingsAccordion } from 'features/settingsAccordions/components/RefinerSettingsAccordion/RefinerSettingsAccordion';
|
import { RefinerSettingsAccordion } from 'features/settingsAccordions/components/RefinerSettingsAccordion/RefinerSettingsAccordion';
|
||||||
@ -44,7 +43,7 @@ const ParametersPanelTextToImage = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
const controlLayersCount = useAppSelector((s) => s.canvasV2.layers.length);
|
const controlLayersCount = useAppSelector((s) => s.layers.layers.length);
|
||||||
const controlLayersTitle = useMemo(() => {
|
const controlLayersTitle = useMemo(() => {
|
||||||
if (controlLayersCount === 0) {
|
if (controlLayersCount === 0) {
|
||||||
return t('controlLayers.controlLayers');
|
return t('controlLayers.controlLayers');
|
||||||
@ -108,8 +107,7 @@ const ParametersPanelTextToImage = () => {
|
|||||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||||
<ImageSettingsAccordion />
|
<ImageSettingsAccordion />
|
||||||
<GenerationSettingsAccordion />
|
<GenerationSettingsAccordion />
|
||||||
{activeTabName !== 'generation' && <ControlSettingsAccordion />}
|
<CompositingSettingsAccordion />
|
||||||
{activeTabName === 'canvas' && <CompositingSettingsAccordion />}
|
|
||||||
{isSDXL && <RefinerSettingsAccordion />}
|
{isSDXL && <RefinerSettingsAccordion />}
|
||||||
<AdvancedSettingsAccordion />
|
<AdvancedSettingsAccordion />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
import { Flex } from '@invoke-ai/ui-library';
|
|
||||||
import IAIDropOverlay from 'common/components/IAIDropOverlay';
|
|
||||||
import IAICanvas from 'features/canvas/components/IAICanvas';
|
|
||||||
import IAICanvasToolbar from 'features/canvas/components/IAICanvasToolbar/IAICanvasToolbar';
|
|
||||||
import { CANVAS_TAB_TESTID } from 'features/canvas/store/constants';
|
|
||||||
import { useDroppableTypesafe } from 'features/dnd/hooks/typesafeHooks';
|
|
||||||
import type { CanvasInitialImageDropData } from 'features/dnd/types';
|
|
||||||
import { isValidDrop } from 'features/dnd/util/isValidDrop';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const droppableData: CanvasInitialImageDropData = {
|
|
||||||
id: 'canvas-intial-image',
|
|
||||||
actionType: 'SET_CANVAS_INITIAL_IMAGE',
|
|
||||||
};
|
|
||||||
|
|
||||||
const UnifiedCanvasTab = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const {
|
|
||||||
isOver,
|
|
||||||
setNodeRef: setDroppableRef,
|
|
||||||
active,
|
|
||||||
} = useDroppableTypesafe({
|
|
||||||
id: 'unifiedCanvas',
|
|
||||||
data: droppableData,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
position="relative"
|
|
||||||
layerStyle="first"
|
|
||||||
ref={setDroppableRef}
|
|
||||||
flexDirection="column"
|
|
||||||
alignItems="center"
|
|
||||||
gap={4}
|
|
||||||
p={2}
|
|
||||||
borderRadius="base"
|
|
||||||
w="full"
|
|
||||||
h="full"
|
|
||||||
data-testid={CANVAS_TAB_TESTID}
|
|
||||||
>
|
|
||||||
<IAICanvasToolbar />
|
|
||||||
<IAICanvas />
|
|
||||||
{isValidDrop(droppableData, active?.data.current) && (
|
|
||||||
<IAIDropOverlay isOver={isOver} label={t('toast.setCanvasInitialImage')} />
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(UnifiedCanvasTab);
|
|
@ -1,3 +1,3 @@
|
|||||||
export const TAB_NUMBER_MAP = ['generation', 'canvas', 'upscaling', 'workflows', 'models', 'queue'] as const;
|
export const TAB_NUMBER_MAP = ['generation', 'upscaling', 'workflows', 'models', 'queue'] as const;
|
||||||
|
|
||||||
export type InvokeTabName = (typeof TAB_NUMBER_MAP)[number];
|
export type InvokeTabName = (typeof TAB_NUMBER_MAP)[number];
|
||||||
|
@ -195,44 +195,28 @@ export type OutputFields<T extends AnyInvocation> = Extract<
|
|||||||
// Node Outputs
|
// Node Outputs
|
||||||
export type ImageOutput = S['ImageOutput'];
|
export type ImageOutput = S['ImageOutput'];
|
||||||
|
|
||||||
// Post-image upload actions, controls workflows when images are uploaded
|
export type CAImagePostUploadAction = {
|
||||||
|
type: 'SET_CA_IMAGE';
|
||||||
type ControlAdapterAction = {
|
|
||||||
type: 'SET_CONTROL_ADAPTER_IMAGE';
|
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CALayerImagePostUploadAction = {
|
|
||||||
type: 'SET_CA_LAYER_IMAGE';
|
|
||||||
layerId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type IPALayerImagePostUploadAction = {
|
export type IPALayerImagePostUploadAction = {
|
||||||
type: 'SET_IPA_LAYER_IMAGE';
|
type: 'SET_IPA_IMAGE';
|
||||||
layerId: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RGLayerIPAdapterImagePostUploadAction = {
|
export type RGIPAdapterImagePostUploadAction = {
|
||||||
type: 'SET_RG_LAYER_IP_ADAPTER_IMAGE';
|
type: 'SET_RG_IP_ADAPTER_IMAGE';
|
||||||
layerId: string;
|
id: string;
|
||||||
ipAdapterId: string;
|
ipAdapterId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IILayerImagePostUploadAction = {
|
|
||||||
type: 'SET_II_LAYER_IMAGE';
|
|
||||||
layerId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type NodesAction = {
|
type NodesAction = {
|
||||||
type: 'SET_NODES_IMAGE';
|
type: 'SET_NODES_IMAGE';
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CanvasInitialImageAction = {
|
|
||||||
type: 'SET_CANVAS_INITIAL_IMAGE';
|
|
||||||
};
|
|
||||||
|
|
||||||
type UpscaleInitialImageAction = {
|
type UpscaleInitialImageAction = {
|
||||||
type: 'SET_UPSCALE_INITIAL_IMAGE';
|
type: 'SET_UPSCALE_INITIAL_IMAGE';
|
||||||
};
|
};
|
||||||
@ -247,13 +231,10 @@ type AddToBatchAction = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type PostUploadAction =
|
export type PostUploadAction =
|
||||||
| ControlAdapterAction
|
|
||||||
| NodesAction
|
| NodesAction
|
||||||
| CanvasInitialImageAction
|
|
||||||
| ToastAction
|
| ToastAction
|
||||||
| AddToBatchAction
|
| AddToBatchAction
|
||||||
| CALayerImagePostUploadAction
|
| CAImagePostUploadAction
|
||||||
| IPALayerImagePostUploadAction
|
| IPALayerImagePostUploadAction
|
||||||
| RGLayerIPAdapterImagePostUploadAction
|
| RGIPAdapterImagePostUploadAction
|
||||||
| IILayerImagePostUploadAction
|
|
||||||
| UpscaleInitialImageAction;
|
| UpscaleInitialImageAction;
|
||||||
|
Loading…
Reference in New Issue
Block a user