mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): get processed images back into controlnet ui
This commit is contained in:
@ -1,15 +1,13 @@
|
|||||||
import { startAppListening } from '..';
|
import { startAppListening } from '..';
|
||||||
import { imageMetadataReceived, imageUploaded } from 'services/thunks/image';
|
import { imageMetadataReceived } from 'services/thunks/image';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
|
||||||
import { log } from 'app/logging/useLogger';
|
import { log } from 'app/logging/useLogger';
|
||||||
import { controlNetImageProcessed } from 'features/controlNet/store/actions';
|
import { controlNetImageProcessed } from 'features/controlNet/store/actions';
|
||||||
import { Graph } from 'services/api';
|
import { Graph } from 'services/api';
|
||||||
import { sessionCreated } from 'services/thunks/session';
|
import { sessionCreated } from 'services/thunks/session';
|
||||||
import { sessionReadyToInvoke } from 'features/system/store/actions';
|
import { sessionReadyToInvoke } from 'features/system/store/actions';
|
||||||
import { appSocketInvocationComplete } from 'services/events/actions';
|
import { socketInvocationComplete } from 'services/events/actions';
|
||||||
import { isImageOutput } from 'services/types/guards';
|
import { isImageOutput } from 'services/types/guards';
|
||||||
import { controlNetProcessedImageChanged } from 'features/controlNet/store/controlNetSlice';
|
import { controlNetProcessedImageChanged } from 'features/controlNet/store/controlNetSlice';
|
||||||
import { selectImagesById } from 'features/gallery/store/imagesSlice';
|
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'controlNet' });
|
const moduleLog = log.child({ namespace: 'controlNet' });
|
||||||
|
|
||||||
@ -18,27 +16,36 @@ export const addControlNetImageProcessedListener = () => {
|
|||||||
actionCreator: controlNetImageProcessed,
|
actionCreator: controlNetImageProcessed,
|
||||||
effect: async (action, { dispatch, getState, take }) => {
|
effect: async (action, { dispatch, getState, take }) => {
|
||||||
const { controlNetId, processorNode } = action.payload;
|
const { controlNetId, processorNode } = action.payload;
|
||||||
const { id } = processorNode;
|
|
||||||
|
// ControlNet one-off procressing graph is just he processor node, no edges
|
||||||
const graph: Graph = {
|
const graph: Graph = {
|
||||||
nodes: { [id]: processorNode },
|
nodes: { [processorNode.id]: processorNode },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create a session to run the graph & wait til it's ready to invoke
|
||||||
const sessionCreatedAction = dispatch(sessionCreated({ graph }));
|
const sessionCreatedAction = dispatch(sessionCreated({ graph }));
|
||||||
const [sessionCreatedFulfilledAction] = await take(
|
const [sessionCreatedFulfilledAction] = await take(
|
||||||
(action): action is ReturnType<typeof sessionCreated.fulfilled> =>
|
(action): action is ReturnType<typeof sessionCreated.fulfilled> =>
|
||||||
sessionCreated.fulfilled.match(action) &&
|
sessionCreated.fulfilled.match(action) &&
|
||||||
action.meta.requestId === sessionCreatedAction.requestId
|
action.meta.requestId === sessionCreatedAction.requestId
|
||||||
);
|
);
|
||||||
|
|
||||||
const sessionId = sessionCreatedFulfilledAction.payload.id;
|
const sessionId = sessionCreatedFulfilledAction.payload.id;
|
||||||
|
|
||||||
|
// Invoke the session & wait til it's complete
|
||||||
dispatch(sessionReadyToInvoke());
|
dispatch(sessionReadyToInvoke());
|
||||||
const [processorAction] = await take(
|
const [invocationCompleteAction] = await take(
|
||||||
(action): action is ReturnType<typeof appSocketInvocationComplete> =>
|
(action): action is ReturnType<typeof socketInvocationComplete> =>
|
||||||
appSocketInvocationComplete.match(action) &&
|
socketInvocationComplete.match(action) &&
|
||||||
action.payload.data.graph_execution_state_id === sessionId
|
action.payload.data.graph_execution_state_id === sessionId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isImageOutput(processorAction.payload.data.result)) {
|
// We still have to check the output type
|
||||||
const { image_name } = processorAction.payload.data.result.image;
|
if (isImageOutput(invocationCompleteAction.payload.data.result)) {
|
||||||
|
const { image_name } =
|
||||||
|
invocationCompleteAction.payload.data.result.image;
|
||||||
|
|
||||||
|
// Wait for the ImageDTO to be received
|
||||||
const [imageMetadataReceivedAction] = await take(
|
const [imageMetadataReceivedAction] = await take(
|
||||||
(
|
(
|
||||||
action
|
action
|
||||||
@ -46,8 +53,9 @@ export const addControlNetImageProcessedListener = () => {
|
|||||||
imageMetadataReceived.fulfilled.match(action) &&
|
imageMetadataReceived.fulfilled.match(action) &&
|
||||||
action.payload.image_name === image_name
|
action.payload.image_name === image_name
|
||||||
);
|
);
|
||||||
|
|
||||||
const processedControlImage = imageMetadataReceivedAction.payload;
|
const processedControlImage = imageMetadataReceivedAction.payload;
|
||||||
|
|
||||||
|
// Update the processed image in the store
|
||||||
dispatch(
|
dispatch(
|
||||||
controlNetProcessedImageChanged({
|
controlNetProcessedImageChanged({
|
||||||
controlNetId,
|
controlNetId,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback, useState } from 'react';
|
||||||
import { ControlNetProcessorNode } from '../store/types';
|
import { ControlNetProcessorNode } from '../store/types';
|
||||||
import { ImageDTO } from 'services/api';
|
import { ImageDTO } from 'services/api';
|
||||||
import CannyProcessor from './processors/CannyProcessor';
|
import CannyProcessor from './processors/CannyProcessor';
|
||||||
@ -27,19 +27,10 @@ import { Flex, HStack, VStack } from '@chakra-ui/react';
|
|||||||
import IAISelectableImage from './parameters/IAISelectableImage';
|
import IAISelectableImage from './parameters/IAISelectableImage';
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import IAISwitch from 'common/components/IAISwitch';
|
||||||
export type ControlNetProcessorProps = {
|
import ParamControlNetIsPreprocessed from './parameters/ParamControlNetIsPreprocessed';
|
||||||
controlNetId: string;
|
import IAICollapse from 'common/components/IAICollapse';
|
||||||
image: ImageDTO;
|
import ControlNetProcessorCollapse from './ControlNetProcessorCollapse';
|
||||||
type: ControlNetProcessorNode['type'];
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderProcessorComponent = (props: ControlNetProcessorProps) => {
|
|
||||||
const { type } = props;
|
|
||||||
if (type === 'canny_image_processor') {
|
|
||||||
return <CannyProcessor {...props} />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
type ControlNetProps = {
|
type ControlNetProps = {
|
||||||
controlNet: ControlNet;
|
controlNet: ControlNet;
|
||||||
@ -59,6 +50,10 @@ const ControlNet = (props: ControlNetProps) => {
|
|||||||
} = props.controlNet;
|
} = props.controlNet;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const [processorType, setProcessorType] = useState<
|
||||||
|
ControlNetProcessorNode['type']
|
||||||
|
>('canny_image_processor');
|
||||||
|
|
||||||
const handleControlImageChanged = useCallback(
|
const handleControlImageChanged = useCallback(
|
||||||
(controlImage: ImageDTO) => {
|
(controlImage: ImageDTO) => {
|
||||||
dispatch(controlNetImageChanged({ controlNetId, controlImage }));
|
dispatch(controlNetImageChanged({ controlNetId, controlImage }));
|
||||||
@ -74,14 +69,6 @@ const ControlNet = (props: ControlNetProps) => {
|
|||||||
dispatch(controlNetRemoved(controlNetId));
|
dispatch(controlNetRemoved(controlNetId));
|
||||||
}, [controlNetId, dispatch]);
|
}, [controlNetId, dispatch]);
|
||||||
|
|
||||||
const handleIsControlImageProcessedToggled = useCallback(() => {
|
|
||||||
dispatch(
|
|
||||||
isControlNetImageProcessedToggled({
|
|
||||||
controlNetId,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [controlNetId, dispatch]);
|
|
||||||
|
|
||||||
const handleProcessedControlImageChanged = useCallback(
|
const handleProcessedControlImageChanged = useCallback(
|
||||||
(processedControlImage: ImageDTO | null) => {
|
(processedControlImage: ImageDTO | null) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
@ -98,11 +85,15 @@ const ControlNet = (props: ControlNetProps) => {
|
|||||||
<Flex sx={{ flexDir: 'column', gap: 3, pb: 4 }}>
|
<Flex sx={{ flexDir: 'column', gap: 3, pb: 4 }}>
|
||||||
<IAIButton onClick={handleControlNetRemoved}>Remove ControlNet</IAIButton>
|
<IAIButton onClick={handleControlNetRemoved}>Remove ControlNet</IAIButton>
|
||||||
<IAISelectableImage
|
<IAISelectableImage
|
||||||
image={controlImage}
|
image={processedControlImage || controlImage}
|
||||||
onChange={handleControlImageChanged}
|
onChange={handleControlImageChanged}
|
||||||
onReset={handleControlImageReset}
|
onReset={handleControlImageReset}
|
||||||
resetIconSize="sm"
|
resetIconSize="sm"
|
||||||
/>
|
/>
|
||||||
|
<ControlNetProcessorCollapse
|
||||||
|
controlNetId={controlNetId}
|
||||||
|
image={controlImage}
|
||||||
|
/>
|
||||||
<ParamControlNetModel controlNetId={controlNetId} model={model} />
|
<ParamControlNetModel controlNetId={controlNetId} model={model} />
|
||||||
<ParamControlNetIsEnabled
|
<ParamControlNetIsEnabled
|
||||||
controlNetId={controlNetId}
|
controlNetId={controlNetId}
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
import { useDisclosure } from '@chakra-ui/react';
|
||||||
|
import IAICollapse from 'common/components/IAICollapse';
|
||||||
|
import { memo, useState } from 'react';
|
||||||
|
import CannyProcessor from './processors/CannyProcessor';
|
||||||
|
import { ImageDTO } from 'services/api';
|
||||||
|
import IAICustomSelect from 'common/components/IAICustomSelect';
|
||||||
|
import {
|
||||||
|
CONTROLNET_PROCESSORS,
|
||||||
|
ControlNetProcessor,
|
||||||
|
} from '../store/controlNetSlice';
|
||||||
|
|
||||||
|
export type ControlNetProcessorProps = {
|
||||||
|
controlNetId: string;
|
||||||
|
image: ImageDTO;
|
||||||
|
type: ControlNetProcessor;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProcessorComponent = (props: ControlNetProcessorProps) => {
|
||||||
|
const { type } = props;
|
||||||
|
if (type === 'canny') {
|
||||||
|
return <CannyProcessor {...props} />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ControlNetProcessorCollapseProps = {
|
||||||
|
controlNetId: string;
|
||||||
|
image: ImageDTO | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ControlNetProcessorCollapse = (
|
||||||
|
props: ControlNetProcessorCollapseProps
|
||||||
|
) => {
|
||||||
|
const { image, controlNetId } = props;
|
||||||
|
const { isOpen, onToggle } = useDisclosure();
|
||||||
|
|
||||||
|
const [processorType, setProcessorType] =
|
||||||
|
useState<ControlNetProcessor>('canny');
|
||||||
|
|
||||||
|
const handleProcessorTypeChanged = (type: string | null | undefined) => {
|
||||||
|
setProcessorType(type as ControlNetProcessor);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAICollapse
|
||||||
|
isOpen={Boolean(isOpen && image)}
|
||||||
|
onToggle={onToggle}
|
||||||
|
label="Process Image"
|
||||||
|
withSwitch
|
||||||
|
>
|
||||||
|
<IAICustomSelect
|
||||||
|
items={CONTROLNET_PROCESSORS}
|
||||||
|
selectedItem={processorType}
|
||||||
|
setSelectedItem={handleProcessorTypeChanged}
|
||||||
|
/>
|
||||||
|
{image && (
|
||||||
|
<ProcessorComponent
|
||||||
|
controlNetId={controlNetId}
|
||||||
|
image={image}
|
||||||
|
type={processorType}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</IAICollapse>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ControlNetProcessorCollapse);
|
@ -0,0 +1,36 @@
|
|||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import IAIFullCheckbox from 'common/components/IAIFullCheckbox';
|
||||||
|
import IAISwitch from 'common/components/IAISwitch';
|
||||||
|
import {
|
||||||
|
controlNetToggled,
|
||||||
|
isControlNetImageProcessedToggled,
|
||||||
|
} from 'features/controlNet/store/controlNetSlice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
|
||||||
|
type ParamControlNetIsEnabledProps = {
|
||||||
|
controlNetId: string;
|
||||||
|
isControlImageProcessed: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ParamControlNetIsEnabled = (props: ParamControlNetIsEnabledProps) => {
|
||||||
|
const { controlNetId, isControlImageProcessed } = props;
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleIsControlImageProcessedToggled = useCallback(() => {
|
||||||
|
dispatch(
|
||||||
|
isControlNetImageProcessedToggled({
|
||||||
|
controlNetId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [controlNetId, dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAISwitch
|
||||||
|
label="Preprocess"
|
||||||
|
isChecked={isControlImageProcessed}
|
||||||
|
onChange={handleIsControlImageProcessedToggled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ParamControlNetIsEnabled);
|
@ -4,9 +4,11 @@ import { memo, useCallback, useState } from 'react';
|
|||||||
import ControlNetProcessButton from './common/ControlNetProcessButton';
|
import ControlNetProcessButton from './common/ControlNetProcessButton';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { controlNetImageProcessed } from 'features/controlNet/store/actions';
|
import { controlNetImageProcessed } from 'features/controlNet/store/actions';
|
||||||
import { ControlNetProcessorProps } from '../ControlNet';
|
import ControlNetResetProcessedImageButton from './common/ControlNetResetProcessedImageButton';
|
||||||
|
import { ControlNetProcessorProps } from '../ControlNetProcessorCollapse';
|
||||||
|
import { controlNetProcessedImageChanged } from 'features/controlNet/store/controlNetSlice';
|
||||||
|
|
||||||
export const CANNY_PROCESSOR = 'canny_processor';
|
export const CANNY_PROCESSOR = 'canny_image_processor';
|
||||||
|
|
||||||
const CannyProcessor = (props: ControlNetProcessorProps) => {
|
const CannyProcessor = (props: ControlNetProcessorProps) => {
|
||||||
const { controlNetId, image, type } = props;
|
const { controlNetId, image, type } = props;
|
||||||
@ -36,6 +38,15 @@ const CannyProcessor = (props: ControlNetProcessorProps) => {
|
|||||||
);
|
);
|
||||||
}, [controlNetId, dispatch, highThreshold, image, lowThreshold]);
|
}, [controlNetId, dispatch, highThreshold, image, lowThreshold]);
|
||||||
|
|
||||||
|
const handleReset = useCallback(() => {
|
||||||
|
dispatch(
|
||||||
|
controlNetProcessedImageChanged({
|
||||||
|
controlNetId,
|
||||||
|
processedControlImage: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [controlNetId, dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex sx={{ flexDirection: 'column', gap: 2 }}>
|
<Flex sx={{ flexDirection: 'column', gap: 2 }}>
|
||||||
<IAISlider
|
<IAISlider
|
||||||
@ -54,7 +65,10 @@ const CannyProcessor = (props: ControlNetProcessorProps) => {
|
|||||||
max={255}
|
max={255}
|
||||||
withInput
|
withInput
|
||||||
/>
|
/>
|
||||||
<ControlNetProcessButton onClick={handleProcess} />
|
<Flex sx={{ gap: 4 }}>
|
||||||
|
<ControlNetProcessButton onClick={handleProcess} />
|
||||||
|
<ControlNetResetProcessedImageButton onClick={handleReset} />
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import IAIButton from 'common/components/IAIButton';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { FaUnderline, FaUndo } from 'react-icons/fa';
|
||||||
|
|
||||||
|
type ControlNetResetProcessedImageButtonProps = {
|
||||||
|
onClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ControlNetResetProcessedImageButton = (
|
||||||
|
props: ControlNetResetProcessedImageButtonProps
|
||||||
|
) => {
|
||||||
|
const { onClick } = props;
|
||||||
|
return (
|
||||||
|
<IAIButton leftIcon={<FaUndo />} onClick={onClick}>
|
||||||
|
Reset Processing
|
||||||
|
</IAIButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ControlNetResetProcessedImageButton);
|
@ -18,20 +18,22 @@ export const CONTROLNET_MODELS = [
|
|||||||
'lllyasviel/sd-controlnet-mlsd',
|
'lllyasviel/sd-controlnet-mlsd',
|
||||||
];
|
];
|
||||||
|
|
||||||
// export const CONTROLNET_PROCESSORS = [
|
export const CONTROLNET_PROCESSORS = [
|
||||||
// 'canny',
|
'canny',
|
||||||
// 'contentShuffle',
|
'contentShuffle',
|
||||||
// 'hed',
|
'hed',
|
||||||
// 'lineart',
|
'lineart',
|
||||||
// 'lineartAnime',
|
'lineartAnime',
|
||||||
// 'mediapipeFace',
|
'mediapipeFace',
|
||||||
// 'midasDepth',
|
'midasDepth',
|
||||||
// 'mlsd',
|
'mlsd',
|
||||||
// 'normalBae',
|
'normalBae',
|
||||||
// 'openpose',
|
'openpose',
|
||||||
// 'pidi',
|
'pidi',
|
||||||
// 'zoeDepth',
|
'zoeDepth',
|
||||||
// ] as const;
|
];
|
||||||
|
|
||||||
|
export type ControlNetProcessor = (typeof CONTROLNET_PROCESSORS)[number];
|
||||||
|
|
||||||
export type ControlNetModel = (typeof CONTROLNET_MODELS)[number];
|
export type ControlNetModel = (typeof CONTROLNET_MODELS)[number];
|
||||||
|
|
||||||
@ -109,6 +111,7 @@ export const controlNetSlice = createSlice({
|
|||||||
) => {
|
) => {
|
||||||
const { controlNetId, controlImage } = action.payload;
|
const { controlNetId, controlImage } = action.payload;
|
||||||
state.controlNets[controlNetId].controlImage = controlImage;
|
state.controlNets[controlNetId].controlImage = controlImage;
|
||||||
|
state.controlNets[controlNetId].processedControlImage = null;
|
||||||
},
|
},
|
||||||
isControlNetImageProcessedToggled: (
|
isControlNetImageProcessedToggled: (
|
||||||
state,
|
state,
|
||||||
|
Reference in New Issue
Block a user