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
93b185dc3b
commit
ca9090d070
@ -4,12 +4,12 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
|
||||
import type { AppDispatch } from 'app/store/store';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import {
|
||||
caLayerImageChanged,
|
||||
caLayerModelChanged,
|
||||
caLayerProcessedImageChanged,
|
||||
caLayerProcessorConfigChanged,
|
||||
caLayerProcessorPendingBatchIdChanged,
|
||||
caLayerRecalled,
|
||||
controlAdapterImageChanged,
|
||||
controlAdapterModelChanged,
|
||||
controlAdapterProcessedImageChanged,
|
||||
controlAdapterProcessorConfigChanged,
|
||||
controlAdapterProcessorPendingBatchIdChanged,
|
||||
controlAdapterRecalled,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isControlAdapterLayer } from 'features/controlLayers/store/types';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||
@ -23,11 +23,11 @@ import { socketInvocationComplete } from 'services/events/actions';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
const matcher = isAnyOf(
|
||||
caLayerImageChanged,
|
||||
caLayerProcessedImageChanged,
|
||||
caLayerProcessorConfigChanged,
|
||||
caLayerModelChanged,
|
||||
caLayerRecalled
|
||||
controlAdapterImageChanged,
|
||||
controlAdapterProcessedImageChanged,
|
||||
controlAdapterProcessorConfigChanged,
|
||||
controlAdapterModelChanged,
|
||||
controlAdapterRecalled
|
||||
);
|
||||
|
||||
const DEBOUNCE_MS = 300;
|
||||
@ -46,7 +46,7 @@ const cancelProcessorBatch = async (dispatch: AppDispatch, layerId: string, batc
|
||||
} finally {
|
||||
req.reset();
|
||||
// Always reset the pending batch ID - the cancel req could fail if the batch doesn't exist
|
||||
dispatch(caLayerProcessorPendingBatchIdChanged({ layerId, batchId: null }));
|
||||
dispatch(controlAdapterProcessorPendingBatchIdChanged({ layerId, batchId: null }));
|
||||
}
|
||||
};
|
||||
|
||||
@ -54,7 +54,7 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
|
||||
startAppListening({
|
||||
matcher,
|
||||
effect: async (action, { dispatch, getState, getOriginalState, cancelActiveListeners, delay, take, signal }) => {
|
||||
const layerId = caLayerRecalled.match(action) ? action.payload.id : action.payload.layerId;
|
||||
const layerId = controlAdapterRecalled.match(action) ? action.payload.id : action.payload.layerId;
|
||||
const state = getState();
|
||||
const originalState = getOriginalState();
|
||||
|
||||
@ -91,7 +91,7 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
|
||||
// - If we have no image, we have nothing to process
|
||||
// - If we have no processor config, we have nothing to process
|
||||
// Clear the processed image and bail
|
||||
dispatch(caLayerProcessedImageChanged({ layerId, imageDTO: null }));
|
||||
dispatch(controlAdapterProcessedImageChanged({ layerId, imageDTO: null }));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -132,7 +132,7 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
|
||||
const enqueueResult = await req.unwrap();
|
||||
// TODO(psyche): Update the pydantic models, pretty sure we will _always_ have a batch_id here, but the model says it's optional
|
||||
assert(enqueueResult.batch.batch_id, 'Batch ID not returned from queue');
|
||||
dispatch(caLayerProcessorPendingBatchIdChanged({ layerId, batchId: enqueueResult.batch.batch_id }));
|
||||
dispatch(controlAdapterProcessorPendingBatchIdChanged({ layerId, batchId: enqueueResult.batch.batch_id }));
|
||||
log.debug({ enqueueResult: parseify(enqueueResult) }, t('queue.graphQueued'));
|
||||
|
||||
// Wait for the processor node to complete
|
||||
@ -155,8 +155,8 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
|
||||
|
||||
// Whew! We made it. Update the layer with the processed image
|
||||
log.debug({ layerId, imageDTO }, 'ControlNet image processed');
|
||||
dispatch(caLayerProcessedImageChanged({ layerId, imageDTO }));
|
||||
dispatch(caLayerProcessorPendingBatchIdChanged({ layerId, batchId: null }));
|
||||
dispatch(controlAdapterProcessedImageChanged({ layerId, imageDTO }));
|
||||
dispatch(controlAdapterProcessorPendingBatchIdChanged({ layerId, batchId: null }));
|
||||
} catch (error) {
|
||||
if (signal.aborted) {
|
||||
// The listener was canceled - we need to cancel the pending processor batch, if there is one (could have changed by now).
|
||||
@ -174,7 +174,7 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
|
||||
if (error instanceof Object) {
|
||||
if ('data' in error && 'status' in error) {
|
||||
if (error.status === 403) {
|
||||
dispatch(caLayerImageChanged({ layerId, imageDTO: null }));
|
||||
dispatch(controlAdapterImageChanged({ layerId, imageDTO: null }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -8,11 +8,11 @@ import {
|
||||
controlAdapterIsEnabledChanged,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import {
|
||||
caLayerImageChanged,
|
||||
controlAdapterImageChanged,
|
||||
iiLayerImageChanged,
|
||||
imageAdded,
|
||||
ipaLayerImageChanged,
|
||||
rgLayerIPAdapterImageChanged,
|
||||
layerImageAdded,
|
||||
ipAdapterImageChanged,
|
||||
regionalGuidanceIPAdapterImageChanged,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { isValidDrop } from 'features/dnd/util/isValidDrop';
|
||||
@ -99,7 +99,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
||||
) {
|
||||
const { layerId } = overData.context;
|
||||
dispatch(
|
||||
caLayerImageChanged({
|
||||
controlAdapterImageChanged({
|
||||
layerId,
|
||||
imageDTO: activeData.payload.imageDTO,
|
||||
})
|
||||
@ -117,7 +117,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
||||
) {
|
||||
const { layerId } = overData.context;
|
||||
dispatch(
|
||||
ipaLayerImageChanged({
|
||||
ipAdapterImageChanged({
|
||||
layerId,
|
||||
imageDTO: activeData.payload.imageDTO,
|
||||
})
|
||||
@ -135,7 +135,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
||||
) {
|
||||
const { layerId, ipAdapterId } = overData.context;
|
||||
dispatch(
|
||||
rgLayerIPAdapterImageChanged({
|
||||
regionalGuidanceIPAdapterImageChanged({
|
||||
layerId,
|
||||
ipAdapterId,
|
||||
imageDTO: activeData.payload.imageDTO,
|
||||
@ -172,7 +172,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
||||
) {
|
||||
const { layerId } = overData.context;
|
||||
dispatch(
|
||||
imageAdded({
|
||||
layerImageAdded({
|
||||
layerId,
|
||||
imageDTO: activeData.payload.imageDTO,
|
||||
})
|
||||
|
@ -6,10 +6,10 @@ import {
|
||||
controlAdapterIsEnabledChanged,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import {
|
||||
caLayerImageChanged,
|
||||
controlAdapterImageChanged,
|
||||
iiLayerImageChanged,
|
||||
ipaLayerImageChanged,
|
||||
rgLayerIPAdapterImageChanged,
|
||||
ipAdapterImageChanged,
|
||||
regionalGuidanceIPAdapterImageChanged,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
@ -122,7 +122,7 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
||||
|
||||
if (postUploadAction?.type === 'SET_CA_LAYER_IMAGE') {
|
||||
const { layerId } = postUploadAction;
|
||||
dispatch(caLayerImageChanged({ layerId, imageDTO }));
|
||||
dispatch(controlAdapterImageChanged({ layerId, imageDTO }));
|
||||
toast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
@ -131,7 +131,7 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
||||
|
||||
if (postUploadAction?.type === 'SET_IPA_LAYER_IMAGE') {
|
||||
const { layerId } = postUploadAction;
|
||||
dispatch(ipaLayerImageChanged({ layerId, imageDTO }));
|
||||
dispatch(ipAdapterImageChanged({ layerId, imageDTO }));
|
||||
toast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
@ -140,7 +140,7 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
||||
|
||||
if (postUploadAction?.type === 'SET_RG_LAYER_IP_ADAPTER_IMAGE') {
|
||||
const { layerId, ipAdapterId } = postUploadAction;
|
||||
dispatch(rgLayerIPAdapterImageChanged({ layerId, ipAdapterId, imageDTO }));
|
||||
dispatch(regionalGuidanceIPAdapterImageChanged({ layerId, ipAdapterId, imageDTO }));
|
||||
toast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
|
@ -7,14 +7,16 @@ import type { JSONObject } from 'common/types';
|
||||
import { canvasPersistConfig, canvasSlice } from 'features/canvas/store/canvasSlice';
|
||||
import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice';
|
||||
import {
|
||||
controlAdaptersPersistConfig,
|
||||
controlAdaptersSlice,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
controlAdaptersV2PersistConfig,
|
||||
controlAdaptersV2Slice,
|
||||
} from 'features/controlLayers/store/controlAdaptersSlice';
|
||||
import { canvasV2PersistConfig, canvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { ipAdaptersPersistConfig, ipAdaptersSlice } from 'features/controlLayers/store/ipAdaptersSlice';
|
||||
import { layersPersistConfig, layersSlice } from 'features/controlLayers/store/layersSlice';
|
||||
import {
|
||||
controlLayersPersistConfig,
|
||||
controlLayersSlice,
|
||||
controlLayersUndoableConfig,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
regionalGuidancePersistConfig,
|
||||
regionalGuidanceSlice,
|
||||
} from 'features/controlLayers/store/regionalGuidanceSlice';
|
||||
import { deleteImageModalSlice } from 'features/deleteImageModal/store/slice';
|
||||
import { dynamicPromptsPersistConfig, dynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||
import { galleryPersistConfig, gallerySlice } from 'features/gallery/store/gallerySlice';
|
||||
@ -49,6 +51,7 @@ import { stateSanitizer } from './middleware/devtools/stateSanitizer';
|
||||
import { listenerMiddleware } from './middleware/listenerMiddleware';
|
||||
|
||||
const allReducers = {
|
||||
[api.reducerPath]: api.reducer,
|
||||
[canvasSlice.name]: canvasSlice.reducer,
|
||||
[gallerySlice.name]: gallerySlice.reducer,
|
||||
[generationSlice.name]: generationSlice.reducer,
|
||||
@ -56,7 +59,6 @@ const allReducers = {
|
||||
[systemSlice.name]: systemSlice.reducer,
|
||||
[configSlice.name]: configSlice.reducer,
|
||||
[uiSlice.name]: uiSlice.reducer,
|
||||
[controlAdaptersSlice.name]: controlAdaptersSlice.reducer,
|
||||
[dynamicPromptsSlice.name]: dynamicPromptsSlice.reducer,
|
||||
[deleteImageModalSlice.name]: deleteImageModalSlice.reducer,
|
||||
[changeBoardModalSlice.name]: changeBoardModalSlice.reducer,
|
||||
@ -66,11 +68,14 @@ const allReducers = {
|
||||
[queueSlice.name]: queueSlice.reducer,
|
||||
[workflowSlice.name]: workflowSlice.reducer,
|
||||
[hrfSlice.name]: hrfSlice.reducer,
|
||||
[controlLayersSlice.name]: undoable(controlLayersSlice.reducer, controlLayersUndoableConfig),
|
||||
[canvasV2Slice.name]: canvasV2Slice.reducer,
|
||||
[workflowSettingsSlice.name]: workflowSettingsSlice.reducer,
|
||||
[api.reducerPath]: api.reducer,
|
||||
[upscaleSlice.name]: upscaleSlice.reducer,
|
||||
[stylePresetSlice.name]: stylePresetSlice.reducer,
|
||||
[layersSlice.name]: layersSlice.reducer,
|
||||
[controlAdaptersV2Slice.name]: controlAdaptersV2Slice.reducer,
|
||||
[ipAdaptersSlice.name]: ipAdaptersSlice.reducer,
|
||||
[regionalGuidanceSlice.name]: regionalGuidanceSlice.reducer,
|
||||
};
|
||||
|
||||
const rootReducer = combineReducers(allReducers);
|
||||
@ -107,16 +112,19 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
|
||||
[systemPersistConfig.name]: systemPersistConfig,
|
||||
[workflowPersistConfig.name]: workflowPersistConfig,
|
||||
[uiPersistConfig.name]: uiPersistConfig,
|
||||
[controlAdaptersPersistConfig.name]: controlAdaptersPersistConfig,
|
||||
[dynamicPromptsPersistConfig.name]: dynamicPromptsPersistConfig,
|
||||
[sdxlPersistConfig.name]: sdxlPersistConfig,
|
||||
[loraPersistConfig.name]: loraPersistConfig,
|
||||
[modelManagerV2PersistConfig.name]: modelManagerV2PersistConfig,
|
||||
[hrfPersistConfig.name]: hrfPersistConfig,
|
||||
[controlLayersPersistConfig.name]: controlLayersPersistConfig,
|
||||
[canvasV2PersistConfig.name]: canvasV2PersistConfig,
|
||||
[workflowSettingsPersistConfig.name]: workflowSettingsPersistConfig,
|
||||
[upscalePersistConfig.name]: upscalePersistConfig,
|
||||
[stylePresetPersistConfig.name]: stylePresetPersistConfig,
|
||||
[layersPersistConfig.name]: layersPersistConfig,
|
||||
[controlAdaptersV2PersistConfig.name]: controlAdaptersV2PersistConfig,
|
||||
[ipAdaptersPersistConfig.name]: ipAdaptersPersistConfig,
|
||||
[regionalGuidancePersistConfig.name]: regionalGuidancePersistConfig,
|
||||
};
|
||||
|
||||
const unserialize: UnserializeFunction = (data, key) => {
|
||||
|
@ -6,8 +6,8 @@ import {
|
||||
selectControlAdaptersSlice,
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
||||
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { Layer } from 'features/controlLayers/store/types';
|
||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { LayerData } 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,7 +24,7 @@ import { forEach, upperFirst } from 'lodash-es';
|
||||
import { useMemo } from 'react';
|
||||
import { getConnectedEdges } from 'reactflow';
|
||||
|
||||
const LAYER_TYPE_TO_TKEY: Record<Layer['type'], string> = {
|
||||
const LAYER_TYPE_TO_TKEY: Record<LayerData['type'], string> = {
|
||||
initial_image_layer: 'controlLayers.globalInitialImage',
|
||||
control_adapter_layer: 'controlLayers.globalControlAdapter',
|
||||
ip_adapter_layer: 'controlLayers.globalIPAdapter',
|
||||
@ -41,7 +41,7 @@ const createSelector = (templates: Templates) =>
|
||||
selectNodesSlice,
|
||||
selectWorkflowSettingsSlice,
|
||||
selectDynamicPromptsSlice,
|
||||
selectControlLayersSlice,
|
||||
selectCanvasV2Slice,
|
||||
activeTabNameSelector,
|
||||
selectUpscalelice,
|
||||
selectConfigSlice,
|
||||
|
@ -1,85 +1,170 @@
|
||||
import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/arrayUtils';
|
||||
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe('Array Manipulation Functions', () => {
|
||||
const originalArray = ['a', 'b', 'c', 'd'];
|
||||
describe('moveForwardOne', () => {
|
||||
it('should move an item forward by one position', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveForward(array, (item) => item === 'b');
|
||||
expect(result).toEqual(['a', 'c', 'b', 'd']);
|
||||
});
|
||||
|
||||
it('should do nothing if the item is at the end', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveForward(array, (item) => item === 'd');
|
||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
describe('moveOneToEnd', () => {
|
||||
describe('with callback', () => {
|
||||
it('should move an item forward by one position', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveOneToEnd(array, (item) => item === 'b');
|
||||
expect(result).toEqual(['a', 'c', 'b', 'd']);
|
||||
});
|
||||
|
||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveForward(array, (item) => item === 'z');
|
||||
expect(result).toEqual(originalArray);
|
||||
it('should do nothing if the item is at the end', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveOneToEnd(array, (item) => item === 'd');
|
||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
|
||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveOneToEnd(array, (item) => item === 'z');
|
||||
expect(result).toEqual(originalArray);
|
||||
});
|
||||
});
|
||||
describe('with item', () => {
|
||||
it('should move an item forward by one position', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveOneToEnd(array, (item) => item === 'b');
|
||||
expect(result).toEqual(['a', 'c', 'b', 'd']);
|
||||
});
|
||||
|
||||
it('should do nothing if the item is at the end', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveOneToEnd(array, (item) => item === 'd');
|
||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
|
||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveOneToEnd(array, (item) => item === 'z');
|
||||
expect(result).toEqual(originalArray);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('moveToFront', () => {
|
||||
it('should move an item to the front', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToFront(array, (item) => item === 'c');
|
||||
expect(result).toEqual(['c', 'a', 'b', 'd']);
|
||||
});
|
||||
describe('moveToStart', () => {
|
||||
describe('with callback', () => {
|
||||
it('should move an item to the front', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToStart(array, (item) => item === 'c');
|
||||
expect(result).toEqual(['c', 'a', 'b', 'd']);
|
||||
});
|
||||
|
||||
it('should do nothing if the item is already at the front', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToFront(array, (item) => item === 'a');
|
||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
it('should do nothing if the item is already at the front', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToStart(array, (item) => item === 'a');
|
||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
|
||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToFront(array, (item) => item === 'z');
|
||||
expect(result).toEqual(originalArray);
|
||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToStart(array, (item) => item === 'z');
|
||||
expect(result).toEqual(originalArray);
|
||||
});
|
||||
});
|
||||
describe('with item', () => {
|
||||
it('should move an item to the front', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToStart(array, 'c');
|
||||
expect(result).toEqual(['c', 'a', 'b', 'd']);
|
||||
});
|
||||
|
||||
it('should do nothing if the item is already at the front', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToStart(array, 'a');
|
||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
|
||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToStart(array, 'z');
|
||||
expect(result).toEqual(originalArray);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('moveBackwardsOne', () => {
|
||||
it('should move an item backward by one position', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveBackward(array, (item) => item === 'c');
|
||||
expect(result).toEqual(['a', 'c', 'b', 'd']);
|
||||
});
|
||||
describe('moveOneToStart', () => {
|
||||
describe('with callback', () => {
|
||||
it('should move an item backward by one position', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveOneToStart(array, (item) => item === 'c');
|
||||
expect(result).toEqual(['a', 'c', 'b', 'd']);
|
||||
});
|
||||
|
||||
it('should do nothing if the item is at the beginning', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveBackward(array, (item) => item === 'a');
|
||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
it('should do nothing if the item is at the beginning', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveOneToStart(array, (item) => item === 'a');
|
||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
|
||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveBackward(array, (item) => item === 'z');
|
||||
expect(result).toEqual(originalArray);
|
||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveOneToStart(array, (item) => item === 'z');
|
||||
expect(result).toEqual(originalArray);
|
||||
});
|
||||
});
|
||||
describe('with item', () => {
|
||||
it('should move an item backward by one position', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveOneToStart(array, 'c');
|
||||
expect(result).toEqual(['a', 'c', 'b', 'd']);
|
||||
});
|
||||
|
||||
it('should do nothing if the item is at the beginning', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveOneToStart(array, 'a');
|
||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
|
||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveOneToStart(array, 'z');
|
||||
expect(result).toEqual(originalArray);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('moveToBack', () => {
|
||||
it('should move an item to the back', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToBack(array, (item) => item === 'b');
|
||||
expect(result).toEqual(['a', 'c', 'd', 'b']);
|
||||
});
|
||||
describe('moveToEnd', () => {
|
||||
describe('with callback', () => {
|
||||
it('should move an item to the back', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToEnd(array, (item) => item === 'b');
|
||||
expect(result).toEqual(['a', 'c', 'd', 'b']);
|
||||
});
|
||||
|
||||
it('should do nothing if the item is already at the back', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToBack(array, (item) => item === 'd');
|
||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
it('should do nothing if the item is already at the back', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToEnd(array, (item) => item === 'd');
|
||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
|
||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToBack(array, (item) => item === 'z');
|
||||
expect(result).toEqual(originalArray);
|
||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToEnd(array, (item) => item === 'z');
|
||||
expect(result).toEqual(originalArray);
|
||||
});
|
||||
});
|
||||
describe('with item', () => {
|
||||
it('should move an item to the back', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToEnd(array, 'b');
|
||||
expect(result).toEqual(['a', 'c', 'd', 'b']);
|
||||
});
|
||||
|
||||
it('should do nothing if the item is already at the back', () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToEnd(array, 'd');
|
||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
|
||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||
const array = [...originalArray];
|
||||
const result = moveToEnd(array, 'z');
|
||||
expect(result).toEqual(originalArray);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,37 +1,45 @@
|
||||
export const moveForward = <T>(array: T[], callback: (item: T) => boolean): T[] => {
|
||||
const index = array.findIndex(callback);
|
||||
if (index >= 0 && index < array.length - 1) {
|
||||
//@ts-expect-error - These indicies are safe per the previous check
|
||||
[array[index], array[index + 1]] = [array[index + 1], array[index]];
|
||||
}
|
||||
return array;
|
||||
};
|
||||
|
||||
export const moveToFront = <T>(array: T[], callback: (item: T) => boolean): T[] => {
|
||||
const index = array.findIndex(callback);
|
||||
export function moveToStart<T>(array: T[], selectItemCallback: (item: T) => boolean): T[];
|
||||
export function moveToStart<T>(array: T[], item: T): T[];
|
||||
export function moveToStart<T>(array: T[], arg1: T | ((item: T) => boolean)): T[] {
|
||||
const index = arg1 instanceof Function ? array.findIndex(arg1) : array.indexOf(arg1);
|
||||
if (index > 0) {
|
||||
const [item] = array.splice(index, 1);
|
||||
//@ts-expect-error - These indicies are safe per the previous check
|
||||
array.unshift(item);
|
||||
}
|
||||
return array;
|
||||
};
|
||||
}
|
||||
|
||||
export const moveBackward = <T>(array: T[], callback: (item: T) => boolean): T[] => {
|
||||
const index = array.findIndex(callback);
|
||||
export function moveOneToStart<T>(array: T[], selectItemCallback: (item: T) => boolean): T[];
|
||||
export function moveOneToStart<T>(array: T[], item: T): T[];
|
||||
export function moveOneToStart<T>(array: T[], arg1: T | ((item: T) => boolean)): T[] {
|
||||
const index = arg1 instanceof Function ? array.findIndex(arg1) : array.indexOf(arg1);
|
||||
if (index > 0) {
|
||||
//@ts-expect-error - These indicies are safe per the previous check
|
||||
[array[index], array[index - 1]] = [array[index - 1], array[index]];
|
||||
}
|
||||
return array;
|
||||
};
|
||||
}
|
||||
|
||||
export const moveToBack = <T>(array: T[], callback: (item: T) => boolean): T[] => {
|
||||
const index = array.findIndex(callback);
|
||||
export function moveToEnd<T>(array: T[], selectItemCallback: (item: T) => boolean): T[];
|
||||
export function moveToEnd<T>(array: T[], item: T): T[];
|
||||
export function moveToEnd<T>(array: T[], arg1: T | ((item: T) => boolean)): T[] {
|
||||
const index = arg1 instanceof Function ? array.findIndex(arg1) : array.indexOf(arg1);
|
||||
if (index >= 0 && index < array.length - 1) {
|
||||
const [item] = array.splice(index, 1);
|
||||
//@ts-expect-error - These indicies are safe per the previous check
|
||||
array.push(item);
|
||||
}
|
||||
return array;
|
||||
};
|
||||
}
|
||||
|
||||
export function moveOneToEnd<T>(array: T[], selectItemCallback: (item: T) => boolean): T[];
|
||||
export function moveOneToEnd<T>(array: T[], item: T): T[];
|
||||
export function moveOneToEnd<T>(array: T[], arg1: T | ((item: T) => boolean)): T[] {
|
||||
const index = arg1 instanceof Function ? array.findIndex(arg1) : array.indexOf(arg1);
|
||||
if (index >= 0 && index < array.length - 1) {
|
||||
//@ts-expect-error - These indicies are safe per the previous check
|
||||
[array[index], array[index + 1]] = [array[index + 1], array[index]];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
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 { rasterLayerAdded, rgLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { layerAdded, regionalGuidanceAdded } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiPlusBold } from 'react-icons/pi';
|
||||
@ -13,10 +13,10 @@ export const AddLayerButton = memo(() => {
|
||||
const [addIPALayer, isAddIPALayerDisabled] = useAddIPALayer();
|
||||
const [addIILayer, isAddIILayerDisabled] = useAddIILayer();
|
||||
const addRGLayer = useCallback(() => {
|
||||
dispatch(rgLayerAdded());
|
||||
dispatch(regionalGuidanceAdded());
|
||||
}, [dispatch]);
|
||||
const addRasterLayer = useCallback(() => {
|
||||
dispatch(rasterLayerAdded());
|
||||
dispatch(layerAdded());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
|
@ -3,9 +3,9 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAddIPAdapterToIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import {
|
||||
rgLayerNegativePromptChanged,
|
||||
rgLayerPositivePromptChanged,
|
||||
selectControlLayersSlice,
|
||||
regionalGuidanceNegativePromptChanged,
|
||||
regionalGuidancePositivePromptChanged,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isRegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
@ -22,7 +22,7 @@ export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => {
|
||||
const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToIPALayer(layerId);
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
return {
|
||||
@ -34,10 +34,10 @@ export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => {
|
||||
);
|
||||
const validActions = useAppSelector(selectValidActions);
|
||||
const addPositivePrompt = useCallback(() => {
|
||||
dispatch(rgLayerPositivePromptChanged({ layerId, prompt: '' }));
|
||||
dispatch(regionalGuidancePositivePromptChanged({ layerId, prompt: '' }));
|
||||
}, [dispatch, layerId]);
|
||||
const addNegativePrompt = useCallback(() => {
|
||||
dispatch(rgLayerNegativePromptChanged({ layerId, prompt: '' }));
|
||||
dispatch(regionalGuidanceNegativePromptChanged({ layerId, prompt: '' }));
|
||||
}, [dispatch, layerId]);
|
||||
|
||||
return (
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { ControlAdapter } from 'features/controlLayers/components/ControlAndIPAdapter/ControlAdapter';
|
||||
import {
|
||||
caLayerControlModeChanged,
|
||||
caLayerImageChanged,
|
||||
caLayerModelChanged,
|
||||
caLayerProcessedImageChanged,
|
||||
caLayerProcessorConfigChanged,
|
||||
caOrIPALayerBeginEndStepPctChanged,
|
||||
caOrIPALayerWeightChanged,
|
||||
controlAdapterControlModeChanged,
|
||||
controlAdapterImageChanged,
|
||||
controlAdapterModelChanged,
|
||||
controlAdapterProcessedImageChanged,
|
||||
controlAdapterProcessorConfigChanged,
|
||||
selectLayerOrThrow,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isControlAdapterLayer } from 'features/controlLayers/store/types';
|
||||
@ -46,7 +46,7 @@ export const CALayerControlAdapterWrapper = memo(({ layerId }: Props) => {
|
||||
const onChangeControlMode = useCallback(
|
||||
(controlMode: ControlModeV2) => {
|
||||
dispatch(
|
||||
caLayerControlModeChanged({
|
||||
controlAdapterControlModeChanged({
|
||||
layerId,
|
||||
controlMode,
|
||||
})
|
||||
@ -64,7 +64,7 @@ export const CALayerControlAdapterWrapper = memo(({ layerId }: Props) => {
|
||||
|
||||
const onChangeProcessorConfig = useCallback(
|
||||
(processorConfig: ProcessorConfig | null) => {
|
||||
dispatch(caLayerProcessorConfigChanged({ layerId, processorConfig }));
|
||||
dispatch(controlAdapterProcessorConfigChanged({ layerId, processorConfig }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
@ -72,7 +72,7 @@ export const CALayerControlAdapterWrapper = memo(({ layerId }: Props) => {
|
||||
const onChangeModel = useCallback(
|
||||
(modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => {
|
||||
dispatch(
|
||||
caLayerModelChanged({
|
||||
controlAdapterModelChanged({
|
||||
layerId,
|
||||
modelConfig,
|
||||
})
|
||||
@ -83,17 +83,17 @@ export const CALayerControlAdapterWrapper = memo(({ layerId }: Props) => {
|
||||
|
||||
const onChangeImage = useCallback(
|
||||
(imageDTO: ImageDTO | null) => {
|
||||
dispatch(caLayerImageChanged({ layerId, imageDTO }));
|
||||
dispatch(controlAdapterImageChanged({ layerId, imageDTO }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onErrorLoadingImage = useCallback(() => {
|
||||
dispatch(caLayerImageChanged({ layerId, imageDTO: null }));
|
||||
dispatch(controlAdapterImageChanged({ layerId, imageDTO: null }));
|
||||
}, [dispatch, layerId]);
|
||||
|
||||
const onErrorLoadingProcessedImage = useCallback(() => {
|
||||
dispatch(caLayerProcessedImageChanged({ layerId, imageDTO: null }));
|
||||
dispatch(controlAdapterProcessedImageChanged({ layerId, imageDTO: null }));
|
||||
}, [dispatch, layerId]);
|
||||
|
||||
const droppableData = useMemo<CALayerImageDropData>(
|
||||
|
@ -11,14 +11,14 @@ import { IILayer } from 'features/controlLayers/components/IILayer/IILayer';
|
||||
import { IPALayer } from 'features/controlLayers/components/IPALayer/IPALayer';
|
||||
import { RasterLayer } from 'features/controlLayers/components/RasterLayer/RasterLayer';
|
||||
import { RGLayer } from 'features/controlLayers/components/RGLayer/RGLayer';
|
||||
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { Layer } from 'features/controlLayers/store/types';
|
||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { LayerData } from 'features/controlLayers/store/types';
|
||||
import { isRenderableLayer } from 'features/controlLayers/store/types';
|
||||
import { partition } from 'lodash-es';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selectLayerIdTypePairs = createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
const selectLayerIdTypePairs = createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const [renderableLayers, ipAdapterLayers] = partition(controlLayers.present.layers, isRenderableLayer);
|
||||
return [...ipAdapterLayers, ...renderableLayers].map((l) => ({ id: l.id, type: l.type })).reverse();
|
||||
});
|
||||
@ -50,7 +50,7 @@ ControlLayersPanelContent.displayName = 'ControlLayersPanelContent';
|
||||
|
||||
type LayerWrapperProps = {
|
||||
id: string;
|
||||
type: Layer['type'];
|
||||
type: LayerData['type'];
|
||||
};
|
||||
|
||||
const LayerWrapper = memo(({ id, type }: LayerWrapperProps) => {
|
||||
|
@ -3,10 +3,10 @@ import { IPAdapter } from 'features/controlLayers/components/ControlAndIPAdapter
|
||||
import {
|
||||
caOrIPALayerBeginEndStepPctChanged,
|
||||
caOrIPALayerWeightChanged,
|
||||
ipaLayerCLIPVisionModelChanged,
|
||||
ipaLayerImageChanged,
|
||||
ipaLayerMethodChanged,
|
||||
ipaLayerModelChanged,
|
||||
ipAdapterCLIPVisionModelChanged,
|
||||
ipAdapterImageChanged,
|
||||
ipAdapterMethodChanged,
|
||||
ipAdapterModelChanged,
|
||||
selectLayerOrThrow,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isIPAdapterLayer } from 'features/controlLayers/store/types';
|
||||
@ -46,28 +46,28 @@ export const IPALayerIPAdapterWrapper = memo(({ layerId }: Props) => {
|
||||
|
||||
const onChangeIPMethod = useCallback(
|
||||
(method: IPMethodV2) => {
|
||||
dispatch(ipaLayerMethodChanged({ layerId, method }));
|
||||
dispatch(ipAdapterMethodChanged({ layerId, method }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(modelConfig: IPAdapterModelConfig) => {
|
||||
dispatch(ipaLayerModelChanged({ layerId, modelConfig }));
|
||||
dispatch(ipAdapterModelChanged({ layerId, modelConfig }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeCLIPVisionModel = useCallback(
|
||||
(clipVisionModel: CLIPVisionModelV2) => {
|
||||
dispatch(ipaLayerCLIPVisionModelChanged({ layerId, clipVisionModel }));
|
||||
dispatch(ipAdapterCLIPVisionModelChanged({ layerId, clipVisionModel }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
||||
const onChangeImage = useCallback(
|
||||
(imageDTO: ImageDTO | null) => {
|
||||
dispatch(ipaLayerImageChanged({ layerId, imageDTO }));
|
||||
dispatch(ipAdapterImageChanged({ layerId, imageDTO }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
layerMovedForward,
|
||||
layerMovedToBack,
|
||||
layerMovedToFront,
|
||||
selectControlLayersSlice,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isRenderableLayer } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
@ -21,7 +21,7 @@ export const LayerMenuArrangeActions = memo(({ layerId }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
assert(isRenderableLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
const layerIndex = controlLayers.present.layers.findIndex((l) => l.id === layerId);
|
||||
|
@ -3,9 +3,9 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAddIPAdapterToIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import {
|
||||
rgLayerNegativePromptChanged,
|
||||
rgLayerPositivePromptChanged,
|
||||
selectControlLayersSlice,
|
||||
regionalGuidanceNegativePromptChanged,
|
||||
regionalGuidancePositivePromptChanged,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isRegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
@ -21,7 +21,7 @@ export const LayerMenuRGActions = memo(({ layerId }: Props) => {
|
||||
const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToIPALayer(layerId);
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
return {
|
||||
@ -33,10 +33,10 @@ export const LayerMenuRGActions = memo(({ layerId }: Props) => {
|
||||
);
|
||||
const validActions = useAppSelector(selectValidActions);
|
||||
const addPositivePrompt = useCallback(() => {
|
||||
dispatch(rgLayerPositivePromptChanged({ layerId, prompt: '' }));
|
||||
dispatch(regionalGuidancePositivePromptChanged({ layerId, prompt: '' }));
|
||||
}, [dispatch, layerId]);
|
||||
const addNegativePrompt = useCallback(() => {
|
||||
dispatch(rgLayerNegativePromptChanged({ layerId, prompt: '' }));
|
||||
dispatch(regionalGuidanceNegativePromptChanged({ layerId, prompt: '' }));
|
||||
}, [dispatch, layerId]);
|
||||
return (
|
||||
<>
|
||||
|
@ -16,7 +16,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import {
|
||||
layerOpacityChanged,
|
||||
selectControlLayersSlice,
|
||||
selectCanvasV2Slice,
|
||||
selectLayerOrThrow,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isLayerWithOpacity } from 'features/controlLayers/store/types';
|
||||
@ -36,7 +36,7 @@ export const LayerOpacity = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const selectOpacity = useMemo(
|
||||
() =>
|
||||
createSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = selectLayerOrThrow(controlLayers.present, layerId, isLayerWithOpacity);
|
||||
return Math.round(layer.opacity * 100);
|
||||
}),
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Text } from '@invoke-ai/ui-library';
|
||||
import type { Layer } from 'features/controlLayers/store/types';
|
||||
import type { LayerData } from 'features/controlLayers/store/types';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
type: Layer['type'];
|
||||
type: LayerData['type'];
|
||||
};
|
||||
|
||||
export const LayerTitle = memo(({ type }: Props) => {
|
||||
|
@ -8,7 +8,7 @@ import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMe
|
||||
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, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { layerSelected, selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isRegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -29,7 +29,7 @@ export const RGLayer = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
return {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { rgLayerAutoNegativeChanged, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { regionalGuidanceAutoNegativeChanged, selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isRegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
@ -15,7 +15,7 @@ type Props = {
|
||||
const useAutoNegative = (layerId: string) => {
|
||||
const selectAutoNegative = useMemo(
|
||||
() =>
|
||||
createSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
return layer.autoNegative;
|
||||
@ -32,7 +32,7 @@ export const RGLayerAutoNegativeCheckbox = memo(({ layerId }: Props) => {
|
||||
const autoNegative = useAutoNegative(layerId);
|
||||
const onChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(rgLayerAutoNegativeChanged({ layerId, autoNegative: e.target.checked ? 'invert' : 'off' }));
|
||||
dispatch(regionalGuidanceAutoNegativeChanged({ layerId, autoNegative: e.target.checked ? 'invert' : 'off' }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import RgbColorPicker from 'common/components/RgbColorPicker';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { rgbColorToString } from 'features/canvas/util/colorToString';
|
||||
import { rgLayerPreviewColorChanged, selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { rgFillChanged, selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isRegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import type { RgbColor } from 'react-colorful';
|
||||
@ -19,7 +19,7 @@ export const RGLayerColorPicker = memo(({ layerId }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const selectColor = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an vector mask layer`);
|
||||
return layer.previewColor;
|
||||
@ -30,7 +30,7 @@ export const RGLayerColorPicker = memo(({ layerId }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const onColorChange = useCallback(
|
||||
(color: RgbColor) => {
|
||||
dispatch(rgLayerPreviewColorChanged({ layerId, color }));
|
||||
dispatch(rgFillChanged({ layerId, color }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
@ -2,7 +2,7 @@ import { Divider, Flex } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { RGLayerIPAdapterWrapper } from 'features/controlLayers/components/RGLayer/RGLayerIPAdapterWrapper';
|
||||
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isRegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { assert } from 'tsafe';
|
||||
@ -14,7 +14,7 @@ type Props = {
|
||||
export const RGLayerIPAdapterList = memo(({ layerId }: Props) => {
|
||||
const selectIPAdapterIds = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.filter(isRegionalGuidanceLayer).find((l) => l.id === layerId);
|
||||
assert(layer, `Layer ${layerId} not found`);
|
||||
return layer.ipAdapters;
|
||||
|
@ -2,13 +2,13 @@ import { Flex, IconButton, Spacer, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { IPAdapter } from 'features/controlLayers/components/ControlAndIPAdapter/IPAdapter';
|
||||
import {
|
||||
rgLayerIPAdapterBeginEndStepPctChanged,
|
||||
rgLayerIPAdapterCLIPVisionModelChanged,
|
||||
rgLayerIPAdapterDeleted,
|
||||
rgLayerIPAdapterImageChanged,
|
||||
rgLayerIPAdapterMethodChanged,
|
||||
rgLayerIPAdapterModelChanged,
|
||||
rgLayerIPAdapterWeightChanged,
|
||||
regionalGuidanceIPAdapterBeginEndStepPctChanged,
|
||||
regionalGuidanceIPAdapterCLIPVisionModelChanged,
|
||||
regionalGuidanceIPAdapterDeleted,
|
||||
regionalGuidanceIPAdapterImageChanged,
|
||||
regionalGuidanceIPAdapterMethodChanged,
|
||||
regionalGuidanceIPAdapterModelChanged,
|
||||
regionalGuidanceIPAdapterWeightChanged,
|
||||
selectRGLayerIPAdapterOrThrow,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
@ -26,14 +26,14 @@ type Props = {
|
||||
export const RGLayerIPAdapterWrapper = memo(({ layerId, ipAdapterId, ipAdapterNumber }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const onDeleteIPAdapter = useCallback(() => {
|
||||
dispatch(rgLayerIPAdapterDeleted({ layerId, ipAdapterId }));
|
||||
dispatch(regionalGuidanceIPAdapterDeleted({ layerId, ipAdapterId }));
|
||||
}, [dispatch, ipAdapterId, layerId]);
|
||||
const ipAdapter = useAppSelector((s) => selectRGLayerIPAdapterOrThrow(s.controlLayers.present, layerId, ipAdapterId));
|
||||
|
||||
const onChangeBeginEndStepPct = useCallback(
|
||||
(beginEndStepPct: [number, number]) => {
|
||||
dispatch(
|
||||
rgLayerIPAdapterBeginEndStepPctChanged({
|
||||
regionalGuidanceIPAdapterBeginEndStepPctChanged({
|
||||
layerId,
|
||||
ipAdapterId,
|
||||
beginEndStepPct,
|
||||
@ -45,35 +45,35 @@ export const RGLayerIPAdapterWrapper = memo(({ layerId, ipAdapterId, ipAdapterNu
|
||||
|
||||
const onChangeWeight = useCallback(
|
||||
(weight: number) => {
|
||||
dispatch(rgLayerIPAdapterWeightChanged({ layerId, ipAdapterId, weight }));
|
||||
dispatch(regionalGuidanceIPAdapterWeightChanged({ layerId, ipAdapterId, weight }));
|
||||
},
|
||||
[dispatch, ipAdapterId, layerId]
|
||||
);
|
||||
|
||||
const onChangeIPMethod = useCallback(
|
||||
(method: IPMethodV2) => {
|
||||
dispatch(rgLayerIPAdapterMethodChanged({ layerId, ipAdapterId, method }));
|
||||
dispatch(regionalGuidanceIPAdapterMethodChanged({ layerId, ipAdapterId, method }));
|
||||
},
|
||||
[dispatch, ipAdapterId, layerId]
|
||||
);
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(modelConfig: IPAdapterModelConfig) => {
|
||||
dispatch(rgLayerIPAdapterModelChanged({ layerId, ipAdapterId, modelConfig }));
|
||||
dispatch(regionalGuidanceIPAdapterModelChanged({ layerId, ipAdapterId, modelConfig }));
|
||||
},
|
||||
[dispatch, ipAdapterId, layerId]
|
||||
);
|
||||
|
||||
const onChangeCLIPVisionModel = useCallback(
|
||||
(clipVisionModel: CLIPVisionModelV2) => {
|
||||
dispatch(rgLayerIPAdapterCLIPVisionModelChanged({ layerId, ipAdapterId, clipVisionModel }));
|
||||
dispatch(regionalGuidanceIPAdapterCLIPVisionModelChanged({ layerId, ipAdapterId, clipVisionModel }));
|
||||
},
|
||||
[dispatch, ipAdapterId, layerId]
|
||||
);
|
||||
|
||||
const onChangeImage = useCallback(
|
||||
(imageDTO: ImageDTO | null) => {
|
||||
dispatch(rgLayerIPAdapterImageChanged({ layerId, ipAdapterId, imageDTO }));
|
||||
dispatch(regionalGuidanceIPAdapterImageChanged({ layerId, ipAdapterId, imageDTO }));
|
||||
},
|
||||
[dispatch, ipAdapterId, layerId]
|
||||
);
|
||||
|
@ -2,7 +2,7 @@ import { Box, Textarea } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { RGLayerPromptDeleteButton } from 'features/controlLayers/components/RGLayer/RGLayerPromptDeleteButton';
|
||||
import { useLayerNegativePrompt } from 'features/controlLayers/hooks/layerStateHooks';
|
||||
import { rgLayerNegativePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { regionalGuidanceNegativePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||
@ -21,7 +21,7 @@ export const RGLayerNegativePrompt = memo(({ layerId }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const _onChange = useCallback(
|
||||
(v: string) => {
|
||||
dispatch(rgLayerNegativePromptChanged({ layerId, prompt: v }));
|
||||
dispatch(regionalGuidanceNegativePromptChanged({ layerId, prompt: v }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
@ -2,7 +2,7 @@ import { Box, Textarea } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { RGLayerPromptDeleteButton } from 'features/controlLayers/components/RGLayer/RGLayerPromptDeleteButton';
|
||||
import { useLayerPositivePrompt } from 'features/controlLayers/hooks/layerStateHooks';
|
||||
import { rgLayerPositivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { regionalGuidancePositivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||
@ -21,7 +21,7 @@ export const RGLayerPositivePrompt = memo(({ layerId }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const _onChange = useCallback(
|
||||
(v: string) => {
|
||||
dispatch(rgLayerPositivePromptChanged({ layerId, prompt: v }));
|
||||
dispatch(regionalGuidancePositivePromptChanged({ layerId, prompt: v }));
|
||||
},
|
||||
[dispatch, layerId]
|
||||
);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { IconButton, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import {
|
||||
rgLayerNegativePromptChanged,
|
||||
rgLayerPositivePromptChanged,
|
||||
regionalGuidanceNegativePromptChanged,
|
||||
regionalGuidancePositivePromptChanged,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -18,9 +18,9 @@ export const RGLayerPromptDeleteButton = memo(({ layerId, polarity }: Props) =>
|
||||
const dispatch = useAppDispatch();
|
||||
const onClick = useCallback(() => {
|
||||
if (polarity === 'positive') {
|
||||
dispatch(rgLayerPositivePromptChanged({ layerId, prompt: null }));
|
||||
dispatch(regionalGuidancePositivePromptChanged({ layerId, prompt: null }));
|
||||
} else {
|
||||
dispatch(rgLayerNegativePromptChanged({ layerId, prompt: null }));
|
||||
dispatch(regionalGuidanceNegativePromptChanged({ layerId, prompt: null }));
|
||||
}
|
||||
}, [dispatch, layerId, polarity]);
|
||||
return (
|
||||
|
@ -2,8 +2,8 @@ import { $alt, $ctrl, $meta, $shift, Box, Flex, Heading } from '@invoke-ai/ui-li
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||
import { HeadsUpDisplay } from 'features/controlLayers/components/HeadsUpDisplay';
|
||||
import {
|
||||
BRUSH_SPACING_PCT,
|
||||
@ -15,16 +15,16 @@ import { setStageEventHandlers } from 'features/controlLayers/konva/events';
|
||||
import { debouncedRenderers, renderers as normalRenderers } from 'features/controlLayers/konva/renderers/layers';
|
||||
import {
|
||||
$bbox,
|
||||
$brushColor,
|
||||
$brushSize,
|
||||
$brushSpacingPx,
|
||||
$brushWidth,
|
||||
$fill,
|
||||
$invertScroll,
|
||||
$isDrawing,
|
||||
$isMouseDown,
|
||||
$lastAddedPoint,
|
||||
$lastCursorPos,
|
||||
$lastMouseDownPos,
|
||||
$selectedLayer,
|
||||
$shouldInvertBrushSizeScrollDirection,
|
||||
$spaceKey,
|
||||
$stageAttrs,
|
||||
$tool,
|
||||
@ -37,15 +37,16 @@ import {
|
||||
layerTranslated,
|
||||
linePointsAdded,
|
||||
rectAdded,
|
||||
selectControlLayersSlice,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { selectLayersSlice } from 'features/controlLayers/store/layersSlice';
|
||||
import { selectRegionalGuidanceSlice } from 'features/controlLayers/store/regionalGuidanceSlice';
|
||||
import type {
|
||||
AddBrushLineArg,
|
||||
AddEraserLineArg,
|
||||
AddPointToLineArg,
|
||||
AddRectShapeArg,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { isRegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { IRect } from 'konva/lib/types';
|
||||
import { clamp } from 'lodash-es';
|
||||
@ -60,26 +61,26 @@ Konva.showWarnings = false;
|
||||
|
||||
const log = logger('controlLayers');
|
||||
|
||||
const selectBrushColor = createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers
|
||||
.filter(isRegionalGuidanceLayer)
|
||||
.find((l) => l.id === controlLayers.present.selectedLayerId);
|
||||
const selectBrushColor = createSelector(
|
||||
selectCanvasV2Slice,
|
||||
selectLayersSlice,
|
||||
selectRegionalGuidanceSlice,
|
||||
(canvas, layers, regionalGuidance) => {
|
||||
const rg = regionalGuidance.regions.find((i) => i.id === canvas.lastSelectedItem?.id);
|
||||
|
||||
if (layer) {
|
||||
return { ...layer.previewColor, a: controlLayers.present.globalMaskLayerOpacity };
|
||||
if (rg) {
|
||||
return rgbaColorToString({ ...rg.fill, a: regionalGuidance.opacity });
|
||||
}
|
||||
|
||||
return rgbaColorToString(canvas.tool.fill);
|
||||
}
|
||||
);
|
||||
|
||||
return controlLayers.present.brushColor;
|
||||
});
|
||||
|
||||
const selectSelectedLayer = createSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
const selectSelectedLayer = createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
return controlLayers.present.layers.find((l) => l.id === controlLayers.present.selectedLayerId) ?? null;
|
||||
});
|
||||
|
||||
const selectLayerCount = createSelector(
|
||||
selectControlLayersSlice,
|
||||
(controlLayers) => controlLayers.present.layers.length
|
||||
);
|
||||
const selectLayerCount = createSelector(selectCanvasV2Slice, (controlLayers) => controlLayers.present.layers.length);
|
||||
|
||||
const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null, asPreview: boolean) => {
|
||||
const dispatch = useAppDispatch();
|
||||
@ -100,11 +101,11 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null,
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
$brushColor.set(brushColor);
|
||||
$brushSize.set(state.brushSize);
|
||||
$fill.set(brushColor);
|
||||
$brushWidth.set(state.brushSize);
|
||||
$brushSpacingPx.set(brushSpacingPx);
|
||||
$selectedLayer.set(selectedLayer);
|
||||
$shouldInvertBrushSizeScrollDirection.set(shouldInvertBrushSizeScrollDirection);
|
||||
$invertScroll.set(shouldInvertBrushSizeScrollDirection);
|
||||
$bbox.set(state.bbox);
|
||||
}, [
|
||||
brushSpacingPx,
|
||||
@ -196,8 +197,8 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null,
|
||||
setIsDrawing: $isDrawing.set,
|
||||
getIsMouseDown: $isMouseDown.get,
|
||||
setIsMouseDown: $isMouseDown.set,
|
||||
getBrushColor: $brushColor.get,
|
||||
getBrushSize: $brushSize.get,
|
||||
getBrushColor: $fill.get,
|
||||
getBrushSize: $brushWidth.get,
|
||||
getBrushSpacingPx: $brushSpacingPx.get,
|
||||
getSelectedLayer: $selectedLayer.get,
|
||||
getLastAddedPoint: $lastAddedPoint.get,
|
||||
@ -206,7 +207,7 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null,
|
||||
setLastCursorPos: $lastCursorPos.set,
|
||||
getLastMouseDownPos: $lastMouseDownPos.get,
|
||||
setLastMouseDownPos: $lastMouseDownPos.set,
|
||||
getShouldInvert: $shouldInvertBrushSizeScrollDirection.get,
|
||||
getShouldInvert: $invertScroll.get,
|
||||
getSpaceKey: $spaceKey.get,
|
||||
setStageAttrs: $stageAttrs.set,
|
||||
onBrushSizeChanged,
|
||||
|
@ -5,7 +5,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
$tool,
|
||||
layerReset,
|
||||
selectControlLayersSlice,
|
||||
selectCanvasV2Slice,
|
||||
selectedLayerDeleted,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { useCallback } from 'react';
|
||||
@ -20,7 +20,7 @@ import {
|
||||
PiRectangleBold,
|
||||
} from 'react-icons/pi';
|
||||
|
||||
const selectIsDisabled = createSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
const selectIsDisabled = createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const selectedLayer = controlLayers.present.layers.find((l) => l.id === controlLayers.present.selectedLayerId);
|
||||
return selectedLayer?.type !== 'regional_guidance_layer' && selectedLayer?.type !== 'raster_layer';
|
||||
});
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
caLayerAdded,
|
||||
controlAdapterAdded,
|
||||
iiLayerAdded,
|
||||
ipaLayerAdded,
|
||||
rgLayerIPAdapterAdded,
|
||||
ipAdapterAdded,
|
||||
regionalGuidanceIPAdapterAdded,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isInitialImageLayer } from 'features/controlLayers/store/types';
|
||||
import {
|
||||
@ -46,7 +46,7 @@ export const useAddCALayer = () => {
|
||||
processorConfig,
|
||||
});
|
||||
|
||||
dispatch(caLayerAdded(controlAdapter));
|
||||
dispatch(controlAdapterAdded(controlAdapter));
|
||||
}, [dispatch, model, baseModel]);
|
||||
|
||||
return [addCALayer, isDisabled] as const;
|
||||
@ -70,7 +70,7 @@ export const useAddIPALayer = () => {
|
||||
const ipAdapter = buildIPAdapter(id, {
|
||||
model: zModelIdentifierField.parse(model),
|
||||
});
|
||||
dispatch(ipaLayerAdded(ipAdapter));
|
||||
dispatch(ipAdapterAdded(ipAdapter));
|
||||
}, [dispatch, model]);
|
||||
|
||||
return [addIPALayer, isDisabled] as const;
|
||||
@ -94,7 +94,7 @@ export const useAddIPAdapterToIPALayer = (layerId: string) => {
|
||||
const ipAdapter = buildIPAdapter(id, {
|
||||
model: zModelIdentifierField.parse(model),
|
||||
});
|
||||
dispatch(rgLayerIPAdapterAdded({ layerId, ipAdapter }));
|
||||
dispatch(regionalGuidanceIPAdapterAdded({ layerId, ipAdapter }));
|
||||
}, [dispatch, model, layerId]);
|
||||
|
||||
return [addIPAdapter, isDisabled] as const;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { isControlAdapterLayer, isRegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
||||
import { useMemo } from 'react';
|
||||
import { assert } from 'tsafe';
|
||||
@ -9,7 +9,7 @@ import { assert } from 'tsafe';
|
||||
export const useLayerPositivePrompt = (layerId: string) => {
|
||||
const selectLayer = useMemo(
|
||||
() =>
|
||||
createSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
assert(layer.positivePrompt !== null, `Layer ${layerId} does not have a positive prompt`);
|
||||
@ -24,7 +24,7 @@ export const useLayerPositivePrompt = (layerId: string) => {
|
||||
export const useLayerNegativePrompt = (layerId: string) => {
|
||||
const selectLayer = useMemo(
|
||||
() =>
|
||||
createSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
assert(isRegionalGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`);
|
||||
assert(layer.negativePrompt !== null, `Layer ${layerId} does not have a negative prompt`);
|
||||
@ -39,7 +39,7 @@ export const useLayerNegativePrompt = (layerId: string) => {
|
||||
export const useLayerIsEnabled = (layerId: string) => {
|
||||
const selectLayer = useMemo(
|
||||
() =>
|
||||
createSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
assert(layer, `Layer ${layerId} not found`);
|
||||
return layer.isEnabled;
|
||||
@ -53,7 +53,7 @@ export const useLayerIsEnabled = (layerId: string) => {
|
||||
export const useLayerType = (layerId: string) => {
|
||||
const selectLayer = useMemo(
|
||||
() =>
|
||||
createSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
createSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.find((l) => l.id === layerId);
|
||||
assert(layer, `Layer ${layerId} not found`);
|
||||
return layer.type;
|
||||
@ -67,7 +67,7 @@ export const useLayerType = (layerId: string) => {
|
||||
export const useCALayerOpacity = (layerId: string) => {
|
||||
const selectLayer = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
createMemoizedSelector(selectCanvasV2Slice, (controlLayers) => {
|
||||
const layer = controlLayers.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
|
||||
assert(layer, `Layer ${layerId} not found`);
|
||||
return { opacity: Math.round(layer.opacity * 100), isFilterEnabled: layer.isFilterEnabled };
|
||||
|
@ -6,7 +6,7 @@ import type {
|
||||
AddEraserLineArg,
|
||||
AddPointToLineArg,
|
||||
AddRectShapeArg,
|
||||
Layer,
|
||||
LayerData,
|
||||
StageAttrs,
|
||||
Tool,
|
||||
} from 'features/controlLayers/store/types';
|
||||
@ -38,7 +38,7 @@ type Arg = {
|
||||
getBrushColor: () => RgbaColor;
|
||||
getBrushSize: () => number;
|
||||
getBrushSpacingPx: () => number;
|
||||
getSelectedLayer: () => Layer | null;
|
||||
getSelectedLayer: () => LayerData | null;
|
||||
getShouldInvert: () => boolean;
|
||||
getSpaceKey: () => boolean;
|
||||
onBrushLineAdded: (arg: AddBrushLineArg) => void;
|
||||
@ -72,7 +72,7 @@ const updateLastCursorPos = (stage: Konva.Stage, setLastCursorPos: Arg['setLastC
|
||||
* @param onPointAddedToLine The callback to add a point to a line
|
||||
*/
|
||||
const maybeAddNextPoint = (
|
||||
layerId: string,
|
||||
selectedLayer: LayerData,
|
||||
currentPos: Vector2d,
|
||||
getLastAddedPoint: Arg['getLastAddedPoint'],
|
||||
setLastAddedPoint: Arg['setLastAddedPoint'],
|
||||
@ -88,7 +88,7 @@ const maybeAddNextPoint = (
|
||||
}
|
||||
}
|
||||
setLastAddedPoint(currentPos);
|
||||
onPointAddedToLine({ layerId, point: [currentPos.x, currentPos.y] });
|
||||
onPointAddedToLine({ layerId, point: [currentPos.x - selectedLayer.x, currentPos.y - selectedLayer.y] });
|
||||
};
|
||||
|
||||
export const setStageEventHandlers = ({
|
||||
@ -158,7 +158,7 @@ export const setStageEventHandlers = ({
|
||||
if (tool === 'brush') {
|
||||
onBrushLineAdded({
|
||||
layerId: selectedLayer.id,
|
||||
points: [pos.x, pos.y, pos.x, pos.y],
|
||||
points: [pos.x - selectedLayer.x, pos.y - selectedLayer.y, pos.x - selectedLayer.x, pos.y - selectedLayer.y],
|
||||
color: selectedLayer.type === 'raster_layer' ? getBrushColor() : DEFAULT_RGBA_COLOR,
|
||||
});
|
||||
}
|
||||
@ -166,7 +166,7 @@ export const setStageEventHandlers = ({
|
||||
if (tool === 'eraser') {
|
||||
onEraserLineAdded({
|
||||
layerId: selectedLayer.id,
|
||||
points: [pos.x, pos.y, pos.x, pos.y],
|
||||
points: [pos.x - selectedLayer.x, pos.y - selectedLayer.y, pos.x - selectedLayer.x, pos.y - selectedLayer.y],
|
||||
});
|
||||
}
|
||||
|
||||
@ -262,7 +262,7 @@ export const setStageEventHandlers = ({
|
||||
// Start a new line
|
||||
onBrushLineAdded({
|
||||
layerId: selectedLayer.id,
|
||||
points: [pos.x, pos.y, pos.x, pos.y],
|
||||
points: [pos.x - selectedLayer.x, pos.y - selectedLayer.y, pos.x - selectedLayer.x, pos.y - selectedLayer.y],
|
||||
color: selectedLayer.type === 'raster_layer' ? getBrushColor() : DEFAULT_RGBA_COLOR,
|
||||
});
|
||||
setIsDrawing(true);
|
||||
@ -282,7 +282,10 @@ export const setStageEventHandlers = ({
|
||||
);
|
||||
} else {
|
||||
// Start a new line
|
||||
onEraserLineAdded({ layerId: selectedLayer.id, points: [pos.x, pos.y, pos.x, pos.y] });
|
||||
onEraserLineAdded({
|
||||
layerId: selectedLayer.id,
|
||||
points: [pos.x - selectedLayer.x, pos.y - selectedLayer.y, pos.x - selectedLayer.x, pos.y - selectedLayer.y],
|
||||
});
|
||||
setIsDrawing(true);
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,8 @@ export const RASTER_LAYER_ERASER_LINE_NAME = 'raster_layer.eraser_line';
|
||||
export const RASTER_LAYER_RECT_SHAPE_NAME = 'raster_layer.rect_shape';
|
||||
export const RASTER_LAYER_IMAGE_NAME = 'raster_layer.image';
|
||||
|
||||
export const INPAINT_MASK_LAYER_NAME = 'inpaint_mask_layer';
|
||||
|
||||
// Getters for non-singleton layer and object IDs
|
||||
export const getRGLayerId = (layerId: string) => `${RG_LAYER_NAME}_${layerId}`;
|
||||
export const getRasterLayerId = (layerId: string) => `${RASTER_LAYER_NAME}_${layerId}`;
|
||||
|
@ -6,8 +6,8 @@ import {
|
||||
RG_LAYER_OBJECT_GROUP_NAME,
|
||||
} from 'features/controlLayers/konva/naming';
|
||||
import { createBboxRect } from 'features/controlLayers/konva/renderers/objects';
|
||||
import type { Layer } from 'features/controlLayers/store/types';
|
||||
import { isRegionalGuidanceLayer, isRGOrRasterlayer } from 'features/controlLayers/store/types';
|
||||
import type { LayerData } from 'features/controlLayers/store/types';
|
||||
import { isRegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { IRect } from 'konva/lib/types';
|
||||
import { assert } from 'tsafe';
|
||||
@ -185,10 +185,10 @@ const filterRasterChildren = (node: Konva.Node): boolean => node.name() === RAST
|
||||
*/
|
||||
export const updateBboxes = (
|
||||
stage: Konva.Stage,
|
||||
layerStates: Layer[],
|
||||
layerStates: LayerData[],
|
||||
onBboxChanged: (layerId: string, bbox: IRect | null) => void
|
||||
): void => {
|
||||
for (const layerState of layerStates.filter(isRGOrRasterlayer)) {
|
||||
for (const layerState of layerStates) {
|
||||
const konvaLayer = stage.findOne<Konva.Layer>(`#${layerState.id}`);
|
||||
assert(konvaLayer, `Layer ${layerState.id} not found in stage`);
|
||||
// We only need to recalculate the bbox if the layer has changed
|
||||
|
@ -7,10 +7,11 @@ import { renderBboxPreview, renderToolPreview } from 'features/controlLayers/kon
|
||||
import { renderRasterLayer } from 'features/controlLayers/konva/renderers/rasterLayer';
|
||||
import { renderRGLayer } from 'features/controlLayers/konva/renderers/rgLayer';
|
||||
import { mapId, selectRenderableLayers } from 'features/controlLayers/konva/util';
|
||||
import type { Layer, Tool } from 'features/controlLayers/store/types';
|
||||
import type { LayerData, Tool } from 'features/controlLayers/store/types';
|
||||
import {
|
||||
isControlAdapterLayer,
|
||||
isInitialImageLayer,
|
||||
isInpaintMaskLayer,
|
||||
isRasterLayer,
|
||||
isRegionalGuidanceLayer,
|
||||
isRenderableLayer,
|
||||
@ -34,7 +35,7 @@ import type { ImageDTO } from 'services/api/types';
|
||||
*/
|
||||
const renderLayers = (
|
||||
stage: Konva.Stage,
|
||||
layerStates: Layer[],
|
||||
layerStates: LayerData[],
|
||||
globalMaskLayerOpacity: number,
|
||||
tool: Tool,
|
||||
getImageDTO: (imageName: string) => Promise<ImageDTO | null>,
|
||||
@ -52,15 +53,14 @@ const renderLayers = (
|
||||
for (const layer of layerStates) {
|
||||
if (isRegionalGuidanceLayer(layer)) {
|
||||
renderRGLayer(stage, layer, globalMaskLayerOpacity, tool, zIndex, onLayerPosChanged);
|
||||
}
|
||||
if (isControlAdapterLayer(layer)) {
|
||||
} else if (isControlAdapterLayer(layer)) {
|
||||
renderCALayer(stage, layer, zIndex, getImageDTO);
|
||||
}
|
||||
if (isInitialImageLayer(layer)) {
|
||||
} else if (isInitialImageLayer(layer)) {
|
||||
renderIILayer(stage, layer, zIndex, getImageDTO);
|
||||
}
|
||||
if (isRasterLayer(layer)) {
|
||||
} else if (isRasterLayer(layer)) {
|
||||
renderRasterLayer(stage, layer, tool, zIndex, onLayerPosChanged);
|
||||
} else if (isInpaintMaskLayer(layer)) {
|
||||
//
|
||||
}
|
||||
// IP Adapter layers are not rendered
|
||||
// Increment the z-index for the tool layer
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
LAYER_BBOX_NAME,
|
||||
PREVIEW_GENERATION_BBOX_DUMMY_RECT,
|
||||
} from 'features/controlLayers/konva/naming';
|
||||
import type { BrushLine, EraserLine, ImageObject, Layer, RectShape } from 'features/controlLayers/store/types';
|
||||
import type { BrushLine, EraserLine, ImageObject, LayerData, RectShape } from 'features/controlLayers/store/types';
|
||||
import { DEFAULT_RGBA_COLOR } from 'features/controlLayers/store/types';
|
||||
import { t } from 'i18next';
|
||||
import Konva from 'konva';
|
||||
@ -177,7 +177,7 @@ export const createImageObjectGroup = async (
|
||||
* @param layerState The layer state for the layer to create the bounding box for
|
||||
* @param konvaLayer The konva layer to attach the bounding box to
|
||||
*/
|
||||
export const createBboxRect = (layerState: Layer, konvaLayer: Konva.Layer): Konva.Rect => {
|
||||
export const createBboxRect = (layerState: LayerData, konvaLayer: Konva.Layer): Konva.Rect => {
|
||||
const rect = new Konva.Rect({
|
||||
id: getLayerBboxId(layerState.id),
|
||||
name: LAYER_BBOX_NAME,
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
PREVIEW_TOOL_GROUP_ID,
|
||||
} from 'features/controlLayers/konva/naming';
|
||||
import { selectRenderableLayers } from 'features/controlLayers/konva/util';
|
||||
import type { Layer, RgbaColor, Tool } from 'features/controlLayers/store/types';
|
||||
import type { LayerData, RgbaColor, Tool } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||
import { atom } from 'nanostores';
|
||||
@ -338,7 +338,7 @@ export const renderToolPreview = (
|
||||
stage: Konva.Stage,
|
||||
tool: Tool,
|
||||
brushColor: RgbaColor,
|
||||
selectedLayerType: Layer['type'] | null,
|
||||
selectedLayerType: LayerData['type'] | null,
|
||||
globalMaskLayerOpacity: number,
|
||||
cursorPos: Vector2d | null,
|
||||
lastMouseDownPos: Vector2d | null,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {
|
||||
CA_LAYER_NAME,
|
||||
INITIAL_IMAGE_LAYER_NAME,
|
||||
INPAINT_MASK_LAYER_NAME,
|
||||
RASTER_LAYER_BRUSH_LINE_NAME,
|
||||
RASTER_LAYER_ERASER_LINE_NAME,
|
||||
RASTER_LAYER_IMAGE_NAME,
|
||||
@ -98,7 +99,8 @@ export const selectRenderableLayers = (node: Konva.Node): boolean =>
|
||||
node.name() === RG_LAYER_NAME ||
|
||||
node.name() === CA_LAYER_NAME ||
|
||||
node.name() === INITIAL_IMAGE_LAYER_NAME ||
|
||||
node.name() === RASTER_LAYER_NAME;
|
||||
node.name() === RASTER_LAYER_NAME ||
|
||||
node.name() === INPAINT_MASK_LAYER_NAME;
|
||||
|
||||
/**
|
||||
* Konva selection callback to select RG mask objects. This includes lines and rects.
|
||||
|
@ -0,0 +1,282 @@
|
||||
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';
|
||||
|
||||
type ControlAdaptersV2State = {
|
||||
_version: 1;
|
||||
controlAdapters: ControlAdapterData[];
|
||||
};
|
||||
|
||||
const initialState: ControlAdaptersV2State = {
|
||||
_version: 1,
|
||||
controlAdapters: [],
|
||||
};
|
||||
|
||||
const selectCa = (state: ControlAdaptersV2State, id: string) => state.controlAdapters.find((ca) => ca.id === id);
|
||||
|
||||
export const controlAdaptersV2Slice = createSlice({
|
||||
name: 'controlAdaptersV2',
|
||||
initialState,
|
||||
reducers: {
|
||||
caAdded: {
|
||||
reducer: (state, action: PayloadAction<{ id: string; config: ControlAdapterConfig }>) => {
|
||||
const { id, config } = action.payload;
|
||||
state.controlAdapters.push({
|
||||
id,
|
||||
type: 'control_adapter',
|
||||
x: 0,
|
||||
y: 0,
|
||||
bbox: null,
|
||||
bboxNeedsUpdate: false,
|
||||
isEnabled: true,
|
||||
opacity: 1,
|
||||
filter: 'lightness_to_alpha',
|
||||
processorPendingBatchId: null,
|
||||
...config,
|
||||
});
|
||||
},
|
||||
prepare: (config: ControlAdapterConfig) => ({
|
||||
payload: { id: uuidv4(), config },
|
||||
}),
|
||||
},
|
||||
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);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.isEnabled = isEnabled;
|
||||
},
|
||||
caTranslated: (state, action: PayloadAction<{ id: string; x: number; y: number }>) => {
|
||||
const { id, x, y } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.x = x;
|
||||
ca.y = y;
|
||||
},
|
||||
caBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => {
|
||||
const { id, bbox } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.bbox = bbox;
|
||||
ca.bboxNeedsUpdate = false;
|
||||
},
|
||||
caDeleted: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
state.controlAdapters = state.controlAdapters.filter((ca) => ca.id !== id);
|
||||
},
|
||||
caOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => {
|
||||
const { id, opacity } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.opacity = opacity;
|
||||
},
|
||||
caMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
moveOneToEnd(state.controlAdapters, ca);
|
||||
},
|
||||
caMovedToFront: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
moveToEnd(state.controlAdapters, ca);
|
||||
},
|
||||
caMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
moveOneToStart(state.controlAdapters, ca);
|
||||
},
|
||||
caMovedToBack: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
moveToStart(state.controlAdapters, ca);
|
||||
},
|
||||
caImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => {
|
||||
const { id, imageDTO } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.bbox = null;
|
||||
ca.bboxNeedsUpdate = true;
|
||||
ca.isEnabled = true;
|
||||
if (imageDTO) {
|
||||
const newImage = imageDTOToImageWithDims(imageDTO);
|
||||
if (isEqual(newImage, ca.image)) {
|
||||
return;
|
||||
}
|
||||
ca.image = newImage;
|
||||
ca.processedImage = null;
|
||||
} else {
|
||||
ca.image = null;
|
||||
ca.processedImage = null;
|
||||
}
|
||||
},
|
||||
caProcessedImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => {
|
||||
const { id, imageDTO } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.bbox = null;
|
||||
ca.bboxNeedsUpdate = true;
|
||||
ca.isEnabled = true;
|
||||
ca.processedImage = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||
},
|
||||
caModelChanged: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
id: string;
|
||||
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | null;
|
||||
}>
|
||||
) => {
|
||||
const { id, modelConfig } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
if (!modelConfig) {
|
||||
ca.model = null;
|
||||
return;
|
||||
}
|
||||
ca.model = zModelIdentifierField.parse(modelConfig);
|
||||
|
||||
// We may need to convert the CA to match the model
|
||||
if (!ca.controlMode && ca.model.type === 'controlnet') {
|
||||
ca.controlMode = 'balanced';
|
||||
} else if (ca.controlMode && ca.model.type === 't2i_adapter') {
|
||||
ca.controlMode = null;
|
||||
}
|
||||
|
||||
const candidateProcessorConfig = buildControlAdapterProcessorV2(modelConfig);
|
||||
if (candidateProcessorConfig?.type !== ca.processorConfig?.type) {
|
||||
// The processor has changed. For example, the previous model was a Canny model and the new model is a Depth
|
||||
// model. We need to use the new processor.
|
||||
ca.processedImage = null;
|
||||
ca.processorConfig = candidateProcessorConfig;
|
||||
}
|
||||
},
|
||||
caControlModeChanged: (state, action: PayloadAction<{ id: string; controlMode: ControlModeV2 }>) => {
|
||||
const { id, controlMode } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.controlMode = controlMode;
|
||||
},
|
||||
caProcessorConfigChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ id: string; processorConfig: ProcessorConfig | null }>
|
||||
) => {
|
||||
const { id, processorConfig } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.processorConfig = processorConfig;
|
||||
if (!processorConfig) {
|
||||
ca.processedImage = null;
|
||||
}
|
||||
},
|
||||
caFilterChanged: (state, action: PayloadAction<{ id: string; filter: Filter }>) => {
|
||||
const { id, filter } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.filter = filter;
|
||||
},
|
||||
caProcessorPendingBatchIdChanged: (state, action: PayloadAction<{ id: string; batchId: string | null }>) => {
|
||||
const { id, batchId } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.processorPendingBatchId = batchId;
|
||||
},
|
||||
caWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => {
|
||||
const { id, weight } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.weight = weight;
|
||||
},
|
||||
caBeginEndStepPctChanged: (state, action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }>) => {
|
||||
const { id, beginEndStepPct } = action.payload;
|
||||
const ca = selectCa(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.beginEndStepPct = beginEndStepPct;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
caAdded,
|
||||
caBboxChanged,
|
||||
caDeleted,
|
||||
caIsEnabledChanged,
|
||||
caMovedBackwardOne,
|
||||
caMovedForwardOne,
|
||||
caMovedToBack,
|
||||
caMovedToFront,
|
||||
caOpacityChanged,
|
||||
caTranslated,
|
||||
caRecalled,
|
||||
caImageChanged,
|
||||
caProcessedImageChanged,
|
||||
caModelChanged,
|
||||
caControlModeChanged,
|
||||
caProcessorConfigChanged,
|
||||
caFilterChanged,
|
||||
caProcessorPendingBatchIdChanged,
|
||||
caWeightChanged,
|
||||
caBeginEndStepPctChanged,
|
||||
} = controlAdaptersV2Slice.actions;
|
||||
|
||||
export const selectControlAdaptersV2Slice = (state: RootState) => state.controlAdaptersV2;
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
const migrate = (state: any): any => {
|
||||
return state;
|
||||
};
|
||||
|
||||
export const controlAdaptersV2PersistConfig: PersistConfig<ControlAdaptersV2State> = {
|
||||
name: controlAdaptersV2Slice.name,
|
||||
initialState,
|
||||
migrate,
|
||||
persistDenylist: [],
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,140 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/util/controlAdapters';
|
||||
import { imageDTOToImageWithDims } from 'features/controlLayers/util/controlAdapters';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import type { IPAdapterConfig, IPAdapterData } from './types';
|
||||
|
||||
type IPAdaptersState = {
|
||||
_version: 1;
|
||||
ipAdapters: IPAdapterData[];
|
||||
};
|
||||
|
||||
const initialState: IPAdaptersState = {
|
||||
_version: 1,
|
||||
ipAdapters: [],
|
||||
};
|
||||
|
||||
const selectIpa = (state: IPAdaptersState, id: string) => state.ipAdapters.find((ipa) => ipa.id === id);
|
||||
|
||||
export const ipAdaptersSlice = createSlice({
|
||||
name: 'ipAdapters',
|
||||
initialState,
|
||||
reducers: {
|
||||
ipaAdded: {
|
||||
reducer: (state, action: PayloadAction<{ id: string; config: IPAdapterConfig }>) => {
|
||||
const { id, config } = action.payload;
|
||||
const layer: IPAdapterData = {
|
||||
id,
|
||||
type: 'ip_adapter',
|
||||
isEnabled: true,
|
||||
...config,
|
||||
};
|
||||
state.ipAdapters.push(layer);
|
||||
},
|
||||
prepare: (config: IPAdapterConfig) => ({ payload: { id: uuidv4(), config } }),
|
||||
},
|
||||
ipaRecalled: (state, action: PayloadAction<{ data: IPAdapterData }>) => {
|
||||
state.ipAdapters.push(action.payload.data);
|
||||
},
|
||||
ipaIsEnabledChanged: (state, action: PayloadAction<{ id: string; isEnabled: boolean }>) => {
|
||||
const { id, isEnabled } = action.payload;
|
||||
const ipa = selectIpa(state, id);
|
||||
if (ipa) {
|
||||
ipa.isEnabled = isEnabled;
|
||||
}
|
||||
},
|
||||
ipaDeleted: (state, action: PayloadAction<{ id: string }>) => {
|
||||
state.ipAdapters = state.ipAdapters.filter((ipa) => ipa.id !== action.payload.id);
|
||||
},
|
||||
ipaImageChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => {
|
||||
const { id, imageDTO } = action.payload;
|
||||
const ipa = selectIpa(state, id);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
ipa.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||
},
|
||||
ipaMethodChanged: (state, action: PayloadAction<{ id: string; method: IPMethodV2 }>) => {
|
||||
const { id, method } = action.payload;
|
||||
const ipa = selectIpa(state, id);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
ipa.method = method;
|
||||
},
|
||||
ipaModelChanged: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
id: string;
|
||||
modelConfig: IPAdapterModelConfig | null;
|
||||
}>
|
||||
) => {
|
||||
const { id, modelConfig } = action.payload;
|
||||
const ipa = selectIpa(state, id);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
if (modelConfig) {
|
||||
ipa.model = zModelIdentifierField.parse(modelConfig);
|
||||
} else {
|
||||
ipa.model = null;
|
||||
}
|
||||
},
|
||||
ipaCLIPVisionModelChanged: (state, action: PayloadAction<{ id: string; clipVisionModel: CLIPVisionModelV2 }>) => {
|
||||
const { id, clipVisionModel } = action.payload;
|
||||
const ipa = selectIpa(state, id);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
ipa.clipVisionModel = clipVisionModel;
|
||||
},
|
||||
ipaWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => {
|
||||
const { id, weight } = action.payload;
|
||||
const ipa = selectIpa(state, id);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
ipa.weight = weight;
|
||||
},
|
||||
ipaBeginEndStepPctChanged: (state, action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }>) => {
|
||||
const { id, beginEndStepPct } = action.payload;
|
||||
const ipa = selectIpa(state, id);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
ipa.beginEndStepPct = beginEndStepPct;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
ipaAdded,
|
||||
ipaRecalled,
|
||||
ipaIsEnabledChanged,
|
||||
ipaDeleted,
|
||||
ipaImageChanged,
|
||||
ipaMethodChanged,
|
||||
ipaModelChanged,
|
||||
ipaCLIPVisionModelChanged,
|
||||
ipaWeightChanged,
|
||||
ipaBeginEndStepPctChanged,
|
||||
} = ipAdaptersSlice.actions;
|
||||
|
||||
export const selectIPAdaptersSlice = (state: RootState) => state.ipAdapters;
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
const migrate = (state: any): any => {
|
||||
return state;
|
||||
};
|
||||
|
||||
export const ipAdaptersPersistConfig: PersistConfig<IPAdaptersState> = {
|
||||
name: ipAdaptersSlice.name,
|
||||
initialState,
|
||||
migrate,
|
||||
persistDenylist: [],
|
||||
};
|
@ -0,0 +1,268 @@
|
||||
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 { getBrushLineId, getEraserLineId, getImageObjectId, getRectShapeId } from 'features/controlLayers/konva/naming';
|
||||
import type { IRect } from 'konva/lib/types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import type {
|
||||
AddBrushLineArg,
|
||||
AddEraserLineArg,
|
||||
AddImageObjectArg,
|
||||
AddPointToLineArg,
|
||||
AddRectShapeArg,
|
||||
LayerData,
|
||||
} from './types';
|
||||
import { isLine } from './types';
|
||||
|
||||
type LayersState = {
|
||||
_version: 1;
|
||||
layers: LayerData[];
|
||||
};
|
||||
|
||||
const initialState: LayersState = { _version: 1, layers: [] };
|
||||
const selectLayer = (state: LayersState, id: string) => state.layers.find((layer) => layer.id === id);
|
||||
|
||||
export const layersSlice = createSlice({
|
||||
name: 'layers',
|
||||
initialState,
|
||||
reducers: {
|
||||
layerAdded: {
|
||||
reducer: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
state.layers.push({
|
||||
id,
|
||||
type: 'layer',
|
||||
isEnabled: true,
|
||||
bbox: null,
|
||||
bboxNeedsUpdate: false,
|
||||
objects: [],
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
},
|
||||
prepare: () => ({ payload: { id: uuidv4() } }),
|
||||
},
|
||||
layerRecalled: (state, action: PayloadAction<{ data: LayerData }>) => {
|
||||
state.layers.push(action.payload.data);
|
||||
},
|
||||
layerIsEnabledChanged: (state, action: PayloadAction<{ id: string; isEnabled: boolean }>) => {
|
||||
const { id, isEnabled } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
layer.isEnabled = isEnabled;
|
||||
},
|
||||
layerTranslated: (state, action: PayloadAction<{ id: string; x: number; y: number }>) => {
|
||||
const { id, x, y } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
layer.x = x;
|
||||
layer.y = y;
|
||||
},
|
||||
layerBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => {
|
||||
const { id, bbox } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
layer.bbox = bbox;
|
||||
layer.bboxNeedsUpdate = false;
|
||||
if (bbox === null) {
|
||||
layer.objects = [];
|
||||
}
|
||||
},
|
||||
layerReset: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
layer.isEnabled = true;
|
||||
layer.objects = [];
|
||||
layer.bbox = null;
|
||||
layer.bboxNeedsUpdate = false;
|
||||
},
|
||||
layerDeleted: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
state.layers = state.layers.filter((l) => l.id !== id);
|
||||
},
|
||||
layerOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => {
|
||||
const { id, opacity } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
layer.opacity = opacity;
|
||||
},
|
||||
layerMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
moveOneToEnd(state.layers, layer);
|
||||
},
|
||||
layerMovedToFront: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
moveToEnd(state.layers, layer);
|
||||
},
|
||||
layerMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
moveOneToStart(state.layers, layer);
|
||||
},
|
||||
layerMovedToBack: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
moveToStart(state.layers, layer);
|
||||
},
|
||||
layerBrushLineAdded: {
|
||||
reducer: (state, action: PayloadAction<AddBrushLineArg & { lineId: string }>) => {
|
||||
const { id, points, lineId, color, width } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
layer.objects.push({
|
||||
id: getBrushLineId(id, lineId),
|
||||
type: 'brush_line',
|
||||
points,
|
||||
strokeWidth: width,
|
||||
color,
|
||||
});
|
||||
layer.bboxNeedsUpdate = true;
|
||||
},
|
||||
prepare: (payload: AddBrushLineArg) => ({
|
||||
payload: { ...payload, lineId: uuidv4() },
|
||||
}),
|
||||
},
|
||||
layerEraserLineAdded: {
|
||||
reducer: (state, action: PayloadAction<AddEraserLineArg & { lineId: string }>) => {
|
||||
const { id, points, lineId, width } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
layer.objects.push({
|
||||
id: getEraserLineId(id, lineId),
|
||||
type: 'eraser_line',
|
||||
points,
|
||||
strokeWidth: width,
|
||||
});
|
||||
layer.bboxNeedsUpdate = true;
|
||||
},
|
||||
prepare: (payload: AddEraserLineArg) => ({
|
||||
payload: { ...payload, lineId: uuidv4() },
|
||||
}),
|
||||
},
|
||||
layerLinePointAdded: (state, action: PayloadAction<AddPointToLineArg>) => {
|
||||
const { id, point } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
const lastObject = layer.objects[layer.objects.length - 1];
|
||||
if (!lastObject || !isLine(lastObject)) {
|
||||
return;
|
||||
}
|
||||
lastObject.points.push(...point);
|
||||
layer.bboxNeedsUpdate = true;
|
||||
},
|
||||
layerRectAdded: {
|
||||
reducer: (state, action: PayloadAction<AddRectShapeArg & { rectId: string }>) => {
|
||||
const { id, rect, rectId, color } = action.payload;
|
||||
if (rect.height === 0 || rect.width === 0) {
|
||||
// Ignore zero-area rectangles
|
||||
return;
|
||||
}
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
layer.objects.push({
|
||||
type: 'rect_shape',
|
||||
id: getRectShapeId(id, rectId),
|
||||
x: rect.x - layer.x,
|
||||
y: rect.y - layer.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
color,
|
||||
});
|
||||
layer.bboxNeedsUpdate = true;
|
||||
},
|
||||
prepare: (payload: AddRectShapeArg) => ({ payload: { ...payload, rectId: uuidv4() } }),
|
||||
},
|
||||
layerImageAdded: {
|
||||
reducer: (state, action: PayloadAction<AddImageObjectArg & { imageId: string }>) => {
|
||||
const { id, imageId, imageDTO } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
const { width, height, image_name: name } = imageDTO;
|
||||
layer.objects.push({
|
||||
type: 'image',
|
||||
id: getImageObjectId(id, imageId),
|
||||
x: 0,
|
||||
y: 0,
|
||||
width,
|
||||
height,
|
||||
image: { width, height, name },
|
||||
});
|
||||
layer.bboxNeedsUpdate = true;
|
||||
},
|
||||
prepare: (payload: AddImageObjectArg) => ({ payload: { ...payload, imageId: uuidv4() } }),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
layerAdded,
|
||||
layerDeleted,
|
||||
layerReset,
|
||||
layerMovedForwardOne,
|
||||
layerMovedToFront,
|
||||
layerMovedBackwardOne,
|
||||
layerMovedToBack,
|
||||
layerIsEnabledChanged,
|
||||
layerOpacityChanged,
|
||||
layerTranslated,
|
||||
layerBboxChanged,
|
||||
layerBrushLineAdded,
|
||||
layerEraserLineAdded,
|
||||
layerLinePointAdded,
|
||||
layerRectAdded,
|
||||
layerImageAdded,
|
||||
} = layersSlice.actions;
|
||||
|
||||
export const selectLayersSlice = (state: RootState) => state.layers;
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
const migrate = (state: any): any => {
|
||||
return state;
|
||||
};
|
||||
|
||||
export const layersPersistConfig: PersistConfig<LayersState> = {
|
||||
name: layersSlice.name,
|
||||
initialState,
|
||||
migrate,
|
||||
persistDenylist: [],
|
||||
};
|
@ -0,0 +1,440 @@
|
||||
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 { 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 { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas';
|
||||
import type { IRect } from 'konva/lib/types';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import type {
|
||||
AddBrushLineArg,
|
||||
AddEraserLineArg,
|
||||
AddPointToLineArg,
|
||||
AddRectShapeArg,
|
||||
IPAdapterData,
|
||||
RegionalGuidanceData,
|
||||
RgbColor,
|
||||
} from './types';
|
||||
import { isLine } from './types';
|
||||
|
||||
type RegionalGuidanceState = {
|
||||
_version: 1;
|
||||
regions: RegionalGuidanceData[];
|
||||
opacity: number;
|
||||
};
|
||||
|
||||
const initialState: RegionalGuidanceState = {
|
||||
_version: 1,
|
||||
regions: [],
|
||||
opacity: 0.3,
|
||||
};
|
||||
|
||||
const selectRg = (state: RegionalGuidanceState, id: string) => state.regions.find((rg) => rg.id === id);
|
||||
|
||||
const DEFAULT_MASK_COLORS: RgbColor[] = [
|
||||
{ r: 121, g: 157, b: 219 }, // rgb(121, 157, 219)
|
||||
{ r: 131, g: 214, b: 131 }, // rgb(131, 214, 131)
|
||||
{ r: 250, g: 225, b: 80 }, // rgb(250, 225, 80)
|
||||
{ r: 220, g: 144, b: 101 }, // rgb(220, 144, 101)
|
||||
{ r: 224, g: 117, b: 117 }, // rgb(224, 117, 117)
|
||||
{ r: 213, g: 139, b: 202 }, // rgb(213, 139, 202)
|
||||
{ r: 161, g: 120, b: 214 }, // rgb(161, 120, 214)
|
||||
];
|
||||
|
||||
const getRGMaskFill = (state: RegionalGuidanceState): RgbColor => {
|
||||
const lastFill = state.regions.slice(-1)[0]?.fill;
|
||||
let i = DEFAULT_MASK_COLORS.findIndex((c) => isEqual(c, lastFill));
|
||||
if (i === -1) {
|
||||
i = 0;
|
||||
}
|
||||
i = (i + 1) % DEFAULT_MASK_COLORS.length;
|
||||
const fill = DEFAULT_MASK_COLORS[i];
|
||||
assert(fill, 'This should never happen');
|
||||
return fill;
|
||||
};
|
||||
|
||||
export const regionalGuidanceSlice = createSlice({
|
||||
name: 'regionalGuidance',
|
||||
initialState,
|
||||
reducers: {
|
||||
rgAdded: {
|
||||
reducer: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const rg: RegionalGuidanceData = {
|
||||
id,
|
||||
type: 'regional_guidance',
|
||||
isEnabled: true,
|
||||
bbox: null,
|
||||
bboxNeedsUpdate: false,
|
||||
objects: [],
|
||||
fill: getRGMaskFill(state),
|
||||
x: 0,
|
||||
y: 0,
|
||||
autoNegative: 'invert',
|
||||
positivePrompt: '',
|
||||
negativePrompt: null,
|
||||
ipAdapters: [],
|
||||
imageCache: null,
|
||||
};
|
||||
state.regions.push(rg);
|
||||
},
|
||||
prepare: () => ({ payload: { id: uuidv4() } }),
|
||||
},
|
||||
rgRecalled: (state, action: PayloadAction<{ data: RegionalGuidanceData }>) => {
|
||||
const { data } = action.payload;
|
||||
state.regions.push(data);
|
||||
},
|
||||
rgIsEnabledToggled: (state, action: PayloadAction<{ id: string; isEnabled: boolean }>) => {
|
||||
const { id, isEnabled } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (rg) {
|
||||
rg.isEnabled = isEnabled;
|
||||
}
|
||||
},
|
||||
rgTranslated: (state, action: PayloadAction<{ id: string; x: number; y: number }>) => {
|
||||
const { id, x, y } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (rg) {
|
||||
rg.x = x;
|
||||
rg.y = y;
|
||||
}
|
||||
},
|
||||
rgBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => {
|
||||
const { id, bbox } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (rg) {
|
||||
rg.bbox = bbox;
|
||||
rg.bboxNeedsUpdate = false;
|
||||
}
|
||||
},
|
||||
rgDeleted: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
state.regions = state.regions.filter((ca) => ca.id !== id);
|
||||
},
|
||||
rgGlobalOpacityChanged: (state, action: PayloadAction<{ opacity: number }>) => {
|
||||
const { opacity } = action.payload;
|
||||
state.opacity = opacity;
|
||||
},
|
||||
rgMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
moveOneToEnd(state.regions, rg);
|
||||
},
|
||||
rgMovedToFront: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
moveToEnd(state.regions, rg);
|
||||
},
|
||||
rgMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
moveOneToStart(state.regions, rg);
|
||||
},
|
||||
rgMovedToBack: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
moveToStart(state.regions, rg);
|
||||
},
|
||||
rgPositivePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => {
|
||||
const { id, prompt } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
rg.positivePrompt = prompt;
|
||||
},
|
||||
rgNegativePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => {
|
||||
const { id, prompt } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
rg.negativePrompt = prompt;
|
||||
},
|
||||
rgFillChanged: (state, action: PayloadAction<{ id: string; fill: RgbColor }>) => {
|
||||
const { id, fill } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
rg.fill = fill;
|
||||
},
|
||||
rgMaskImageUploaded: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO }>) => {
|
||||
const { id, imageDTO } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
rg.imageCache = imageDTOToImageWithDims(imageDTO);
|
||||
},
|
||||
rgAutoNegativeChanged: (state, action: PayloadAction<{ id: string; autoNegative: ParameterAutoNegative }>) => {
|
||||
const { id, autoNegative } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
rg.autoNegative = autoNegative;
|
||||
},
|
||||
rgIPAdapterAdded: (state, action: PayloadAction<{ id: string; ipAdapter: IPAdapterData }>) => {
|
||||
const { id, ipAdapter } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
rg.ipAdapters.push(ipAdapter);
|
||||
},
|
||||
rgIPAdapterDeleted: (state, action: PayloadAction<{ id: string; ipAdapterId: string }>) => {
|
||||
const { id, ipAdapterId } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
rg.ipAdapters = rg.ipAdapters.filter((ipAdapter) => ipAdapter.id !== ipAdapterId);
|
||||
},
|
||||
rgIPAdapterImageChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ id: string; ipAdapterId: string; imageDTO: ImageDTO | null }>
|
||||
) => {
|
||||
const { id, ipAdapterId, imageDTO } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
ipa.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||
},
|
||||
rgIPAdapterWeightChanged: (state, action: PayloadAction<{ id: string; ipAdapterId: string; weight: number }>) => {
|
||||
const { id, ipAdapterId, weight } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
ipa.weight = weight;
|
||||
},
|
||||
rgIPAdapterBeginEndStepPctChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ id: string; ipAdapterId: string; beginEndStepPct: [number, number] }>
|
||||
) => {
|
||||
const { id, ipAdapterId, beginEndStepPct } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
ipa.beginEndStepPct = beginEndStepPct;
|
||||
},
|
||||
rgIPAdapterMethodChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ id: string; ipAdapterId: string; method: IPMethodV2 }>
|
||||
) => {
|
||||
const { id, ipAdapterId, method } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
ipa.method = method;
|
||||
},
|
||||
rgIPAdapterModelChanged: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
id: string;
|
||||
ipAdapterId: string;
|
||||
modelConfig: IPAdapterModelConfig | null;
|
||||
}>
|
||||
) => {
|
||||
const { id, ipAdapterId, modelConfig } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
if (modelConfig) {
|
||||
ipa.model = zModelIdentifierField.parse(modelConfig);
|
||||
} else {
|
||||
ipa.model = null;
|
||||
}
|
||||
},
|
||||
rgIPAdapterCLIPVisionModelChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ id: string; ipAdapterId: string; clipVisionModel: CLIPVisionModelV2 }>
|
||||
) => {
|
||||
const { id, ipAdapterId, clipVisionModel } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
const ipa = rg.ipAdapters.find((ipa) => ipa.id === ipAdapterId);
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
ipa.clipVisionModel = clipVisionModel;
|
||||
},
|
||||
rgBrushLineAdded: {
|
||||
reducer: (state, action: PayloadAction<AddBrushLineArg & { lineId: string }>) => {
|
||||
const { id, points, lineId, color, width } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
rg.objects.push({
|
||||
id: getBrushLineId(id, lineId),
|
||||
type: 'brush_line',
|
||||
// Points must be offset by the layer's x and y coordinates
|
||||
// TODO: Handle this in the event listener?
|
||||
points: [points[0] - rg.x, points[1] - rg.y, points[2] - rg.x, points[3] - rg.y],
|
||||
strokeWidth: width,
|
||||
color,
|
||||
});
|
||||
rg.bboxNeedsUpdate = true;
|
||||
rg.imageCache = null;
|
||||
},
|
||||
prepare: (payload: AddBrushLineArg) => ({
|
||||
payload: { ...payload, lineId: uuidv4() },
|
||||
}),
|
||||
},
|
||||
rgEraserLineAdded: {
|
||||
reducer: (state, action: PayloadAction<AddEraserLineArg & { lineId: string }>) => {
|
||||
const { id, points, lineId, width } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
rg.objects.push({
|
||||
id: getEraserLineId(id, lineId),
|
||||
type: 'eraser_line',
|
||||
// Points must be offset by the layer's x and y coordinates
|
||||
// TODO: Handle this in the event listener?
|
||||
points: [points[0] - rg.x, points[1] - rg.y, points[2] - rg.x, points[3] - rg.y],
|
||||
strokeWidth: width,
|
||||
});
|
||||
rg.bboxNeedsUpdate = true;
|
||||
rg.imageCache = null;
|
||||
},
|
||||
prepare: (payload: AddEraserLineArg) => ({
|
||||
payload: { ...payload, lineId: uuidv4() },
|
||||
}),
|
||||
},
|
||||
rgLinePointAdded: (state, action: PayloadAction<AddPointToLineArg>) => {
|
||||
const { id, point } = action.payload;
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
const lastObject = rg.objects[rg.objects.length - 1];
|
||||
if (!lastObject || !isLine(lastObject)) {
|
||||
return;
|
||||
}
|
||||
// Points must be offset by the layer's x and y coordinates
|
||||
// TODO: Handle this in the event listener
|
||||
lastObject.points.push(point[0] - rg.x, point[1] - rg.y);
|
||||
rg.bboxNeedsUpdate = true;
|
||||
rg.imageCache = null;
|
||||
},
|
||||
rgRectAdded: {
|
||||
reducer: (state, action: PayloadAction<AddRectShapeArg & { rectId: string }>) => {
|
||||
const { id, rect, rectId, color } = action.payload;
|
||||
if (rect.height === 0 || rect.width === 0) {
|
||||
// Ignore zero-area rectangles
|
||||
return;
|
||||
}
|
||||
const rg = selectRg(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
rg.objects.push({
|
||||
type: 'rect_shape',
|
||||
id: getRectShapeId(id, rectId),
|
||||
x: rect.x - rg.x,
|
||||
y: rect.y - rg.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
color,
|
||||
});
|
||||
rg.bboxNeedsUpdate = true;
|
||||
rg.imageCache = null;
|
||||
},
|
||||
prepare: (payload: AddRectShapeArg) => ({ payload: { ...payload, rectId: uuidv4() } }),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
rgAdded,
|
||||
rgRecalled,
|
||||
rgIsEnabledToggled,
|
||||
rgTranslated,
|
||||
rgBboxChanged,
|
||||
rgDeleted,
|
||||
rgGlobalOpacityChanged,
|
||||
rgMovedForwardOne,
|
||||
rgMovedToFront,
|
||||
rgMovedBackwardOne,
|
||||
rgMovedToBack,
|
||||
rgPositivePromptChanged,
|
||||
rgNegativePromptChanged,
|
||||
rgFillChanged,
|
||||
rgMaskImageUploaded,
|
||||
rgAutoNegativeChanged,
|
||||
rgIPAdapterAdded,
|
||||
rgIPAdapterDeleted,
|
||||
rgIPAdapterImageChanged,
|
||||
rgIPAdapterWeightChanged,
|
||||
rgIPAdapterBeginEndStepPctChanged,
|
||||
rgIPAdapterMethodChanged,
|
||||
rgIPAdapterModelChanged,
|
||||
rgIPAdapterCLIPVisionModelChanged,
|
||||
rgBrushLineAdded,
|
||||
rgEraserLineAdded,
|
||||
rgLinePointAdded,
|
||||
rgRectAdded,
|
||||
} = regionalGuidanceSlice.actions;
|
||||
|
||||
export const selectRegionalGuidanceSlice = (state: RootState) => state.regionalGuidance;
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
const migrate = (state: any): any => {
|
||||
return state;
|
||||
};
|
||||
|
||||
export const regionalGuidancePersistConfig: PersistConfig<RegionalGuidanceState> = {
|
||||
name: regionalGuidanceSlice.name,
|
||||
initialState,
|
||||
migrate,
|
||||
persistDenylist: [],
|
||||
};
|
@ -1,9 +1,13 @@
|
||||
import {
|
||||
zControlNetConfigV2,
|
||||
zBeginEndStepPct,
|
||||
zCLIPVisionModelV2,
|
||||
zControlModeV2,
|
||||
zId,
|
||||
zImageWithDims,
|
||||
zIPAdapterConfigV2,
|
||||
zT2IAdapterConfigV2,
|
||||
zIPMethodV2,
|
||||
zProcessorConfig,
|
||||
} from 'features/controlLayers/util/controlAdapters';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||
import type {
|
||||
ParameterHeight,
|
||||
@ -17,7 +21,6 @@ import {
|
||||
zAutoNegative,
|
||||
zParameterNegativePrompt,
|
||||
zParameterPositivePrompt,
|
||||
zParameterStrength,
|
||||
} from 'features/parameters/types/parameterSchemas';
|
||||
import type { IRect } from 'konva/lib/types';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
@ -31,7 +34,7 @@ const zPoints = z.array(z.number()).refine((points) => points.length % 2 === 0,
|
||||
message: 'Must have an even number of points',
|
||||
});
|
||||
const zOLD_VectorMaskLine = z.object({
|
||||
id: z.string(),
|
||||
id: zId,
|
||||
type: z.literal('vector_mask_line'),
|
||||
tool: zDrawingTool,
|
||||
strokeWidth: z.number().min(1),
|
||||
@ -39,7 +42,7 @@ const zOLD_VectorMaskLine = z.object({
|
||||
});
|
||||
|
||||
const zOLD_VectorMaskRect = z.object({
|
||||
id: z.string(),
|
||||
id: zId,
|
||||
type: z.literal('vector_mask_rect'),
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
@ -52,6 +55,7 @@ const zRgbColor = z.object({
|
||||
g: z.number().int().min(0).max(255),
|
||||
b: z.number().int().min(0).max(255),
|
||||
});
|
||||
export type RgbColor = z.infer<typeof zRgbColor>;
|
||||
const zRgbaColor = zRgbColor.extend({
|
||||
a: z.number().min(0).max(1),
|
||||
});
|
||||
@ -61,7 +65,7 @@ export const DEFAULT_RGBA_COLOR: RgbaColor = { r: 255, g: 255, b: 255, a: 1 };
|
||||
const zOpacity = z.number().gte(0).lte(1);
|
||||
|
||||
const zBrushLine = z.object({
|
||||
id: z.string(),
|
||||
id: zId,
|
||||
type: z.literal('brush_line'),
|
||||
strokeWidth: z.number().min(1),
|
||||
points: zPoints,
|
||||
@ -70,7 +74,7 @@ const zBrushLine = z.object({
|
||||
export type BrushLine = z.infer<typeof zBrushLine>;
|
||||
|
||||
const zEraserline = z.object({
|
||||
id: z.string(),
|
||||
id: zId,
|
||||
type: z.literal('eraser_line'),
|
||||
strokeWidth: z.number().min(1),
|
||||
points: zPoints,
|
||||
@ -78,7 +82,7 @@ const zEraserline = z.object({
|
||||
export type EraserLine = z.infer<typeof zEraserline>;
|
||||
|
||||
const zRectShape = z.object({
|
||||
id: z.string(),
|
||||
id: zId,
|
||||
type: z.literal('rect_shape'),
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
@ -89,7 +93,7 @@ const zRectShape = z.object({
|
||||
export type RectShape = z.infer<typeof zRectShape>;
|
||||
|
||||
const zEllipseShape = z.object({
|
||||
id: z.string(),
|
||||
id: zId,
|
||||
type: z.literal('ellipse_shape'),
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
@ -100,7 +104,7 @@ const zEllipseShape = z.object({
|
||||
export type EllipseShape = z.infer<typeof zEllipseShape>;
|
||||
|
||||
const zPolygonShape = z.object({
|
||||
id: z.string(),
|
||||
id: zId,
|
||||
type: z.literal('polygon_shape'),
|
||||
points: zPoints,
|
||||
color: zRgbaColor,
|
||||
@ -108,7 +112,7 @@ const zPolygonShape = z.object({
|
||||
export type PolygonShape = z.infer<typeof zPolygonShape>;
|
||||
|
||||
const zImageObject = z.object({
|
||||
id: z.string(),
|
||||
id: zId,
|
||||
type: z.literal('image'),
|
||||
image: zImageWithDims,
|
||||
x: z.number(),
|
||||
@ -118,7 +122,7 @@ const zImageObject = z.object({
|
||||
});
|
||||
export type ImageObject = z.infer<typeof zImageObject>;
|
||||
|
||||
const zAnyLayerObject = z.discriminatedUnion('type', [
|
||||
const zLayerObject = z.discriminatedUnion('type', [
|
||||
zImageObject,
|
||||
zBrushLine,
|
||||
zEraserline,
|
||||
@ -126,13 +130,7 @@ const zAnyLayerObject = z.discriminatedUnion('type', [
|
||||
zEllipseShape,
|
||||
zPolygonShape,
|
||||
]);
|
||||
export type AnyLayerObject = z.infer<typeof zAnyLayerObject>;
|
||||
|
||||
const zLayerBase = z.object({
|
||||
id: z.string(),
|
||||
isEnabled: z.boolean().default(true),
|
||||
isSelected: z.boolean().default(true),
|
||||
});
|
||||
export type LayerObject = z.infer<typeof zLayerObject>;
|
||||
|
||||
const zRect = z.object({
|
||||
x: z.number(),
|
||||
@ -140,33 +138,36 @@ const zRect = z.object({
|
||||
width: z.number().min(1),
|
||||
height: z.number().min(1),
|
||||
});
|
||||
const zRenderableLayerBase = zLayerBase.extend({
|
||||
|
||||
const zLayerData = z.object({
|
||||
id: zId,
|
||||
type: z.literal('layer'),
|
||||
isEnabled: z.boolean(),
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
bbox: zRect.nullable(),
|
||||
bboxNeedsUpdate: z.boolean(),
|
||||
});
|
||||
|
||||
const zRasterLayer = zRenderableLayerBase.extend({
|
||||
type: z.literal('raster_layer'),
|
||||
opacity: zOpacity,
|
||||
objects: z.array(zAnyLayerObject),
|
||||
objects: z.array(zLayerObject),
|
||||
});
|
||||
export type RasterLayer = z.infer<typeof zRasterLayer>;
|
||||
export type LayerData = z.infer<typeof zLayerData>;
|
||||
|
||||
const zControlAdapterLayer = zRenderableLayerBase.extend({
|
||||
type: z.literal('control_adapter_layer'),
|
||||
opacity: zOpacity,
|
||||
isFilterEnabled: z.boolean(),
|
||||
controlAdapter: z.discriminatedUnion('type', [zControlNetConfigV2, zT2IAdapterConfigV2]),
|
||||
const zIPAdapterData = z.object({
|
||||
id: zId,
|
||||
type: z.literal('ip_adapter'),
|
||||
isEnabled: z.boolean(),
|
||||
weight: z.number().gte(-1).lte(2),
|
||||
method: zIPMethodV2,
|
||||
image: zImageWithDims.nullable(),
|
||||
model: zModelIdentifierField.nullable(),
|
||||
clipVisionModel: zCLIPVisionModelV2,
|
||||
beginEndStepPct: zBeginEndStepPct,
|
||||
});
|
||||
export type ControlAdapterLayer = z.infer<typeof zControlAdapterLayer>;
|
||||
|
||||
const zIPAdapterLayer = zLayerBase.extend({
|
||||
type: z.literal('ip_adapter_layer'),
|
||||
ipAdapter: zIPAdapterConfigV2,
|
||||
});
|
||||
export type IPAdapterLayer = z.infer<typeof zIPAdapterLayer>;
|
||||
export type IPAdapterData = z.infer<typeof zIPAdapterData>;
|
||||
export type IPAdapterConfig = Pick<
|
||||
IPAdapterData,
|
||||
'weight' | 'image' | 'beginEndStepPct' | 'model' | 'clipVisionModel' | 'method'
|
||||
>;
|
||||
|
||||
const zMaskObject = z
|
||||
.discriminatedUnion('type', [zOLD_VectorMaskLine, zOLD_VectorMaskRect, zBrushLine, zEraserline, zRectShape])
|
||||
@ -201,69 +202,109 @@ const zMaskObject = z
|
||||
})
|
||||
.pipe(z.discriminatedUnion('type', [zBrushLine, zEraserline, zRectShape]));
|
||||
|
||||
const zOLD_RegionalGuidanceLayer = zRenderableLayerBase.extend({
|
||||
type: z.literal('regional_guidance_layer'),
|
||||
maskObjects: z.array(zMaskObject),
|
||||
positivePrompt: zParameterPositivePrompt.nullable(),
|
||||
negativePrompt: zParameterNegativePrompt.nullable(),
|
||||
ipAdapters: z.array(zIPAdapterConfigV2),
|
||||
previewColor: zRgbColor,
|
||||
autoNegative: zAutoNegative,
|
||||
uploadedMaskImage: zImageWithDims.nullable(),
|
||||
});
|
||||
const zRegionalGuidanceLayer = zRenderableLayerBase.extend({
|
||||
type: z.literal('regional_guidance_layer'),
|
||||
const zRegionalGuidanceData = z.object({
|
||||
id: zId,
|
||||
type: z.literal('regional_guidance'),
|
||||
isEnabled: z.boolean(),
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
bbox: zRect.nullable(),
|
||||
bboxNeedsUpdate: z.boolean(),
|
||||
objects: z.array(zMaskObject),
|
||||
positivePrompt: zParameterPositivePrompt.nullable(),
|
||||
negativePrompt: zParameterNegativePrompt.nullable(),
|
||||
ipAdapters: z.array(zIPAdapterConfigV2),
|
||||
previewColor: zRgbColor,
|
||||
ipAdapters: z.array(zIPAdapterData),
|
||||
fill: zRgbColor,
|
||||
autoNegative: zAutoNegative,
|
||||
uploadedMaskImage: zImageWithDims.nullable(),
|
||||
imageCache: zImageWithDims.nullable(),
|
||||
});
|
||||
// TODO(psyche): This doesn't migrate correctly!
|
||||
const zRGLayer = z
|
||||
.union([zOLD_RegionalGuidanceLayer, zRegionalGuidanceLayer])
|
||||
.transform((val) => {
|
||||
if ('maskObjects' in val) {
|
||||
const { maskObjects, ...rest } = val;
|
||||
return { ...rest, objects: maskObjects };
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
})
|
||||
.pipe(zRegionalGuidanceLayer);
|
||||
export type RegionalGuidanceLayer = z.infer<typeof zRGLayer>;
|
||||
export type RegionalGuidanceData = z.infer<typeof zRegionalGuidanceData>;
|
||||
|
||||
const zInitialImageLayer = zRenderableLayerBase.extend({
|
||||
type: z.literal('initial_image_layer'),
|
||||
const zColorFill = z.object({
|
||||
type: z.literal('color_fill'),
|
||||
color: zRgbaColor,
|
||||
});
|
||||
const zImageFill = z.object({
|
||||
type: z.literal('image_fill'),
|
||||
src: z.string(),
|
||||
});
|
||||
const zFill = z.discriminatedUnion('type', [zColorFill, zImageFill]);
|
||||
const zInpaintMaskData = z.object({
|
||||
id: zId,
|
||||
type: z.literal('inpaint_mask'),
|
||||
isEnabled: z.boolean(),
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
bbox: zRect.nullable(),
|
||||
bboxNeedsUpdate: z.boolean(),
|
||||
maskObjects: z.array(zMaskObject),
|
||||
fill: zFill,
|
||||
imageCache: zImageWithDims.nullable(),
|
||||
});
|
||||
export type InpaintMaskData = z.infer<typeof zInpaintMaskData>;
|
||||
|
||||
const zFilter = z.enum(['none', 'lightness_to_alpha']);
|
||||
export type Filter = z.infer<typeof zFilter>;
|
||||
|
||||
const zControlAdapterData = z.object({
|
||||
id: zId,
|
||||
type: z.literal('control_adapter'),
|
||||
isEnabled: z.boolean(),
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
bbox: zRect.nullable(),
|
||||
bboxNeedsUpdate: z.boolean(),
|
||||
opacity: zOpacity,
|
||||
filter: zFilter,
|
||||
weight: z.number().gte(-1).lte(2),
|
||||
image: zImageWithDims.nullable(),
|
||||
denoisingStrength: zParameterStrength,
|
||||
processedImage: zImageWithDims.nullable(),
|
||||
processorConfig: zProcessorConfig.nullable(),
|
||||
processorPendingBatchId: z.string().nullable().default(null),
|
||||
beginEndStepPct: zBeginEndStepPct,
|
||||
model: zModelIdentifierField.nullable(),
|
||||
controlMode: zControlModeV2.nullable(),
|
||||
});
|
||||
export type InitialImageLayer = z.infer<typeof zInitialImageLayer>;
|
||||
export type ControlAdapterData = z.infer<typeof zControlAdapterData>;
|
||||
export type ControlAdapterConfig = Pick<
|
||||
ControlAdapterData,
|
||||
'weight' | 'image' | 'processedImage' | 'processorConfig' | 'beginEndStepPct' | 'model' | 'controlMode'
|
||||
>;
|
||||
|
||||
export const zLayer = z.discriminatedUnion('type', [
|
||||
zRegionalGuidanceLayer,
|
||||
zControlAdapterLayer,
|
||||
zIPAdapterLayer,
|
||||
zInitialImageLayer,
|
||||
zRasterLayer,
|
||||
]);
|
||||
export type Layer = z.infer<typeof zLayer>;
|
||||
const zCanvasItemIdentifier = z.object({
|
||||
type: z.enum([
|
||||
zLayerData.shape.type.value,
|
||||
zIPAdapterData.shape.type.value,
|
||||
zControlAdapterData.shape.type.value,
|
||||
zRegionalGuidanceData.shape.type.value,
|
||||
zInpaintMaskData.shape.type.value,
|
||||
]),
|
||||
id: zId,
|
||||
});
|
||||
type CanvasItemIdentifier = z.infer<typeof zCanvasItemIdentifier>;
|
||||
|
||||
export type ControlLayersState = {
|
||||
export type CanvasV2State = {
|
||||
_version: 3;
|
||||
selectedLayerId: string | null;
|
||||
layers: Layer[];
|
||||
brushSize: number;
|
||||
brushColor: RgbaColor;
|
||||
globalMaskLayerOpacity: number;
|
||||
positivePrompt: ParameterPositivePrompt;
|
||||
negativePrompt: ParameterNegativePrompt;
|
||||
positivePrompt2: ParameterPositiveStylePromptSDXL;
|
||||
negativePrompt2: ParameterNegativeStylePromptSDXL;
|
||||
shouldConcatPrompts: boolean;
|
||||
lastSelectedItem: CanvasItemIdentifier | null;
|
||||
prompts: {
|
||||
positivePrompt: ParameterPositivePrompt;
|
||||
negativePrompt: ParameterNegativePrompt;
|
||||
positivePrompt2: ParameterPositiveStylePromptSDXL;
|
||||
negativePrompt2: ParameterNegativeStylePromptSDXL;
|
||||
shouldConcatPrompts: boolean;
|
||||
};
|
||||
tool: {
|
||||
selected: Tool;
|
||||
selectedBuffer: Tool | null;
|
||||
invertScroll: boolean;
|
||||
brush: {
|
||||
width: number;
|
||||
};
|
||||
eraser: {
|
||||
width: number;
|
||||
};
|
||||
fill: RgbaColor;
|
||||
};
|
||||
size: {
|
||||
width: ParameterWidth;
|
||||
height: ParameterHeight;
|
||||
@ -273,45 +314,13 @@ export type ControlLayersState = {
|
||||
};
|
||||
|
||||
export type StageAttrs = { x: number; y: number; width: number; height: number; scale: number };
|
||||
export type AddEraserLineArg = { layerId: string; points: [number, number, number, number] };
|
||||
export type AddEraserLineArg = { id: string; points: [number, number, number, number]; width: number };
|
||||
export type AddBrushLineArg = AddEraserLineArg & { color: RgbaColor };
|
||||
export type AddPointToLineArg = { layerId: string; point: [number, number] };
|
||||
export type AddRectShapeArg = { layerId: string; rect: IRect; color: RgbaColor };
|
||||
export type AddImageObjectArg = { layerId: string; imageDTO: ImageDTO };
|
||||
export type AddPointToLineArg = { id: string; point: [number, number] };
|
||||
export type AddRectShapeArg = { id: string; rect: IRect; color: RgbaColor };
|
||||
export type AddImageObjectArg = { id: string; imageDTO: ImageDTO };
|
||||
|
||||
//#region Type guards
|
||||
export const isLine = (obj: AnyLayerObject): obj is BrushLine | EraserLine => {
|
||||
export const isLine = (obj: LayerObject): obj is BrushLine | EraserLine => {
|
||||
return obj.type === 'brush_line' || obj.type === 'eraser_line';
|
||||
};
|
||||
export const isRegionalGuidanceLayer = (layer?: Layer): layer is RegionalGuidanceLayer => {
|
||||
return layer?.type === 'regional_guidance_layer';
|
||||
};
|
||||
export const isControlAdapterLayer = (layer?: Layer): layer is ControlAdapterLayer => {
|
||||
return layer?.type === 'control_adapter_layer';
|
||||
};
|
||||
export const isIPAdapterLayer = (layer?: Layer): layer is IPAdapterLayer => {
|
||||
return layer?.type === 'ip_adapter_layer';
|
||||
};
|
||||
export const isInitialImageLayer = (layer?: Layer): layer is InitialImageLayer => {
|
||||
return layer?.type === 'initial_image_layer';
|
||||
};
|
||||
export const isRasterLayer = (layer?: Layer): layer is RasterLayer => {
|
||||
return layer?.type === 'raster_layer';
|
||||
};
|
||||
export const isRenderableLayer = (
|
||||
layer?: Layer
|
||||
): layer is RegionalGuidanceLayer | ControlAdapterLayer | InitialImageLayer | RasterLayer => {
|
||||
return (
|
||||
isRegionalGuidanceLayer(layer) || isControlAdapterLayer(layer) || isInitialImageLayer(layer) || isRasterLayer(layer)
|
||||
);
|
||||
};
|
||||
export const isLayerWithOpacity = (layer?: Layer): layer is ControlAdapterLayer | InitialImageLayer | RasterLayer => {
|
||||
return isControlAdapterLayer(layer) || isInitialImageLayer(layer) || isRasterLayer(layer);
|
||||
};
|
||||
export const isCAOrIPALayer = (layer?: Layer): layer is ControlAdapterLayer | IPAdapterLayer => {
|
||||
return isControlAdapterLayer(layer) || isIPAdapterLayer(layer);
|
||||
};
|
||||
export const isRGOrRasterlayer = (layer?: Layer): layer is RegionalGuidanceLayer | RasterLayer => {
|
||||
return isRegionalGuidanceLayer(layer) || isRasterLayer(layer);
|
||||
};
|
||||
//#endregion
|
||||
|
@ -10,7 +10,7 @@ import type {
|
||||
} from 'services/api/types';
|
||||
import { z } from 'zod';
|
||||
|
||||
const zId = z.string().min(1);
|
||||
export const zId = z.string().min(1);
|
||||
|
||||
const zCannyProcessorConfig = z.object({
|
||||
id: zId,
|
||||
@ -120,7 +120,7 @@ const zZoeDepthProcessorConfig = z.object({
|
||||
});
|
||||
export type ZoeDepthProcessorConfig = z.infer<typeof zZoeDepthProcessorConfig>;
|
||||
|
||||
const zProcessorConfig = z.discriminatedUnion('type', [
|
||||
export const zProcessorConfig = z.discriminatedUnion('type', [
|
||||
zCannyProcessorConfig,
|
||||
zColorMapProcessorConfig,
|
||||
zContentShuffleProcessorConfig,
|
||||
@ -145,7 +145,7 @@ export const zImageWithDims = z.object({
|
||||
});
|
||||
export type ImageWithDims = z.infer<typeof zImageWithDims>;
|
||||
|
||||
const zBeginEndStepPct = z
|
||||
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',
|
||||
@ -161,7 +161,7 @@ const zControlAdapterBase = z.object({
|
||||
beginEndStepPct: zBeginEndStepPct,
|
||||
});
|
||||
|
||||
const zControlModeV2 = z.enum(['balanced', 'more_prompt', 'more_control', 'unbalanced']);
|
||||
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;
|
||||
|
||||
@ -178,11 +178,11 @@ export const zT2IAdapterConfigV2 = zControlAdapterBase.extend({
|
||||
});
|
||||
export type T2IAdapterConfigV2 = z.infer<typeof zT2IAdapterConfigV2>;
|
||||
|
||||
const zCLIPVisionModelV2 = z.enum(['ViT-H', 'ViT-G']);
|
||||
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;
|
||||
|
||||
const zIPMethodV2 = z.enum(['full', 'style', 'composition']);
|
||||
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;
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
|
||||
import { selectControlAdaptersSlice } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
||||
import { getImageUsage, selectImageUsage } from 'features/deleteImageModal/store/selectors';
|
||||
import {
|
||||
@ -27,7 +27,7 @@ const selectImageUsages = createMemoizedSelector(
|
||||
selectCanvasSlice,
|
||||
selectNodesSlice,
|
||||
selectControlAdaptersSlice,
|
||||
selectControlLayersSlice,
|
||||
selectCanvasV2Slice,
|
||||
selectImageUsage,
|
||||
],
|
||||
(deleteImageModal, canvas, nodes, controlAdapters, controlLayers, imagesUsage) => {
|
||||
|
@ -7,8 +7,8 @@ import {
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import type { ControlAdaptersState } from 'features/controlAdapters/store/types';
|
||||
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
||||
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { ControlLayersState } from 'features/controlLayers/store/types';
|
||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { CanvasV2State } from 'features/controlLayers/store/types';
|
||||
import {
|
||||
isControlAdapterLayer,
|
||||
isInitialImageLayer,
|
||||
@ -28,7 +28,7 @@ export const getImageUsage = (
|
||||
canvas: CanvasState,
|
||||
nodes: NodesState,
|
||||
controlAdapters: ControlAdaptersState,
|
||||
controlLayers: ControlLayersState,
|
||||
controlLayers: CanvasV2State,
|
||||
image_name: string
|
||||
) => {
|
||||
const isCanvasImage = canvas.layerState.objects.some((obj) => obj.kind === 'image' && obj.imageName === image_name);
|
||||
@ -75,7 +75,7 @@ export const selectImageUsage = createMemoizedSelector(
|
||||
selectCanvasSlice,
|
||||
selectNodesSlice,
|
||||
selectControlAdaptersSlice,
|
||||
selectControlLayersSlice,
|
||||
selectCanvasV2Slice,
|
||||
(deleteImageModal, canvas, nodes, controlAdapters, controlLayers) => {
|
||||
const { imagesToDelete } = deleteImageModal;
|
||||
|
||||
|
@ -15,7 +15,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
|
||||
import { selectControlAdaptersSlice } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import ImageUsageMessage from 'features/deleteImageModal/components/ImageUsageMessage';
|
||||
import { getImageUsage } from 'features/deleteImageModal/store/selectors';
|
||||
import type { ImageUsage } from 'features/deleteImageModal/store/types';
|
||||
@ -42,7 +42,7 @@ const DeleteBoardModal = (props: Props) => {
|
||||
const selectImageUsageSummary = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(
|
||||
[selectCanvasSlice, selectNodesSlice, selectControlAdaptersSlice, selectControlLayersSlice],
|
||||
[selectCanvasSlice, selectNodesSlice, selectControlAdaptersSlice, selectCanvasV2Slice],
|
||||
(canvas, nodes, controlAdapters, controlLayers) => {
|
||||
const allImageUsage = (boardImageNames ?? []).map((imageName) =>
|
||||
getImageUsage(canvas, nodes, controlAdapters, controlLayers.present, imageName)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { Layer } from 'features/controlLayers/store/types';
|
||||
import type { LayerData } from 'features/controlLayers/store/types';
|
||||
import { MetadataItemView } from 'features/metadata/components/MetadataItemView';
|
||||
import type { MetadataHandlers } from 'features/metadata/types';
|
||||
import { handlers } from 'features/metadata/util/handlers';
|
||||
@ -9,7 +9,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export const MetadataLayers = ({ metadata }: Props) => {
|
||||
const [layers, setLayers] = useState<Layer[]>([]);
|
||||
const [layers, setLayers] = useState<LayerData[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const parse = async () => {
|
||||
@ -40,8 +40,8 @@ const MetadataViewLayer = ({
|
||||
handlers,
|
||||
}: {
|
||||
label: string;
|
||||
layer: Layer;
|
||||
handlers: MetadataHandlers<Layer[], Layer>;
|
||||
layer: LayerData;
|
||||
handlers: MetadataHandlers<LayerData[], LayerData>;
|
||||
}) => {
|
||||
const onRecall = useCallback(() => {
|
||||
if (!handlers.recallItem) {
|
||||
|
@ -2,7 +2,7 @@ import { getStore } from 'app/store/nanostores/store';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { objectKeys } from 'common/util/objectKeys';
|
||||
import { shouldConcatPromptsChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { Layer } from 'features/controlLayers/store/types';
|
||||
import type { LayerData } from 'features/controlLayers/store/types';
|
||||
import type { LoRA } from 'features/lora/store/loraSlice';
|
||||
import type {
|
||||
AnyControlAdapterConfigMetadata,
|
||||
@ -49,7 +49,7 @@ const renderControlAdapterValue: MetadataRenderValueFunc<AnyControlAdapterConfig
|
||||
return `${value.model.key} (${value.model.base.toUpperCase()}) - ${value.weight}`;
|
||||
}
|
||||
};
|
||||
const renderLayerValue: MetadataRenderValueFunc<Layer> = async (layer) => {
|
||||
const renderLayerValue: MetadataRenderValueFunc<LayerData> = async (layer) => {
|
||||
if (layer.type === 'initial_image_layer') {
|
||||
let rendered = t('controlLayers.globalInitialImageLayer');
|
||||
if (layer.image) {
|
||||
@ -89,7 +89,7 @@ const renderLayerValue: MetadataRenderValueFunc<Layer> = async (layer) => {
|
||||
}
|
||||
assert(false, 'Unknown layer type');
|
||||
};
|
||||
const renderLayersValue: MetadataRenderValueFunc<Layer[]> = async (layers) => {
|
||||
const renderLayersValue: MetadataRenderValueFunc<LayerData[]> = async (layers) => {
|
||||
return `${layers.length} ${t('controlLayers.layers', { count: layers.length })}`;
|
||||
};
|
||||
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
} from 'features/controlAdapters/util/buildControlAdapter';
|
||||
import { buildControlAdapterProcessor } from 'features/controlAdapters/util/buildControlAdapterProcessor';
|
||||
import { getCALayerId, getIPALayerId, INITIAL_IMAGE_LAYER_ID } from 'features/controlLayers/konva/naming';
|
||||
import type { ControlAdapterLayer, InitialImageLayer, IPAdapterLayer, Layer } from 'features/controlLayers/store/types';
|
||||
import type { ControlAdapterLayer, InitialImageLayer, IPAdapterLayer, LayerData } from 'features/controlLayers/store/types';
|
||||
import { zLayer } from 'features/controlLayers/store/types';
|
||||
import {
|
||||
CA_PROCESSOR_DATA,
|
||||
@ -431,22 +431,22 @@ const parseAllIPAdapters: MetadataParseFunc<IPAdapterConfigMetadata[]> = async (
|
||||
};
|
||||
|
||||
//#region Control Layers
|
||||
const parseLayer: MetadataParseFunc<Layer> = async (metadataItem) => zLayer.parseAsync(metadataItem);
|
||||
const parseLayer: MetadataParseFunc<LayerData> = async (metadataItem) => zLayer.parseAsync(metadataItem);
|
||||
|
||||
const parseLayers: MetadataParseFunc<Layer[]> = async (metadata) => {
|
||||
const parseLayers: MetadataParseFunc<LayerData[]> = async (metadata) => {
|
||||
// We need to support recalling pre-Control Layers metadata into Control Layers. A separate set of parsers handles
|
||||
// taking pre-CL metadata and parsing it into layers. It doesn't always map 1-to-1, so this is best-effort. For
|
||||
// example, CL Control Adapters don't support resize mode, so we simply omit that property.
|
||||
|
||||
try {
|
||||
const layers: Layer[] = [];
|
||||
const layers: LayerData[] = [];
|
||||
|
||||
try {
|
||||
const control_layers = await getProperty(metadata, 'control_layers');
|
||||
const controlLayersRaw = await getProperty(control_layers, 'layers', isArray);
|
||||
const controlLayersParseResults = await Promise.allSettled(controlLayersRaw.map(parseLayer));
|
||||
const controlLayers = controlLayersParseResults
|
||||
.filter((result): result is PromiseFulfilledResult<Layer> => result.status === 'fulfilled')
|
||||
.filter((result): result is PromiseFulfilledResult<LayerData> => result.status === 'fulfilled')
|
||||
.map((result) => result.value);
|
||||
layers.push(...controlLayers);
|
||||
} catch {
|
||||
|
@ -9,18 +9,18 @@ import {
|
||||
import { getCALayerId, getIPALayerId, getRGLayerId } from 'features/controlLayers/konva/naming';
|
||||
import {
|
||||
allLayersDeleted,
|
||||
caLayerRecalled,
|
||||
controlAdapterRecalled,
|
||||
heightChanged,
|
||||
iiLayerRecalled,
|
||||
ipaLayerRecalled,
|
||||
ipAdapterRecalled,
|
||||
negativePrompt2Changed,
|
||||
negativePromptChanged,
|
||||
positivePrompt2Changed,
|
||||
positivePromptChanged,
|
||||
rgLayerRecalled,
|
||||
regionalGuidanceRecalled,
|
||||
widthChanged,
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { Layer } from 'features/controlLayers/store/types';
|
||||
import type { LayerData } from 'features/controlLayers/store/types';
|
||||
import { setHrfEnabled, setHrfMethod, setHrfStrength } from 'features/hrf/store/hrfSlice';
|
||||
import type { LoRA } from 'features/lora/store/loraSlice';
|
||||
import { loraRecalled, lorasReset } from 'features/lora/store/loraSlice';
|
||||
@ -242,7 +242,7 @@ const recallIPAdapters: MetadataRecallFunc<IPAdapterConfigMetadata[]> = (ipAdapt
|
||||
};
|
||||
|
||||
//#region Control Layers
|
||||
const recallLayer: MetadataRecallFunc<Layer> = async (layer) => {
|
||||
const recallLayer: MetadataRecallFunc<LayerData> = async (layer) => {
|
||||
const { dispatch } = getStore();
|
||||
// We need to check for the existence of all images and models when recalling. If they do not exist, SMITE THEM!
|
||||
// Also, we need fresh IDs for all objects when recalling, to prevent multiple layers with the same ID.
|
||||
@ -269,7 +269,7 @@ const recallLayer: MetadataRecallFunc<Layer> = async (layer) => {
|
||||
}
|
||||
clone.id = getCALayerId(uuidv4());
|
||||
clone.controlAdapter.id = uuidv4();
|
||||
dispatch(caLayerRecalled(clone));
|
||||
dispatch(controlAdapterRecalled(clone));
|
||||
return;
|
||||
}
|
||||
if (layer.type === 'ip_adapter_layer') {
|
||||
@ -289,7 +289,7 @@ const recallLayer: MetadataRecallFunc<Layer> = async (layer) => {
|
||||
}
|
||||
clone.id = getIPALayerId(uuidv4());
|
||||
clone.ipAdapter.id = uuidv4();
|
||||
dispatch(ipaLayerRecalled(clone));
|
||||
dispatch(ipAdapterRecalled(clone));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -315,7 +315,7 @@ const recallLayer: MetadataRecallFunc<Layer> = async (layer) => {
|
||||
ipAdapter.id = uuidv4();
|
||||
}
|
||||
clone.id = getRGLayerId(uuidv4());
|
||||
dispatch(rgLayerRecalled(clone));
|
||||
dispatch(regionalGuidanceRecalled(clone));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -325,7 +325,7 @@ const recallLayer: MetadataRecallFunc<Layer> = async (layer) => {
|
||||
}
|
||||
};
|
||||
|
||||
const recallLayers: MetadataRecallFunc<Layer[]> = (layers) => {
|
||||
const recallLayers: MetadataRecallFunc<LayerData[]> = (layers) => {
|
||||
const { dispatch } = getStore();
|
||||
dispatch(allLayersDeleted());
|
||||
for (const l of layers) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { getStore } from 'app/store/nanostores/store';
|
||||
import type { Layer } from 'features/controlLayers/store/types';
|
||||
import type { LayerData } from 'features/controlLayers/store/types';
|
||||
import type { LoRA } from 'features/lora/store/loraSlice';
|
||||
import type {
|
||||
ControlNetConfigMetadata,
|
||||
@ -110,7 +110,7 @@ const validateIPAdapters: MetadataValidateFunc<IPAdapterConfigMetadata[]> = (ipA
|
||||
return new Promise((resolve) => resolve(validatedIPAdapters));
|
||||
};
|
||||
|
||||
const validateLayer: MetadataValidateFunc<Layer> = async (layer) => {
|
||||
const validateLayer: MetadataValidateFunc<LayerData> = async (layer) => {
|
||||
if (layer.type === 'control_adapter_layer') {
|
||||
const model = layer.controlAdapter.model;
|
||||
assert(model, 'Control Adapter layer missing model');
|
||||
@ -132,8 +132,8 @@ const validateLayer: MetadataValidateFunc<Layer> = async (layer) => {
|
||||
return layer;
|
||||
};
|
||||
|
||||
const validateLayers: MetadataValidateFunc<Layer[]> = async (layers) => {
|
||||
const validatedLayers: Layer[] = [];
|
||||
const validateLayers: MetadataValidateFunc<LayerData[]> = async (layers) => {
|
||||
const validatedLayers: LayerData[] = [];
|
||||
for (const l of layers) {
|
||||
try {
|
||||
const validated = await validateLayer(l);
|
||||
|
@ -5,8 +5,8 @@ import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
|
||||
import { blobToDataURL } from 'features/canvas/util/blobToDataURL';
|
||||
import { RG_LAYER_NAME } from 'features/controlLayers/konva/naming';
|
||||
import { renderers } from 'features/controlLayers/konva/renderers/layers';
|
||||
import { rgLayerMaskImageUploaded } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { InitialImageLayer, Layer, RegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
||||
import { regionalGuidanceMaskImageUploaded } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import type { InitialImageLayer, LayerData, RegionalGuidanceLayer } from 'features/controlLayers/store/types';
|
||||
import {
|
||||
isControlAdapterLayer,
|
||||
isInitialImageLayer,
|
||||
@ -70,7 +70,7 @@ export const addControlLayers = async (
|
||||
| Invocation<'vae_loader'>
|
||||
| Invocation<'main_model_loader'>
|
||||
| Invocation<'sdxl_model_loader'>
|
||||
): Promise<Layer[]> => {
|
||||
): Promise<LayerData[]> => {
|
||||
const isSDXL = base === 'sdxl';
|
||||
|
||||
const validLayers = state.controlLayers.present.layers.filter((l) => isValidLayer(l, base));
|
||||
@ -492,7 +492,7 @@ const isValidIPAdapter = (ipa: IPAdapterConfigV2, base: BaseModelType): boolean
|
||||
return hasModel && modelMatchesBase && hasImage;
|
||||
};
|
||||
|
||||
const isValidLayer = (layer: Layer, base: BaseModelType) => {
|
||||
const isValidLayer = (layer: LayerData, base: BaseModelType) => {
|
||||
if (!layer.isEnabled) {
|
||||
return false;
|
||||
}
|
||||
@ -532,7 +532,7 @@ const getMaskImage = async (layer: RegionalGuidanceLayer, blob: Blob): Promise<I
|
||||
req.reset();
|
||||
|
||||
const imageDTO = await req.unwrap();
|
||||
dispatch(rgLayerMaskImageUploaded({ layerId: layer.id, imageDTO }));
|
||||
dispatch(regionalGuidanceMaskImageUploaded({ layerId: layer.id, imageDTO }));
|
||||
return imageDTO;
|
||||
};
|
||||
//#endregion
|
||||
|
@ -2,7 +2,7 @@ import { Divider, Flex, ListItem, Text, Tooltip, UnorderedList } from '@invoke-a
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue';
|
||||
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
@ -12,7 +12,7 @@ import { useEnqueueBatchMutation } from 'services/api/endpoints/queue';
|
||||
import { useBoardName } from 'services/api/hooks/useBoardName';
|
||||
|
||||
const selectPromptsCount = createSelector(
|
||||
selectControlLayersSlice,
|
||||
selectCanvasV2Slice,
|
||||
selectDynamicPromptsSlice,
|
||||
(controlLayers, dynamicPrompts) =>
|
||||
getShouldProcessPrompt(controlLayers.present.positivePrompt) ? dynamicPrompts.prompts.length : 1
|
||||
|
@ -3,7 +3,7 @@ import { Expander, Flex, FormControlGroup, StandaloneAccordion } from '@invoke-a
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
|
||||
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { HrfSettings } from 'features/hrf/components/HrfSettings';
|
||||
import { selectHrfSlice } from 'features/hrf/store/hrfSlice';
|
||||
import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing';
|
||||
@ -24,7 +24,7 @@ import { ImageSizeCanvas } from './ImageSizeCanvas';
|
||||
import { ImageSizeLinear } from './ImageSizeLinear';
|
||||
|
||||
const selector = createMemoizedSelector(
|
||||
[selectGenerationSlice, selectCanvasSlice, selectHrfSlice, selectControlLayersSlice, activeTabNameSelector],
|
||||
[selectGenerationSlice, selectCanvasSlice, selectHrfSlice, selectCanvasV2Slice, activeTabNameSelector],
|
||||
(generation, canvas, hrf, controlLayers, activeTabName) => {
|
||||
const { shouldRandomizeSeed, model } = generation;
|
||||
const { hrfEnabled } = hrf;
|
||||
|
Loading…
Reference in New Issue
Block a user