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
58c656224f
commit
9fe727c9f8
@ -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 { RootState } from 'app/store/store';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
/**
|
||||
@ -19,3 +20,5 @@ export const getSelectorsOptions: GetSelectorsOptions = {
|
||||
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 { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
|
||||
import type { JSONObject } from 'common/types';
|
||||
import { canvasPersistConfig } from 'features/canvas/store/canvasSlice';
|
||||
import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice';
|
||||
import {
|
||||
controlAdaptersV2PersistConfig,
|
||||
@ -104,7 +103,6 @@ export type PersistConfig<T = any> = {
|
||||
};
|
||||
|
||||
const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
|
||||
[canvasPersistConfig.name]: canvasPersistConfig,
|
||||
[galleryPersistConfig.name]: galleryPersistConfig,
|
||||
[generationPersistConfig.name]: generationPersistConfig,
|
||||
[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 { InvokeTabName } from 'features/ui/store/tabMap';
|
||||
import type { O } from 'ts-toolbelt';
|
||||
@ -83,7 +83,7 @@ export type AppConfig = {
|
||||
sd: {
|
||||
defaultModel?: string;
|
||||
disabledControlNetModels: string[];
|
||||
disabledControlNetProcessors: (keyof typeof CONTROLNET_PROCESSORS)[];
|
||||
disabledControlNetProcessors: ProcessorTypeV2;
|
||||
// Core parameters
|
||||
iterations: NumericalParameterConfig;
|
||||
width: NumericalParameterConfig; // initial value comes from model
|
||||
|
@ -17,10 +17,6 @@ const accept: Accept = {
|
||||
const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (activeTabName) => {
|
||||
let postUploadAction: PostUploadAction = { type: 'TOAST' };
|
||||
|
||||
if (activeTabName === 'canvas') {
|
||||
postUploadAction = { type: 'SET_CANVAS_INITIAL_IMAGE' };
|
||||
}
|
||||
|
||||
if (activeTabName === 'upscaling') {
|
||||
postUploadAction = { type: 'SET_UPSCALE_INITIAL_IMAGE' };
|
||||
}
|
||||
@ -30,10 +26,9 @@ const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (ac
|
||||
|
||||
export const useFullscreenDropzone = () => {
|
||||
const { t } = useTranslation();
|
||||
const postUploadAction = useAppSelector(selectPostUploadAction);
|
||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
||||
|
||||
const postUploadAction = useAppSelector(selectPostUploadAction);
|
||||
const [uploadImage] = useUploadImageMutation();
|
||||
|
||||
const fileRejectionCallback = useCallback(
|
||||
|
@ -74,14 +74,6 @@ export const useGlobalHotkeys = () => {
|
||||
|
||||
useHotkeys(
|
||||
'2',
|
||||
() => {
|
||||
dispatch(setActiveTab('canvas'));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'3',
|
||||
() => {
|
||||
dispatch(setActiveTab('workflows'));
|
||||
},
|
||||
@ -89,7 +81,7 @@ export const useGlobalHotkeys = () => {
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'4',
|
||||
'3',
|
||||
() => {
|
||||
if (isModelManagerEnabled) {
|
||||
dispatch(setActiveTab('models'));
|
||||
@ -99,7 +91,7 @@ export const useGlobalHotkeys = () => {
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
isModelManagerEnabled ? '5' : '4',
|
||||
isModelManagerEnabled ? '4' : '3',
|
||||
() => {
|
||||
dispatch(setActiveTab('queue'));
|
||||
},
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
selectControlAdapterAll,
|
||||
selectControlAdaptersSlice,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
||||
import { selectControlAdaptersV2Slice } from 'features/controlLayers/store/controlAdaptersSlice';
|
||||
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 { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
|
||||
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
||||
@ -24,43 +23,49 @@ import { forEach, upperFirst } from 'lodash-es';
|
||||
import { useMemo } from 'react';
|
||||
import { getConnectedEdges } from 'reactflow';
|
||||
|
||||
const LAYER_TYPE_TO_TKEY: Record<LayerData['type'], string> = {
|
||||
initial_image_layer: 'controlLayers.globalInitialImage',
|
||||
control_adapter_layer: 'controlLayers.globalControlAdapter',
|
||||
ip_adapter_layer: 'controlLayers.globalIPAdapter',
|
||||
regional_guidance_layer: 'controlLayers.regionalGuidance',
|
||||
raster_layer: 'controlLayers.raster',
|
||||
const LAYER_TYPE_TO_TKEY: Record<CanvasEntity['type'], string> = {
|
||||
control_adapter: 'controlLayers.globalControlAdapter',
|
||||
ip_adapter: 'controlLayers.globalIPAdapter',
|
||||
regional_guidance: 'controlLayers.regionalGuidance',
|
||||
layer: 'controlLayers.raster',
|
||||
inpaint_mask: 'controlLayers.inpaintMask',
|
||||
};
|
||||
|
||||
const createSelector = (templates: Templates) =>
|
||||
createMemoizedSelector(
|
||||
[
|
||||
selectControlAdaptersSlice,
|
||||
selectGenerationSlice,
|
||||
selectSystemSlice,
|
||||
selectNodesSlice,
|
||||
selectWorkflowSettingsSlice,
|
||||
selectDynamicPromptsSlice,
|
||||
selectCanvasV2Slice,
|
||||
selectLayersSlice,
|
||||
selectControlAdaptersV2Slice,
|
||||
selectRegionalGuidanceSlice,
|
||||
selectIPAdaptersSlice,
|
||||
activeTabNameSelector,
|
||||
selectUpscalelice,
|
||||
selectConfigSlice,
|
||||
],
|
||||
(
|
||||
controlAdapters,
|
||||
generation,
|
||||
system,
|
||||
nodes,
|
||||
workflowSettings,
|
||||
dynamicPrompts,
|
||||
controlLayers,
|
||||
canvasV2,
|
||||
layersState,
|
||||
controlAdaptersState,
|
||||
regionalGuidanceState,
|
||||
ipAdaptersState,
|
||||
activeTabName,
|
||||
upscale,
|
||||
config
|
||||
) => {
|
||||
const { model } = generation;
|
||||
const { size } = canvasV2;
|
||||
const { positivePrompt } = canvasV2;
|
||||
const { positivePrompt } = canvasV2.prompts;
|
||||
|
||||
const { isConnected } = system;
|
||||
|
||||
@ -115,6 +120,26 @@ const createSelector = (templates: Templates) =>
|
||||
});
|
||||
});
|
||||
}
|
||||
} else if (activeTabName === 'upscaling') {
|
||||
if (!upscale.upscaleInitialImage) {
|
||||
reasons.push({ content: i18n.t('upscaling.missingUpscaleInitialImage') });
|
||||
} else if (config.maxUpscaleDimension) {
|
||||
const { width, height } = upscale.upscaleInitialImage;
|
||||
const { scale } = upscale;
|
||||
|
||||
const maxPixels = config.maxUpscaleDimension ** 2;
|
||||
const upscaledPixels = width * scale * height * scale;
|
||||
|
||||
if (upscaledPixels > maxPixels) {
|
||||
reasons.push({ content: i18n.t('upscaling.exceedsMaxSize') });
|
||||
}
|
||||
}
|
||||
if (!upscale.upscaleModel) {
|
||||
reasons.push({ content: i18n.t('upscaling.missingUpscaleModel') });
|
||||
}
|
||||
if (!upscale.tileControlnetModel) {
|
||||
reasons.push({ content: i18n.t('upscaling.missingTileControlNetModel') });
|
||||
}
|
||||
} else {
|
||||
if (dynamicPrompts.prompts.length === 0 && getShouldProcessPrompt(positivePrompt)) {
|
||||
reasons.push({ content: i18n.t('parameters.invoke.noPrompts') });
|
||||
@ -124,140 +149,128 @@ const createSelector = (templates: Templates) =>
|
||||
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') {
|
||||
if (!upscale.upscaleInitialImage) {
|
||||
reasons.push({ content: i18n.t('upscaling.missingUpscaleInitialImage') });
|
||||
} else if (config.maxUpscaleDimension) {
|
||||
const { width, height } = upscale.upscaleInitialImage;
|
||||
const { scale } = upscale;
|
||||
|
||||
const maxPixels = config.maxUpscaleDimension ** 2;
|
||||
const upscaledPixels = width * scale * height * scale;
|
||||
|
||||
if (upscaledPixels > maxPixels) {
|
||||
reasons.push({ content: i18n.t('upscaling.exceedsMaxSize') });
|
||||
controlAdaptersState.controlAdapters
|
||||
.filter((ca) => ca.isEnabled)
|
||||
.forEach((ca, i) => {
|
||||
const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||
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) {
|
||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoModelSelected'));
|
||||
}
|
||||
}
|
||||
if (!upscale.upscaleModel) {
|
||||
reasons.push({ content: i18n.t('upscaling.missingUpscaleModel') });
|
||||
}
|
||||
if (!upscale.tileControlnetModel) {
|
||||
reasons.push({ content: i18n.t('upscaling.missingTileControlNetModel') });
|
||||
}
|
||||
} else {
|
||||
// Handling for all other tabs
|
||||
selectControlAdapterAll(controlAdapters)
|
||||
.filter((ca) => ca.isEnabled)
|
||||
.forEach((ca, i) => {
|
||||
if (!ca.isEnabled) {
|
||||
return;
|
||||
// Model base must match
|
||||
if (ca.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 (!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 (!ca.model) {
|
||||
reasons.push({ content: i18n.t('parameters.invoke.noModelForControlAdapter', { number: i + 1 }) });
|
||||
} else if (ca.model.base !== model?.base) {
|
||||
// This should never happen, just a sanity check
|
||||
reasons.push({
|
||||
content: i18n.t('parameters.invoke.incompatibleBaseModelForControlAdapter', { number: i + 1 }),
|
||||
});
|
||||
if (problems.length) {
|
||||
const content = upperFirst(problems.join(', '));
|
||||
reasons.push({ prefix, content });
|
||||
}
|
||||
});
|
||||
|
||||
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'));
|
||||
}
|
||||
|
||||
if (
|
||||
!ca.controlImage ||
|
||||
(isControlNetOrT2IAdapter(ca) && !ca.processedControlImage && ca.processorType !== 'none')
|
||||
) {
|
||||
reasons.push({
|
||||
content: i18n.t('parameters.invoke.noControlImageForControlAdapter', { number: i + 1 }),
|
||||
});
|
||||
// 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 };
|
||||
|
@ -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 {
|
||||
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);
|
||||
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 { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useAddCALayer, useAddIILayer, useAddIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import { layerAdded, regionalGuidanceAdded } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { useAddCALayer, useAddIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import { layerAdded } from 'features/controlLayers/store/layersSlice';
|
||||
import { rgAdded } from 'features/controlLayers/store/regionalGuidanceSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiPlusBold } from 'react-icons/pi';
|
||||
@ -11,9 +12,8 @@ export const AddLayerButton = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [addCALayer, isAddCALayerDisabled] = useAddCALayer();
|
||||
const [addIPALayer, isAddIPALayerDisabled] = useAddIPALayer();
|
||||
const [addIILayer, isAddIILayerDisabled] = useAddIILayer();
|
||||
const addRGLayer = useCallback(() => {
|
||||
dispatch(regionalGuidanceAdded());
|
||||
dispatch(rgAdded());
|
||||
}, [dispatch]);
|
||||
const addRasterLayer = useCallback(() => {
|
||||
dispatch(layerAdded());
|
||||
@ -42,9 +42,6 @@ export const AddLayerButton = memo(() => {
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addIPALayer} isDisabled={isAddIPALayerDisabled}>
|
||||
{t('controlLayers.globalIPAdapterLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addIILayer} isDisabled={isAddIILayerDisabled}>
|
||||
{t('controlLayers.globalInitialImageLayer')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
|
@ -1,44 +1,42 @@
|
||||
import { Button, Flex } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAddIPAdapterToIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import { useAddIPAdapterToRGLayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import {
|
||||
regionalGuidanceNegativePromptChanged,
|
||||
regionalGuidancePositivePromptChanged,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isRegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
||||
rgNegativePromptChanged,
|
||||
rgPositivePromptChanged,
|
||||
selectRegionalGuidanceSlice,
|
||||
} from 'features/controlLayers/store/regionalGuidanceSlice';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiPlusBold } from 'react-icons/pi';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
type AddPromptButtonProps = {
|
||||
layerId: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => {
|
||||
export const AddPromptButtons = ({ id }: AddPromptButtonProps) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToIPALayer(layerId);
|
||||
const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToRGLayer(id);
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = canvasV2.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
createMemoizedSelector(selectRegionalGuidanceSlice, (regionalGuidanceState) => {
|
||||
const rg = regionalGuidanceState.regions.find((rg) => rg.id === id);
|
||||
return {
|
||||
canAddPositivePrompt: layer.positivePrompt === null,
|
||||
canAddNegativePrompt: layer.negativePrompt === null,
|
||||
canAddPositivePrompt: rg?.positivePrompt === null,
|
||||
canAddNegativePrompt: rg?.negativePrompt === null,
|
||||
};
|
||||
}),
|
||||
[layerId]
|
||||
[id]
|
||||
);
|
||||
const validActions = useAppSelector(selectValidActions);
|
||||
const addPositivePrompt = useCallback(() => {
|
||||
dispatch(regionalGuidancePositivePromptChanged({ layerId, prompt: '' }));
|
||||
}, [dispatch, layerId]);
|
||||
dispatch(rgPositivePromptChanged({ id, prompt: '' }));
|
||||
}, [dispatch, id]);
|
||||
const addNegativePrompt = useCallback(() => {
|
||||
dispatch(regionalGuidanceNegativePromptChanged({ layerId, prompt: '' }));
|
||||
}, [dispatch, layerId]);
|
||||
dispatch(rgNegativePromptChanged({ id, prompt: '' }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
return (
|
||||
<Flex w="full" p={2} justifyContent="space-between">
|
||||
|
@ -10,33 +10,33 @@ import {
|
||||
PopoverTrigger,
|
||||
} from '@invoke-ai/ui-library';
|
||||
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 { useTranslation } from 'react-i18next';
|
||||
|
||||
const marks = [0, 100, 200, 300];
|
||||
const formatPx = (v: number | string) => `${v} px`;
|
||||
|
||||
export const BrushSize = memo(() => {
|
||||
export const BrushWidth = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const brushSize = useAppSelector((s) => s.canvasV2.brushSize);
|
||||
const width = useAppSelector((s) => s.canvasV2.tool.brush.width);
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(brushSizeChanged(Math.round(v)));
|
||||
dispatch(brushWidthChanged(Math.round(v)));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
return (
|
||||
<FormControl w="min-content" gap={2}>
|
||||
<FormLabel m={0}>{t('controlLayers.brushSize')}</FormLabel>
|
||||
<FormLabel m={0}>{t('controlLayers.brushWidth')}</FormLabel>
|
||||
<Popover isLazy>
|
||||
<PopoverTrigger>
|
||||
<CompositeNumberInput
|
||||
min={1}
|
||||
max={600}
|
||||
defaultValue={initialControlLayersState.brushSize}
|
||||
value={brushSize}
|
||||
defaultValue={50}
|
||||
value={width}
|
||||
onChange={onChange}
|
||||
w={24}
|
||||
format={formatPx}
|
||||
@ -45,14 +45,7 @@ export const BrushSize = memo(() => {
|
||||
<PopoverContent w={200} py={2} px={4}>
|
||||
<PopoverArrow />
|
||||
<PopoverBody>
|
||||
<CompositeSlider
|
||||
min={1}
|
||||
max={300}
|
||||
defaultValue={initialControlLayersState.brushSize}
|
||||
value={brushSize}
|
||||
onChange={onChange}
|
||||
marks={marks}
|
||||
/>
|
||||
<CompositeSlider min={1} max={300} defaultValue={50} value={width} onChange={onChange} marks={marks} />
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</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 ariaLabel = ['Begin Step %', 'End Step %'];
|
||||
|
||||
export const ControlAdapterBeginEndStepPct = memo(({ beginEndStepPct, onChange }: Props) => {
|
||||
export const BeginEndStepPct = memo(({ beginEndStepPct, onChange }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const onReset = useCallback(() => {
|
||||
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 marks = [0, 1, 2];
|
||||
|
||||
export const ControlAdapterWeight = memo(({ weight, onChange }: Props) => {
|
||||
export const Weight = memo(({ weight, onChange }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const initial = useAppSelector((s) => s.config.sd.ca.weight.initial);
|
||||
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 { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import type { ControlModeV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import { isControlModeV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { ControlModeV2} from 'features/controlLayers/store/types';
|
||||
import { isControlModeV2 } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { assert } from 'tsafe';
|
||||
@ -12,7 +12,7 @@ type Props = {
|
||||
onChange: (controlMode: ControlModeV2) => void;
|
||||
};
|
||||
|
||||
export const ControlAdapterControlModeSelect = memo(({ controlMode, onChange }: Props) => {
|
||||
export const CAControlModeSelect = memo(({ controlMode, onChange }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
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 IAIDndImage from 'common/components/IAIDndImage';
|
||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||
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 { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold, PiFloppyDiskBold, PiRulerBold } from 'react-icons/pi';
|
||||
@ -22,7 +20,7 @@ import {
|
||||
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2;
|
||||
controlAdapter: ControlAdapterData;
|
||||
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
||||
droppableData: TypesafeDroppableData;
|
||||
postUploadAction: PostUploadAction;
|
||||
@ -30,7 +28,7 @@ type Props = {
|
||||
onErrorLoadingProcessedImage: () => void;
|
||||
};
|
||||
|
||||
export const ControlAdapterImagePreview = memo(
|
||||
export const CAImagePreview = memo(
|
||||
({
|
||||
controlAdapter,
|
||||
onChangeImage,
|
||||
@ -43,7 +41,6 @@ export const ControlAdapterImagePreview = memo(
|
||||
const dispatch = useAppDispatch();
|
||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const shift = useShiftModifier();
|
||||
|
||||
@ -88,27 +85,21 @@ export const ControlAdapterImagePreview = memo(
|
||||
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) {
|
||||
const { width, height } = controlImage;
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
} else {
|
||||
const { width, height } = calculateNewSize(
|
||||
controlImage.width / controlImage.height,
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
}
|
||||
if (shift) {
|
||||
const { width, height } = controlImage;
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
} else {
|
||||
const { width, height } = calculateNewSize(
|
||||
controlImage.width / controlImage.height,
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
dispatch(widthChanged({ width, ...options }));
|
||||
dispatch(heightChanged({ height, ...options }));
|
||||
}
|
||||
}, [controlImage, activeTabName, dispatch, optimalDimension, shift]);
|
||||
}, [controlImage, dispatch, optimalDimension, shift]);
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
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;
|
||||
};
|
||||
|
||||
export const ControlAdapterModelCombobox = memo(({ modelKey, onChange: onChangeModel }: Props) => {
|
||||
export const CAModelCombobox = memo(({ modelKey, onChange: onChangeModel }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const currentBaseModel = useAppSelector((s) => s.generation.model?.base);
|
||||
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 { stopPropagation } from 'common/util/stopPropagation';
|
||||
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 { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiDropHalfFill } from 'react-icons/pi';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
const marks = [0, 25, 50, 75, 100];
|
||||
const formatPct = (v: number | string) => `${v} %`;
|
||||
|
||||
const CALayerOpacity = ({ layerId }: Props) => {
|
||||
export const CAOpacityAndFilter = memo(({ id }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const { opacity, isFilterEnabled } = useCALayerOpacity(layerId);
|
||||
const { opacity, isFilterEnabled } = useCALayerOpacity(id);
|
||||
const onChangeOpacity = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(layerOpacityChanged({ layerId, opacity: v / 100 }));
|
||||
dispatch(caOpacityChanged({ id, opacity: v / 100 }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
[dispatch, id]
|
||||
);
|
||||
const onChangeFilter = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(caLayerIsFilterEnabledChanged({ layerId, isFilterEnabled: e.target.checked }));
|
||||
dispatch(caFilterChanged({ id, filter: e.target.checked ? 'LightnessToAlphaFilter' : 'none' }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
[dispatch, id]
|
||||
);
|
||||
return (
|
||||
<Popover isLazy>
|
||||
@ -93,6 +93,6 @@ const CALayerOpacity = ({ layerId }: Props) => {
|
||||
</PopoverContent>
|
||||
</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 { 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 = {
|
||||
config: ProcessorConfig | null;
|
||||
onChange: (config: ProcessorConfig | null) => void;
|
||||
};
|
||||
|
||||
export const ControlAdapterProcessorConfig = memo(({ config, onChange }: Props) => {
|
||||
export const CAProcessorConfig = memo(({ config, onChange }: Props) => {
|
||||
if (!config) {
|
||||
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 { useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import type { ProcessorConfig } from 'features/controlLayers/util/controlAdapters';
|
||||
import { CA_PROCESSOR_DATA, isProcessorTypeV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import type {ProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { CA_PROCESSOR_DATA, isProcessorTypeV2 } from 'features/controlLayers/store/types';
|
||||
import { configSelector } from 'features/system/store/configSelectors';
|
||||
import { includes, map } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
@ -22,7 +22,7 @@ const selectDisabledProcessors = createMemoizedSelector(
|
||||
(config) => config.sd.disabledControlNetProcessors
|
||||
);
|
||||
|
||||
export const ControlAdapterProcessorTypeSelect = memo(({ config, onChange }: Props) => {
|
||||
export const CAProcessorTypeSelect = memo(({ config, onChange }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const disabledProcessors = useAppSelector(selectDisabledProcessors);
|
||||
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 { ControlAdapterBeginEndStepPct } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapterBeginEndStepPct';
|
||||
import { BeginEndStepPct } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapterBeginEndStepPct';
|
||||
import { ControlAdapterWeight } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapterWeight';
|
||||
import { IPAdapterImagePreview } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterImagePreview';
|
||||
import { IPAdapterMethod } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapterMethod';
|
||||
@ -49,7 +49,7 @@ export const IPAdapter = memo(
|
||||
<Flex flexDir="column" gap={3} w="full">
|
||||
<IPAdapterMethod method={ipAdapter.method} onChange={onChangeIPMethod} />
|
||||
<ControlAdapterWeight weight={ipAdapter.weight} onChange={onChangeWeight} />
|
||||
<ControlAdapterBeginEndStepPct
|
||||
<BeginEndStepPct
|
||||
beginEndStepPct={ipAdapter.beginEndStepPct}
|
||||
onChange={onChangeBeginEndStepPct}
|
||||
/>
|
||||
|
@ -8,7 +8,7 @@ import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton
|
||||
import { CALayer } from 'features/controlLayers/components/CALayer/CALayer';
|
||||
import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton';
|
||||
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 { RGLayer } from 'features/controlLayers/components/RGLayer/RGLayer';
|
||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
@ -58,10 +58,10 @@ const LayerWrapper = memo(({ id, type }: LayerWrapperProps) => {
|
||||
return <RGLayer key={id} layerId={id} />;
|
||||
}
|
||||
if (type === 'control_adapter_layer') {
|
||||
return <CALayer key={id} layerId={id} />;
|
||||
return <CALayer key={id} id={id} />;
|
||||
}
|
||||
if (type === 'ip_adapter_layer') {
|
||||
return <IPALayer key={id} layerId={id} />;
|
||||
return <IPAEntity key={id} layerId={id} />;
|
||||
}
|
||||
if (type === 'initial_image_layer') {
|
||||
return <IILayer key={id} layerId={id} />;
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
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 { ToolChooser } from 'features/controlLayers/components/ToolChooser';
|
||||
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
|
||||
@ -28,7 +28,7 @@ export const ControlLayersToolbar = memo(() => {
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex flex={1} gap={2} justifyContent="center" alignItems="center">
|
||||
{withBrushSize && <BrushSize />}
|
||||
{withBrushSize && <BrushWidth />}
|
||||
{withBrushColor && <BrushColorPicker />}
|
||||
</Flex>
|
||||
<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 { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIColorPicker from 'common/components/IAIColorPicker';
|
||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||
import { brushColorChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { fillChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { RgbaColor } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const BrushColorPicker = memo(() => {
|
||||
export const FillColorPicker = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const brushColor = useAppSelector((s) => s.canvasV2.brushColor);
|
||||
const fill = useAppSelector((s) => s.canvasV2.tool.fill);
|
||||
const dispatch = useAppDispatch();
|
||||
const onChange = useCallback(
|
||||
(color: RgbaColor) => {
|
||||
dispatch(brushColorChanged(color));
|
||||
dispatch(fillChanged(color));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
@ -25,7 +25,7 @@ export const BrushColorPicker = memo(() => {
|
||||
aria-label={t('controlLayers.brushColor')}
|
||||
borderRadius="full"
|
||||
borderWidth={1}
|
||||
bg={rgbaColorToString(brushColor)}
|
||||
bg={rgbaColorToString(fill)}
|
||||
w={8}
|
||||
h={8}
|
||||
cursor="pointer"
|
||||
@ -34,11 +34,11 @@ export const BrushColorPicker = memo(() => {
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<PopoverBody minH={64}>
|
||||
<IAIColorPicker color={brushColor} onChange={onChange} withNumberInput />
|
||||
<IAIColorPicker color={fill} onChange={onChange} withNumberInput />
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</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 { InitialImagePreview } from 'features/controlLayers/components/IILayer/InitialImagePreview';
|
||||
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 { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { LayerIsEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { EntityTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { EntityEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||
import {
|
||||
iiLayerDenoisingStrengthChanged,
|
||||
@ -67,11 +67,11 @@ export const IILayer = memo(({ layerId }: Props) => {
|
||||
return (
|
||||
<LayerWrapper onClick={onClick} borderColor={layer.isSelected ? 'base.400' : 'base.800'}>
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||
<LayerIsEnabledToggle layerId={layerId} />
|
||||
<LayerTitle type="initial_image_layer" />
|
||||
<EntityEnabledToggle layerId={layerId} />
|
||||
<EntityTitle type="initial_image_layer" />
|
||||
<Spacer />
|
||||
<LayerOpacity layerId={layerId} />
|
||||
<LayerMenu layerId={layerId} />
|
||||
<EntityMenu layerId={layerId} />
|
||||
<LayerDeleteButton layerId={layerId} />
|
||||
</Flex>
|
||||
{isOpen && (
|
||||
|
@ -2,41 +2,39 @@ import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { IPALayerIPAdapterWrapper } from 'features/controlLayers/components/IPALayer/IPALayerIPAdapterWrapper';
|
||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { LayerIsEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { EntityTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { EntityEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||
import { layerSelected, selectLayerOrThrow } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isIPAdapterLayer } from 'features/controlLayers/store/types';
|
||||
import { entitySelected } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
type Props = {
|
||||
layerId: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const IPALayer = memo(({ layerId }: Props) => {
|
||||
export const IPAEntity = memo(({ id }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const isSelected = useAppSelector(
|
||||
(s) => selectLayerOrThrow(s.canvasV2, layerId, isIPAdapterLayer).isSelected
|
||||
);
|
||||
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === id);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(layerSelected(layerId));
|
||||
}, [dispatch, layerId]);
|
||||
dispatch(entitySelected({ id, type: 'ip_adapter' }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
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="ip_adapter_layer" />
|
||||
<EntityEnabledToggle id={id} />
|
||||
<EntityTitle type="ip_adapter" />
|
||||
<Spacer />
|
||||
<LayerDeleteButton layerId={layerId} />
|
||||
<LayerDeleteButton id={id} />
|
||||
</Flex>
|
||||
{isOpen && (
|
||||
<Flex flexDir="column" gap={3} px={3} pb={3}>
|
||||
<IPALayerIPAdapterWrapper layerId={layerId} />
|
||||
<IPALayerIPAdapterWrapper id={id} />
|
||||
</Flex>
|
||||
)}
|
||||
</LayerWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
IPALayer.displayName = 'IPALayer';
|
||||
IPAEntity.displayName = 'IPAEntity';
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isIPAdapterLayer } from 'features/controlLayers/store/types';
|
||||
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 type { ImageDTO, IPAdapterModelConfig, IPALayerImagePostUploadAction } from 'services/api/types';
|
||||
|
||||
@ -72,7 +72,7 @@ export const IPALayerIPAdapterWrapper = memo(({ layerId }: Props) => {
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const droppableData = useMemo<IPALayerImageDropData>(
|
||||
const droppableData = useMemo<IPAImageDropData>(
|
||||
() => ({
|
||||
actionType: 'SET_IPA_LAYER_IMAGE',
|
||||
context: {
|
||||
|
@ -1,19 +1,13 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { layerDeleted } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
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 dispatch = useAppDispatch();
|
||||
const deleteLayer = useCallback(() => {
|
||||
dispatch(layerDeleted(layerId));
|
||||
}, [dispatch, layerId]);
|
||||
return (
|
||||
<IconButton
|
||||
size="sm"
|
||||
@ -21,10 +15,10 @@ export const LayerDeleteButton = memo(({ layerId }: Props) => {
|
||||
aria-label={t('common.delete')}
|
||||
tooltip={t('common.delete')}
|
||||
icon={<PiTrashSimpleBold />}
|
||||
onClick={deleteLayer}
|
||||
onClick={onDelete}
|
||||
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 { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { useLayerIsEnabled } from 'features/controlLayers/hooks/layerStateHooks';
|
||||
import { layerIsEnabledToggled } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCheckBold } from 'react-icons/pi';
|
||||
|
||||
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 dispatch = useAppDispatch();
|
||||
const isEnabled = useLayerIsEnabled(layerId);
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(layerIsEnabledToggled(layerId));
|
||||
}, [dispatch, layerId]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
@ -26,11 +19,11 @@ export const LayerIsEnabledToggle = memo(({ layerId }: Props) => {
|
||||
tooltip={t(isEnabled ? 'common.enabled' : 'common.disabled')}
|
||||
variant="outline"
|
||||
icon={isEnabled ? <PiCheckBold /> : undefined}
|
||||
onClick={onClick}
|
||||
onClick={onToggle}
|
||||
colorScheme="base"
|
||||
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 };
|
||||
|
||||
export const LayerMenu = memo(({ layerId }: Props) => {
|
||||
export const EntityMenu = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
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 { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAddIPAdapterToIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import { useAddIPAdapterToRGLayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import {
|
||||
regionalGuidanceNegativePromptChanged,
|
||||
regionalGuidancePositivePromptChanged,
|
||||
@ -18,7 +18,7 @@ type Props = { layerId: string };
|
||||
export const LayerMenuRGActions = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToIPALayer(layerId);
|
||||
const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToRGLayer(layerId);
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
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 { AddPromptButtons } from 'features/controlLayers/components/AddPromptButtons';
|
||||
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 { EntityMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
||||
import { EntityTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { EntityEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||
import { layerSelected, selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isRegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
||||
@ -52,8 +52,8 @@ export const RGLayer = memo(({ layerId }: Props) => {
|
||||
return (
|
||||
<LayerWrapper onClick={onClick} borderColor={isSelected ? color : 'base.800'}>
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||
<LayerIsEnabledToggle layerId={layerId} />
|
||||
<LayerTitle type="regional_guidance_layer" />
|
||||
<EntityEnabledToggle layerId={layerId} />
|
||||
<EntityTitle type="regional_guidance_layer" />
|
||||
<Spacer />
|
||||
{autoNegative === 'invert' && (
|
||||
<Badge color="base.300" bg="transparent" borderWidth={1} userSelect="none">
|
||||
@ -62,12 +62,12 @@ export const RGLayer = memo(({ layerId }: Props) => {
|
||||
)}
|
||||
<RGLayerColorPicker layerId={layerId} />
|
||||
<RGLayerSettingsPopover layerId={layerId} />
|
||||
<LayerMenu layerId={layerId} />
|
||||
<EntityMenu layerId={layerId} />
|
||||
<LayerDeleteButton layerId={layerId} />
|
||||
</Flex>
|
||||
{isOpen && (
|
||||
<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} />}
|
||||
{hasNegativePrompt && <RGLayerNegativePrompt layerId={layerId} />}
|
||||
{hasIPAdapters && <RGLayerIPAdapterList layerId={layerId} />}
|
||||
|
@ -12,10 +12,10 @@ import {
|
||||
selectRGLayerIPAdapterOrThrow,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
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 { 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 = {
|
||||
layerId: string;
|
||||
@ -78,7 +78,7 @@ export const RGLayerIPAdapterWrapper = memo(({ layerId, ipAdapterId, ipAdapterNu
|
||||
[dispatch, ipAdapterId, layerId]
|
||||
);
|
||||
|
||||
const droppableData = useMemo<RGLayerIPAdapterImageDropData>(
|
||||
const droppableData = useMemo<RGIPAdapterImageDropData>(
|
||||
() => ({
|
||||
actionType: 'SET_RG_LAYER_IP_ADAPTER_IMAGE',
|
||||
context: {
|
||||
@ -90,7 +90,7 @@ export const RGLayerIPAdapterWrapper = memo(({ layerId, ipAdapterId, ipAdapterNu
|
||||
[ipAdapterId, layerId]
|
||||
);
|
||||
|
||||
const postUploadAction = useMemo<RGLayerIPAdapterImagePostUploadAction>(
|
||||
const postUploadAction = useMemo<RGIPAdapterImagePostUploadAction>(
|
||||
() => ({
|
||||
type: 'SET_RG_LAYER_IP_ADAPTER_IMAGE',
|
||||
layerId,
|
||||
|
@ -2,14 +2,14 @@ import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDroppable from 'common/components/IAIDroppable';
|
||||
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 { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { LayerIsEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { EntityTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { EntityEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||
import { layerSelected, selectLayerOrThrow } from 'features/controlLayers/store/controlLayersSlice';
|
||||
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';
|
||||
|
||||
type Props = {
|
||||
@ -27,7 +27,7 @@ export const RasterLayer = memo(({ layerId }: Props) => {
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
|
||||
const droppableData = useMemo(() => {
|
||||
const _droppableData: RasterLayerImageDropData = {
|
||||
const _droppableData: LayerImageDropData = {
|
||||
id: layerId,
|
||||
actionType: 'ADD_RASTER_LAYER_IMAGE',
|
||||
context: { layerId },
|
||||
@ -38,11 +38,11 @@ export const RasterLayer = memo(({ layerId }: Props) => {
|
||||
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="raster_layer" />
|
||||
<EntityEnabledToggle layerId={layerId} />
|
||||
<EntityTitle type="raster_layer" />
|
||||
<Spacer />
|
||||
<LayerOpacity layerId={layerId} />
|
||||
<LayerMenu layerId={layerId} />
|
||||
<EntityMenu layerId={layerId} />
|
||||
<LayerDeleteButton layerId={layerId} />
|
||||
</Flex>
|
||||
{isOpen && (
|
||||
|
@ -1,18 +1,14 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
controlAdapterAdded,
|
||||
iiLayerAdded,
|
||||
ipAdapterAdded,
|
||||
regionalGuidanceIPAdapterAdded,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isInitialImageLayer } from 'features/controlLayers/store/types';
|
||||
import { caAdded } from 'features/controlLayers/store/controlAdaptersSlice';
|
||||
import { ipaAdded } from 'features/controlLayers/store/ipAdaptersSlice';
|
||||
import { rgIPAdapterAdded } from 'features/controlLayers/store/regionalGuidanceSlice';
|
||||
import {
|
||||
buildControlNet,
|
||||
buildIPAdapter,
|
||||
buildT2IAdapter,
|
||||
CA_PROCESSOR_DATA,
|
||||
isProcessorTypeV2,
|
||||
} from 'features/controlLayers/util/controlAdapters';
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useControlNetAndT2IAdapterModels, useIPAdapterModels } from 'services/api/hooks/modelsByType';
|
||||
@ -46,7 +42,7 @@ export const useAddCALayer = () => {
|
||||
processorConfig,
|
||||
});
|
||||
|
||||
dispatch(controlAdapterAdded(controlAdapter));
|
||||
dispatch(caAdded(controlAdapter));
|
||||
}, [dispatch, model, baseModel]);
|
||||
|
||||
return [addCALayer, isDisabled] as const;
|
||||
@ -70,13 +66,13 @@ export const useAddIPALayer = () => {
|
||||
const ipAdapter = buildIPAdapter(id, {
|
||||
model: zModelIdentifierField.parse(model),
|
||||
});
|
||||
dispatch(ipAdapterAdded(ipAdapter));
|
||||
dispatch(ipaAdded(ipAdapter));
|
||||
}, [dispatch, model]);
|
||||
|
||||
return [addIPALayer, isDisabled] as const;
|
||||
};
|
||||
|
||||
export const useAddIPAdapterToIPALayer = (layerId: string) => {
|
||||
export const useAddIPAdapterToRGLayer = (id: string) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const baseModel = useAppSelector((s) => s.generation.model?.base);
|
||||
const [modelConfigs] = useIPAdapterModels();
|
||||
@ -90,22 +86,11 @@ export const useAddIPAdapterToIPALayer = (layerId: string) => {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
const id = uuidv4();
|
||||
const ipAdapter = buildIPAdapter(id, {
|
||||
const ipAdapter = buildIPAdapter(uuidv4(), {
|
||||
model: zModelIdentifierField.parse(model),
|
||||
});
|
||||
dispatch(regionalGuidanceIPAdapterAdded({ layerId, ipAdapter }));
|
||||
}, [dispatch, model, layerId]);
|
||||
dispatch(rgIPAdapterAdded({ id, ipAdapter: { ...ipAdapter, id: uuidv4(), type: 'ip_adapter', isEnabled: true } }));
|
||||
}, [model, dispatch, id]);
|
||||
|
||||
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,
|
||||
scaleY: 1,
|
||||
visible: ca.isEnabled,
|
||||
filters: ca.filter === LightnessToAlphaFilter.name ? [LightnessToAlphaFilter] : [],
|
||||
filters: ca.filter === 'LightnessToAlphaFilter' ? [LightnessToAlphaFilter] : [],
|
||||
});
|
||||
needsCache = true;
|
||||
}
|
||||
|
@ -2,15 +2,14 @@ import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
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 type { IRect } from 'konva/lib/types';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import type { ControlNetModelConfig, ImageDTO, T2IAdapterModelConfig } from 'services/api/types';
|
||||
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 = {
|
||||
_version: 1;
|
||||
@ -22,7 +21,7 @@ const initialState: ControlAdaptersV2State = {
|
||||
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({
|
||||
name: 'controlAdaptersV2',
|
||||
@ -40,7 +39,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
bboxNeedsUpdate: false,
|
||||
isEnabled: true,
|
||||
opacity: 1,
|
||||
filter: 'lightness_to_alpha',
|
||||
filter: 'LightnessToAlphaFilter',
|
||||
processorPendingBatchId: null,
|
||||
...config,
|
||||
});
|
||||
@ -52,17 +51,17 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
caRecalled: (state, action: PayloadAction<{ data: ControlAdapterData }>) => {
|
||||
state.controlAdapters.push(action.payload.data);
|
||||
},
|
||||
caIsEnabledChanged: (state, action: PayloadAction<{ id: string; isEnabled: boolean }>) => {
|
||||
const { id, isEnabled } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
caIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.isEnabled = isEnabled;
|
||||
ca.isEnabled = !ca.isEnabled;
|
||||
},
|
||||
caTranslated: (state, action: PayloadAction<{ id: string; x: number; y: number }>) => {
|
||||
const { id, x, y } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -71,7 +70,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
},
|
||||
caBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => {
|
||||
const { id, bbox } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -84,7 +83,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
},
|
||||
caOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => {
|
||||
const { id, opacity } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -92,7 +91,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
},
|
||||
caMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -100,7 +99,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
},
|
||||
caMovedToFront: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -108,7 +107,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
},
|
||||
caMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -116,7 +115,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
},
|
||||
caMovedToBack: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -124,7 +123,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
},
|
||||
caImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => {
|
||||
const { id, imageDTO } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -145,7 +144,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
},
|
||||
caProcessedImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => {
|
||||
const { id, imageDTO } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -162,7 +161,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
}>
|
||||
) => {
|
||||
const { id, modelConfig } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -189,7 +188,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
},
|
||||
caControlModeChanged: (state, action: PayloadAction<{ id: string; controlMode: ControlModeV2 }>) => {
|
||||
const { id, controlMode } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -200,7 +199,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
action: PayloadAction<{ id: string; processorConfig: ProcessorConfig | null }>
|
||||
) => {
|
||||
const { id, processorConfig } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -211,7 +210,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
},
|
||||
caFilterChanged: (state, action: PayloadAction<{ id: string; filter: Filter }>) => {
|
||||
const { id, filter } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -219,7 +218,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
},
|
||||
caProcessorPendingBatchIdChanged: (state, action: PayloadAction<{ id: string; batchId: string | null }>) => {
|
||||
const { id, batchId } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -227,7 +226,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
},
|
||||
caWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => {
|
||||
const { id, weight } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -235,7 +234,7 @@ export const controlAdaptersV2Slice = createSlice({
|
||||
},
|
||||
caBeginEndStepPctChanged: (state, action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }>) => {
|
||||
const { id, beginEndStepPct } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
@ -248,7 +247,7 @@ export const {
|
||||
caAdded,
|
||||
caBboxChanged,
|
||||
caDeleted,
|
||||
caIsEnabledChanged,
|
||||
caIsEnabledToggled,
|
||||
caMovedBackwardOne,
|
||||
caMovedForwardOne,
|
||||
caMovedToBack,
|
||||
|
@ -11,7 +11,7 @@ import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/
|
||||
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||
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';
|
||||
|
||||
const initialState: CanvasV2State = {
|
||||
@ -110,6 +110,9 @@ export const canvasV2Slice = createSlice({
|
||||
toolBufferChanged: (state, action: PayloadAction<Tool | null>) => {
|
||||
state.tool.selectedBuffer = action.payload;
|
||||
},
|
||||
entitySelected: (state, action: PayloadAction<CanvasEntityIdentifier>) => {
|
||||
state.selectedEntityIdentifier = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
builder.addCase(modelChanged, (state, action) => {
|
||||
@ -145,6 +148,7 @@ export const {
|
||||
invertScrollChanged,
|
||||
toolChanged,
|
||||
toolBufferChanged,
|
||||
entitySelected,
|
||||
} = canvasV2Slice.actions;
|
||||
|
||||
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 { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
||||
import { getBrushLineId, getEraserLineId, getRectShapeId } from 'features/controlLayers/konva/naming';
|
||||
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import { imageDTOToImageWithDims } from 'features/controlLayers/util/controlAdapters';
|
||||
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
|
||||
import { imageDTOToImageWithDims } from 'features/controlLayers/store/types';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas';
|
||||
import type { IRect } from 'konva/lib/types';
|
||||
|
@ -24,7 +24,7 @@ import type {
|
||||
ProcessorConfig,
|
||||
ProcessorTypeV2,
|
||||
ZoeDepthProcessorConfig,
|
||||
} from './controlAdapters';
|
||||
} from './types';
|
||||
|
||||
describe('Control Adapter Types', () => {
|
||||
test('ProcessorType', () => {
|
@ -1,13 +1,4 @@
|
||||
import { LightnessToAlphaFilter } from 'features/controlLayers/konva/filters';
|
||||
import {
|
||||
zBeginEndStepPct,
|
||||
zCLIPVisionModelV2,
|
||||
zControlModeV2,
|
||||
zId,
|
||||
zImageWithDims,
|
||||
zIPMethodV2,
|
||||
zProcessorConfig,
|
||||
} from 'features/controlLayers/util/controlAdapters';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||
import type {
|
||||
@ -24,11 +15,442 @@ import {
|
||||
zParameterPositivePrompt,
|
||||
} from 'features/parameters/types/parameterSchemas';
|
||||
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';
|
||||
|
||||
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']);
|
||||
export type Tool = z.infer<typeof zTool>;
|
||||
|
||||
const zDrawingTool = zTool.extract(['brush', 'eraser']);
|
||||
|
||||
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>;
|
||||
|
||||
const zFilter = z.enum(['none', LightnessToAlphaFilter.name]);
|
||||
const zFilter = z.enum(['none', 'LightnessToAlphaFilter']);
|
||||
export type Filter = z.infer<typeof zFilter>;
|
||||
|
||||
const zControlAdapterData = z.object({
|
||||
@ -272,6 +694,64 @@ export type ControlAdapterConfig = Pick<
|
||||
'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 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';
|
||||
};
|
||||
|
||||
type ControlAdapterDropData = BaseDropData & {
|
||||
actionType: 'SET_CONTROL_ADAPTER_IMAGE';
|
||||
export type CAImageDropData = BaseDropData & {
|
||||
actionType: 'SET_CA_IMAGE';
|
||||
context: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type CALayerImageDropData = BaseDropData & {
|
||||
actionType: 'SET_CA_LAYER_IMAGE';
|
||||
export type IPAImageDropData = BaseDropData & {
|
||||
actionType: 'SET_IPA_IMAGE';
|
||||
context: {
|
||||
layerId: string;
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type IPALayerImageDropData = BaseDropData & {
|
||||
actionType: 'SET_IPA_LAYER_IMAGE';
|
||||
export type RGIPAdapterImageDropData = BaseDropData & {
|
||||
actionType: 'SET_RG_IP_ADAPTER_IMAGE';
|
||||
context: {
|
||||
layerId: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type RGLayerIPAdapterImageDropData = BaseDropData & {
|
||||
actionType: 'SET_RG_LAYER_IP_ADAPTER_IMAGE';
|
||||
context: {
|
||||
layerId: string;
|
||||
id: string;
|
||||
ipAdapterId: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type IILayerImageDropData = BaseDropData & {
|
||||
actionType: 'SET_II_LAYER_IMAGE';
|
||||
export type LayerImageDropData = BaseDropData & {
|
||||
actionType: 'ADD_LAYER_IMAGE';
|
||||
context: {
|
||||
layerId: string;
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
|
||||
@ -100,16 +93,14 @@ export type SelectForCompareDropData = BaseDropData & {
|
||||
|
||||
export type TypesafeDroppableData =
|
||||
| CurrentImageDropData
|
||||
| ControlAdapterDropData
|
||||
| CanvasInitialImageDropData
|
||||
| NodesImageDropData
|
||||
| AddToBoardDropData
|
||||
| RemoveFromBoardDropData
|
||||
| CALayerImageDropData
|
||||
| IPALayerImageDropData
|
||||
| RGLayerIPAdapterImageDropData
|
||||
| IILayerImageDropData
|
||||
| CAImageDropData
|
||||
| IPAImageDropData
|
||||
| RGIPAdapterImageDropData
|
||||
| SelectForCompareDropData
|
||||
| RasterLayerImageDropData
|
||||
| UpscaleInitialImageDropData;
|
||||
|
||||
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 { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
|
||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { HrfSettings } from 'features/hrf/components/HrfSettings';
|
||||
import { selectHrfSlice } from 'features/hrf/store/hrfSlice';
|
||||
@ -20,34 +19,22 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ImageSizeCanvas } from './ImageSizeCanvas';
|
||||
import { ImageSizeLinear } from './ImageSizeLinear';
|
||||
|
||||
const selector = createMemoizedSelector(
|
||||
[selectGenerationSlice, selectCanvasSlice, selectHrfSlice, selectCanvasV2Slice, activeTabNameSelector],
|
||||
(generation, canvas, hrf, controlLayers, activeTabName) => {
|
||||
[selectGenerationSlice, selectHrfSlice, selectCanvasV2Slice, activeTabNameSelector],
|
||||
(generation, hrf, canvasV2, activeTabName) => {
|
||||
const { shouldRandomizeSeed, model } = generation;
|
||||
const { hrfEnabled } = hrf;
|
||||
const badges: string[] = [];
|
||||
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;
|
||||
badges.push(`${width}×${height}`);
|
||||
badges.push(aspectRatio.id);
|
||||
if (aspectRatio.isLocked) {
|
||||
badges.push('locked');
|
||||
}
|
||||
const { aspectRatio, width, height } = canvasV2.size;
|
||||
badges.push(`${width}×${height}`);
|
||||
badges.push(aspectRatio.id);
|
||||
|
||||
if (aspectRatio.isLocked) {
|
||||
badges.push('locked');
|
||||
}
|
||||
|
||||
if (!shouldRandomizeSeed) {
|
||||
@ -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 flexDir="column" gap={4}>
|
||||
{activeTabName === 'canvas' ? <ImageSizeCanvas /> : <ImageSizeLinear />}
|
||||
{activeTabName === 'canvas' && <ParamImageToImageStrength />}
|
||||
<ImageSizeLinear />
|
||||
<ParamImageToImageStrength />
|
||||
</Flex>
|
||||
<Expander label={t('accordions.advanced.options')} isOpen={isOpenExpander} onToggle={onToggleExpander}>
|
||||
<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 { resetCanvas } from 'features/canvas/store/canvasSlice';
|
||||
import { controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -40,8 +38,8 @@ export const useClearIntermediates = (shouldShowClearIntermediates: boolean): Us
|
||||
_clearIntermediates()
|
||||
.unwrap()
|
||||
.then((clearedCount) => {
|
||||
dispatch(controlAdaptersReset());
|
||||
dispatch(resetCanvas());
|
||||
// dispatch(controlAdaptersReset());
|
||||
// dispatch(resetCanvas());
|
||||
toast({
|
||||
id: 'INTERMEDIATES_CLEARED',
|
||||
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 QueueTab from 'features/ui/components/tabs/QueueTab';
|
||||
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 { usePanel } from 'features/ui/hooks/usePanel';
|
||||
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
|
||||
@ -30,11 +29,10 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MdZoomOutMap } from 'react-icons/md';
|
||||
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 { Panel, PanelGroup } from 'react-resizable-panels';
|
||||
|
||||
import ParametersPanelCanvas from './ParametersPanels/ParametersPanelCanvas';
|
||||
import ParametersPanelUpscale from './ParametersPanels/ParametersPanelUpscale';
|
||||
import ResizeHandle from './tabs/ResizeHandle';
|
||||
import UpscalingTab from './tabs/UpscalingTab';
|
||||
@ -55,13 +53,6 @@ const TAB_DATA: Record<InvokeTabName, TabData> = {
|
||||
content: <TextToImageTab />,
|
||||
parametersPanel: <ParametersPanelTextToImage />,
|
||||
},
|
||||
canvas: {
|
||||
id: 'canvas',
|
||||
translationKey: 'ui.tabs.canvas',
|
||||
icon: <RiBrushLine />,
|
||||
content: <UnifiedCanvasTab />,
|
||||
parametersPanel: <ParametersPanelCanvas />,
|
||||
},
|
||||
upscaling: {
|
||||
id: '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 { 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';
|
||||
@ -44,7 +43,7 @@ const ParametersPanelTextToImage = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
const controlLayersCount = useAppSelector((s) => s.canvasV2.layers.length);
|
||||
const controlLayersCount = useAppSelector((s) => s.layers.layers.length);
|
||||
const controlLayersTitle = useMemo(() => {
|
||||
if (controlLayersCount === 0) {
|
||||
return t('controlLayers.controlLayers');
|
||||
@ -108,8 +107,7 @@ const ParametersPanelTextToImage = () => {
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<ImageSettingsAccordion />
|
||||
<GenerationSettingsAccordion />
|
||||
{activeTabName !== 'generation' && <ControlSettingsAccordion />}
|
||||
{activeTabName === 'canvas' && <CompositingSettingsAccordion />}
|
||||
<CompositingSettingsAccordion />
|
||||
{isSDXL && <RefinerSettingsAccordion />}
|
||||
<AdvancedSettingsAccordion />
|
||||
</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];
|
||||
|
@ -167,44 +167,28 @@ export type OutputFields<T extends AnyInvocation> = Extract<
|
||||
// Node Outputs
|
||||
export type ImageOutput = S['ImageOutput'];
|
||||
|
||||
// Post-image upload actions, controls workflows when images are uploaded
|
||||
|
||||
type ControlAdapterAction = {
|
||||
type: 'SET_CONTROL_ADAPTER_IMAGE';
|
||||
export type CAImagePostUploadAction = {
|
||||
type: 'SET_CA_IMAGE';
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type CALayerImagePostUploadAction = {
|
||||
type: 'SET_CA_LAYER_IMAGE';
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
export type IPALayerImagePostUploadAction = {
|
||||
type: 'SET_IPA_LAYER_IMAGE';
|
||||
layerId: string;
|
||||
type: 'SET_IPA_IMAGE';
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type RGLayerIPAdapterImagePostUploadAction = {
|
||||
type: 'SET_RG_LAYER_IP_ADAPTER_IMAGE';
|
||||
layerId: string;
|
||||
export type RGIPAdapterImagePostUploadAction = {
|
||||
type: 'SET_RG_IP_ADAPTER_IMAGE';
|
||||
id: string;
|
||||
ipAdapterId: string;
|
||||
};
|
||||
|
||||
export type IILayerImagePostUploadAction = {
|
||||
type: 'SET_II_LAYER_IMAGE';
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
type NodesAction = {
|
||||
type: 'SET_NODES_IMAGE';
|
||||
nodeId: string;
|
||||
fieldName: string;
|
||||
};
|
||||
|
||||
type CanvasInitialImageAction = {
|
||||
type: 'SET_CANVAS_INITIAL_IMAGE';
|
||||
};
|
||||
|
||||
type UpscaleInitialImageAction = {
|
||||
type: 'SET_UPSCALE_INITIAL_IMAGE';
|
||||
};
|
||||
@ -219,13 +203,10 @@ type AddToBatchAction = {
|
||||
};
|
||||
|
||||
export type PostUploadAction =
|
||||
| ControlAdapterAction
|
||||
| NodesAction
|
||||
| CanvasInitialImageAction
|
||||
| ToastAction
|
||||
| AddToBatchAction
|
||||
| CALayerImagePostUploadAction
|
||||
| CAImagePostUploadAction
|
||||
| IPALayerImagePostUploadAction
|
||||
| RGLayerIPAdapterImagePostUploadAction
|
||||
| IILayerImagePostUploadAction
|
||||
| RGIPAdapterImagePostUploadAction
|
||||
| UpscaleInitialImageAction;
|
||||
|
Loading…
Reference in New Issue
Block a user