removed upscale button, created spandrel model dropdown, created upscale initial image that works with dnd

This commit is contained in:
Mary Hipp 2024-07-17 10:50:33 -04:00 committed by psychedelicious
parent 43b3e242b0
commit a0a54348e8
20 changed files with 317 additions and 39 deletions

View File

@ -24,6 +24,7 @@ import {
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { imagesApi } from 'services/api/endpoints/images';
import { upscaleInitialImageChanged } from '../../../../../features/parameters/store/upscaleSlice';
export const dndDropped = createAction<{
overData: TypesafeDroppableData;
@ -243,6 +244,20 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
return;
}
/**
* Image dropped on upscale initial image
*/
if (
overData.actionType === 'SET_UPSCALE_INITIAL_IMAGE' &&
activeData.payloadType === 'IMAGE_DTO' &&
activeData.payload.imageDTO
) {
const { imageDTO } = activeData.payload;
dispatch(upscaleInitialImageChanged(imageDTO));
return;
}
/**
* Multiple images dropped on user board
*/

View File

@ -19,6 +19,7 @@ import { t } from 'i18next';
import { omit } from 'lodash-es';
import { boardsApi } from 'services/api/endpoints/boards';
import { imagesApi } from 'services/api/endpoints/images';
import { upscaleInitialImageChanged } from '../../../../../features/parameters/store/upscaleSlice';
export const addImageUploadedFulfilledListener = (startAppListening: AppStartListening) => {
startAppListening({
@ -89,6 +90,15 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
return;
}
if (postUploadAction?.type === 'SET_UPSCALE_INITIAL_IMAGE') {
dispatch(upscaleInitialImageChanged(imageDTO));
toast({
...DEFAULT_UPLOADED_TOAST,
description: "set as upscale initial image",
});
return;
}
if (postUploadAction?.type === 'SET_CONTROL_ADAPTER_IMAGE') {
const { id } = postUploadAction;
dispatch(

View File

@ -17,7 +17,8 @@ import { forEach } from 'lodash-es';
import type { Logger } from 'roarr';
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
import type { AnyModelConfig } from 'services/api/types';
import { isNonRefinerMainModelConfig, isRefinerMainModelModelConfig, isVAEModelConfig } from 'services/api/types';
import { isNonRefinerMainModelConfig, isRefinerMainModelModelConfig, isSpandrelImageToImageModelConfig, isVAEModelConfig } from 'services/api/types';
import { upscaleModelChanged } from '../../../../../features/parameters/store/upscaleSlice';
export const addModelsLoadedListener = (startAppListening: AppStartListening) => {
startAppListening({
@ -36,6 +37,7 @@ export const addModelsLoadedListener = (startAppListening: AppStartListening) =>
handleVAEModels(models, state, dispatch, log);
handleLoRAModels(models, state, dispatch, log);
handleControlAdapterModels(models, state, dispatch, log);
handleSpandrelImageToImageModels(models, state, dispatch, log);
},
});
};
@ -177,3 +179,24 @@ const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log)
dispatch(controlAdapterModelCleared({ id: ca.id }));
});
};
const handleSpandrelImageToImageModels: ModelHandler = (models, state, dispatch, _log) => {
const currentUpscaleModel = state.upscale.upscaleModel;
const upscaleModels = models.filter(isSpandrelImageToImageModelConfig);
if (currentUpscaleModel) {
const isCurrentUpscaleModelAvailable = upscaleModels.some((m) => m.key === currentUpscaleModel.key);
if (isCurrentUpscaleModelAvailable) {
return;
}
}
const firstModel = upscaleModels[0];
if (firstModel) {
dispatch(upscaleModelChanged(firstModel))
return
}
dispatch(upscaleModelChanged(null))
};

View File

@ -46,6 +46,7 @@ import { actionSanitizer } from './middleware/devtools/actionSanitizer';
import { actionsDenylist } from './middleware/devtools/actionsDenylist';
import { stateSanitizer } from './middleware/devtools/stateSanitizer';
import { listenerMiddleware } from './middleware/listenerMiddleware';
import { upscalePersistConfig, upscaleSlice } from '../../features/parameters/store/upscaleSlice';
const allReducers = {
[canvasSlice.name]: canvasSlice.reducer,
@ -69,6 +70,7 @@ const allReducers = {
[controlLayersSlice.name]: undoable(controlLayersSlice.reducer, controlLayersUndoableConfig),
[workflowSettingsSlice.name]: workflowSettingsSlice.reducer,
[api.reducerPath]: api.reducer,
[upscaleSlice.name]: upscaleSlice.reducer
};
const rootReducer = combineReducers(allReducers);
@ -114,6 +116,7 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
[hrfPersistConfig.name]: hrfPersistConfig,
[controlLayersPersistConfig.name]: controlLayersPersistConfig,
[workflowSettingsPersistConfig.name]: workflowSettingsPersistConfig,
[upscalePersistConfig.name]: upscalePersistConfig
};
const unserialize: UnserializeFunction = (data, key) => {

View File

@ -21,6 +21,10 @@ const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (ac
postUploadAction = { type: 'SET_CANVAS_INITIAL_IMAGE' };
}
if (activeTabName === 'upscaling') {
postUploadAction = { type: 'SET_UPSCALE_INITIAL_IMAGE' };
}
return postUploadAction;
});

View File

@ -62,6 +62,10 @@ export type CanvasInitialImageDropData = BaseDropData & {
actionType: 'SET_CANVAS_INITIAL_IMAGE';
};
export type UpscaleInitialImageDropData = BaseDropData & {
actionType: 'SET_UPSCALE_INITIAL_IMAGE';
};
type NodesImageDropData = BaseDropData & {
actionType: 'SET_NODES_IMAGE';
context: {
@ -87,6 +91,8 @@ export type SelectForCompareDropData = BaseDropData & {
};
};
export type TypesafeDroppableData =
| CurrentImageDropData
| ControlAdapterDropData
@ -98,7 +104,8 @@ export type TypesafeDroppableData =
| IPALayerImageDropData
| RGLayerIPAdapterImageDropData
| IILayerImageDropData
| SelectForCompareDropData;
| SelectForCompareDropData
| UpscaleInitialImageDropData;
type BaseDragData = {
id: string;
@ -159,11 +166,11 @@ interface DragEvent {
over: TypesafeOver | null;
}
export interface DragStartEvent extends Pick<DragEvent, 'active'> {}
interface DragMoveEvent extends DragEvent {}
interface DragOverEvent extends DragMoveEvent {}
export interface DragEndEvent extends DragEvent {}
interface DragCancelEvent extends DragEndEvent {}
export interface DragStartEvent extends Pick<DragEvent, 'active'> { }
interface DragMoveEvent extends DragEvent { }
interface DragOverEvent extends DragMoveEvent { }
export interface DragEndEvent extends DragEvent { }
interface DragCancelEvent extends DragEndEvent { }
export interface DndContextTypesafeProps
extends Omit<DndContextProps, 'onDragStart' | 'onDragMove' | 'onDragOver' | 'onDragEnd' | 'onDragCancel'> {

View File

@ -27,6 +27,8 @@ export const isValidDrop = (overData?: TypesafeDroppableData | null, activeData?
return payloadType === 'IMAGE_DTO';
case 'SET_CANVAS_INITIAL_IMAGE':
return payloadType === 'IMAGE_DTO';
case 'SET_UPSCALE_INITIAL_IMAGE':
return payloadType === 'IMAGE_DTO';
case 'SET_NODES_IMAGE':
return payloadType === 'IMAGE_DTO';
case 'SELECT_FOR_COMPARE':

View File

@ -11,12 +11,8 @@ import SingleSelectionMenuItems from 'features/gallery/components/ImageContextMe
import { useImageActions } from 'features/gallery/hooks/useImageActions';
import { sentImageToImg2Img } from 'features/gallery/store/actions';
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
import { selectGallerySlice } from 'features/gallery/store/gallerySlice';
import { parseAndRecallImageDimensions } from 'features/metadata/util/handlers';
import { $templates } from 'features/nodes/store/nodesSlice';
import ParamUpscalePopover from 'features/parameters/components/Upscale/ParamUpscaleSettings';
import { useIsQueueMutationInProgress } from 'features/queue/hooks/useIsQueueMutationInProgress';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { selectSystemSlice } from 'features/system/store/systemSlice';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow';
@ -37,9 +33,8 @@ import { useGetImageDTOQuery } from 'services/api/endpoints/images';
const selectShouldDisableToolbarButtons = createSelector(
selectSystemSlice,
selectGallerySlice,
selectLastSelectedImage,
(system, gallery, lastSelectedImage) => {
(system, lastSelectedImage) => {
const hasProgressImage = Boolean(system.denoiseProgress?.progress_image);
return hasProgressImage || !lastSelectedImage;
}
@ -47,13 +42,10 @@ const selectShouldDisableToolbarButtons = createSelector(
const CurrentImageButtons = () => {
const dispatch = useAppDispatch();
const isConnected = useAppSelector((s) => s.system.isConnected);
const lastSelectedImage = useAppSelector(selectLastSelectedImage);
const selection = useAppSelector((s) => s.gallery.selection);
const shouldDisableToolbarButtons = useAppSelector(selectShouldDisableToolbarButtons);
const templates = useStore($templates);
const isUpscalingEnabled = useFeatureStatus('upscaling');
const isQueueMutationInProgress = useIsQueueMutationInProgress();
const { t } = useTranslation();
const { currentData: imageDTO } = useGetImageDTOQuery(lastSelectedImage?.image_name ?? skipToken);
@ -107,17 +99,6 @@ const CurrentImageButtons = () => {
dispatch(imagesToDeleteSelected(selection));
}, [dispatch, imageDTO, selection]);
useHotkeys(
'Shift+U',
() => {
handleClickUpscale();
},
{
enabled: () => Boolean(isUpscalingEnabled && !shouldDisableToolbarButtons && isConnected),
},
[isUpscalingEnabled, imageDTO, shouldDisableToolbarButtons, isConnected]
);
useHotkeys(
'delete',
() => {
@ -191,12 +172,6 @@ const CurrentImageButtons = () => {
/>
</ButtonGroup>
{isUpscalingEnabled && (
<ButtonGroup isDisabled={isQueueMutationInProgress}>
{isUpscalingEnabled && <ParamUpscalePopover imageDTO={imageDTO} />}
</ButtonGroup>
)}
<ButtonGroup>
<DeleteImageButton onClick={handleDelete} />
</ButtonGroup>

View File

@ -63,7 +63,7 @@ const ParamESRGANModel = () => {
return (
<FormControl orientation="vertical">
<FormLabel>{t('models.esrganModel')} </FormLabel>
<FormLabel>{t('models.esrganModel')}</FormLabel>
<Combobox value={value} onChange={onChange} options={options} />
</FormControl>
);

View File

@ -0,0 +1,47 @@
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useSpandrelImageToImageModels } from '../../../../services/api/hooks/modelsByType';
import { useModelCombobox } from '../../../../common/hooks/useModelCombobox';
import { SpandrelImageToImageModelConfig } from '../../../../services/api/types';
import { upscaleModelChanged } from '../../store/upscaleSlice';
const ParamSpandrelModel = () => {
const { t } = useTranslation();
const [modelConfigs, { isLoading }] = useSpandrelImageToImageModels();
const model = useAppSelector((s) => s.upscale.upscaleModel);
const dispatch = useAppDispatch();
const _onChange = useCallback(
(v: SpandrelImageToImageModelConfig | null) => {
dispatch(upscaleModelChanged(v));
},
[dispatch]
);
const { options, value, onChange, placeholder, noOptionsMessage } = useModelCombobox({
modelConfigs,
onChange: _onChange,
selectedModel: model,
isLoading,
});
return (
<FormControl orientation="vertical">
<FormLabel>Upscale Model</FormLabel>
<Combobox
value={value}
placeholder={placeholder}
options={options}
onChange={onChange}
noOptionsMessage={noOptionsMessage}
isClearable
/>
</FormControl>
);
};
export default memo(ParamSpandrelModel);

View File

@ -17,7 +17,8 @@ import { useTranslation } from 'react-i18next';
import { PiFrameCornersBold } from 'react-icons/pi';
import type { ImageDTO } from 'services/api/types';
import ParamESRGANModel from './ParamRealESRGANModel';
import ParamSpandrelModel from './ParamSpandrelModel';
import { useSpandrelImageToImageModels } from '../../../../services/api/hooks/modelsByType';
type Props = { imageDTO?: ImageDTO };
@ -28,6 +29,7 @@ const ParamUpscalePopover = (props: Props) => {
const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure();
const { isAllowedToUpscale, detail } = useIsAllowedToUpscale(imageDTO);
const [modelConfigs] = useSpandrelImageToImageModels();
const handleClickUpscale = useCallback(() => {
onClose();
@ -45,16 +47,17 @@ const ParamUpscalePopover = (props: Props) => {
onClick={onOpen}
icon={<PiFrameCornersBold />}
aria-label={t('parameters.upscale')}
isDisabled={!modelConfigs.length}
/>
</PopoverTrigger>
<PopoverContent>
<PopoverBody minW={96}>
<Flex flexDirection="column" gap={4}>
<ParamESRGANModel />
<ParamSpandrelModel />
<Button
tooltip={detail}
size="sm"
isDisabled={!imageDTO || inProgress || !isAllowedToUpscale}
isDisabled={!imageDTO || inProgress || !isAllowedToUpscale || !modelConfigs.length}
onClick={handleClickUpscale}
>
{t('parameters.upscaleImage')}

View File

@ -5,6 +5,7 @@ import { selectConfigSlice } from 'features/system/store/configSlice';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import type { ImageDTO } from 'services/api/types';
import { useSpandrelImageToImageModels } from '../../../services/api/hooks/modelsByType';
const getUpscaledPixels = (imageDTO?: ImageDTO, maxUpscalePixels?: number) => {
if (!imageDTO) {

View File

@ -10,7 +10,7 @@ import type {
ParameterSeed,
ParameterSteps,
ParameterStrength,
ParameterVAEModel,
ParameterVAEModel
} from 'features/parameters/types/parameterSchemas';
import type { RgbaColor } from 'react-colorful';

View File

@ -0,0 +1,50 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { ParameterSpandrelImageToImageModel } from '../types/parameterSchemas';
import { ImageDTO } from '../../../services/api/types';
interface UpscaleState {
_version: 1;
upscaleModel: ParameterSpandrelImageToImageModel | null;
upscaleInitialImage: ImageDTO | null;
}
const initialUpscaleState: UpscaleState = {
_version: 1,
upscaleModel: null,
upscaleInitialImage: null
};
export const upscaleSlice = createSlice({
name: 'upscale',
initialState: initialUpscaleState,
reducers: {
upscaleModelChanged: (state, action: PayloadAction<ParameterSpandrelImageToImageModel | null>) => {
state.upscaleModel = action.payload;
},
upscaleInitialImageChanged: (state, action: PayloadAction<ImageDTO | null>) => {
state.upscaleInitialImage = action.payload;
},
},
});
export const { upscaleModelChanged, upscaleInitialImageChanged } = upscaleSlice.actions;
export const selectUpscalelice = (state: RootState) => state.upscale;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateUpscaleState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};
export const upscalePersistConfig: PersistConfig<UpscaleState> = {
name: upscaleSlice.name,
initialState: initialUpscaleState,
migrate: migrateUpscaleState,
persistDenylist: [],
};

View File

@ -126,6 +126,11 @@ const zParameterT2IAdapterModel = zModelIdentifierField;
export type ParameterT2IAdapterModel = z.infer<typeof zParameterT2IAdapterModel>;
// #endregion
// #region VAE Model
export const zParameterSpandrelImageToImageModel = zModelIdentifierField;
export type ParameterSpandrelImageToImageModel = z.infer<typeof zParameterSpandrelImageToImageModel>;
// #endregion
// #region Strength (l2l strength)
export const zParameterStrength = z.number().min(0).max(1);
export type ParameterStrength = z.infer<typeof zParameterStrength>;

View File

@ -0,0 +1,55 @@
import { Flex } from '@invoke-ai/ui-library';
import { useCallback, useMemo } from 'react';
import IAIDndImage from '../../../../common/components/IAIDndImage';
import { PostUploadAction } from '../../../../services/api/types';
import { TypesafeDroppableData } from '../../../dnd/types';
import { useAppDispatch, useAppSelector } from '../../../../app/store/storeHooks';
import { t } from 'i18next';
import { PiArrowCounterClockwiseBold } from 'react-icons/pi';
import IAIDndImageIcon from '../../../../common/components/IAIDndImageIcon';
import { upscaleInitialImageChanged } from '../../../parameters/store/upscaleSlice';
export const UpscaleInitialImage = () => {
const dispatch = useAppDispatch();
const imageDTO = useAppSelector((s) => s.upscale.upscaleInitialImage);
const droppableData = useMemo<TypesafeDroppableData | undefined>(
() => ({
actionType: 'SET_UPSCALE_INITIAL_IMAGE',
id: 'upscale-intial-image',
}),
[]
);
const postUploadAction = useMemo<PostUploadAction>(
() => ({
type: 'SET_UPSCALE_INITIAL_IMAGE',
}),
[]
);
const onReset = useCallback(() => {
dispatch(upscaleInitialImageChanged(null));
}, [dispatch]);
return (
<Flex justifyContent="flex-start">
<Flex position="relative" w={36} h={36} alignItems="center" justifyContent="center">
<IAIDndImage
droppableData={droppableData}
imageDTO={imageDTO || undefined}
postUploadAction={postUploadAction}
/>
{imageDTO && (
<Flex position="absolute" flexDir="column" top={1} insetInlineEnd={1} gap={1}>
<IAIDndImageIcon
onClick={onReset}
icon={<PiArrowCounterClockwiseBold size={16} />}
tooltip={t('controlnet.resetControlImage')}
/>
</Flex>
)}
</Flex>
</Flex>
);
};

View File

@ -0,0 +1,27 @@
import { Flex, StandaloneAccordion } from '@invoke-ai/ui-library';
import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import ParamSpandrelModel from '../../../parameters/components/Upscale/ParamSpandrelModel';
import { UpscaleInitialImage } from './UpscaleInitialImage';
export const UpscaleSettingsAccordion = memo(() => {
const { t } = useTranslation();
const { isOpen: isOpenAccordion, onToggle: onToggleAccordion } = useStandaloneAccordionToggle({
id: 'upscale-settings',
defaultIsOpen: true,
});
return (
<StandaloneAccordion label="Upscale" isOpen={isOpenAccordion} onToggle={onToggleAccordion}>
<Flex p={4} w="full" h="full" flexDir="column" data-testid="image-settings-accordion">
<Flex gap={4}>
<UpscaleInitialImage />
<ParamSpandrelModel />
</Flex>
</Flex>
</StandaloneAccordion>
);
});
UpscaleSettingsAccordion.displayName = 'UpscaleSettingsAccordion';

View File

@ -37,6 +37,7 @@ import { Panel, PanelGroup } from 'react-resizable-panels';
import ParametersPanelCanvas from './ParametersPanels/ParametersPanelCanvas';
import ResizeHandle from './tabs/ResizeHandle';
import UpscalingTab from './tabs/UpscalingTab';
import ParametersPanelUpscale from './ParametersPanels/ParametersPanelUpscale';
type TabData = {
id: InvokeTabName;
@ -66,6 +67,7 @@ const TAB_DATA: Record<InvokeTabName, TabData> = {
translationKey: 'ui.tabs.upscaling',
icon: <MdZoomOutMap />,
content: <UpscalingTab />,
parametersPanel: <ParametersPanelUpscale />,
},
workflows: {
id: 'workflows',

View File

@ -0,0 +1,44 @@
import { Box, Flex, Switch } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
import { Prompts } from 'features/parameters/components/Prompts/Prompts';
import QueueControls from 'features/queue/components/QueueControls';
import { SDXLPrompts } from 'features/sdxl/components/SDXLPrompts/SDXLPrompts';
import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion';
import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import type { CSSProperties } from 'react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { UpscaleSettingsAccordion } from '../../../settingsAccordions/components/UpscaleSettingsAccordion/UpscaleSettingsAccordion';
const overlayScrollbarsStyles: CSSProperties = {
height: '100%',
width: '100%',
};
const ParametersPanelUpscale = () => {
const { t } = useTranslation();
const isSDXL = useAppSelector((s) => s.generation.model?.base === 'sdxl');
return (
<Flex w="full" h="full" flexDir="column" gap={2}>
<QueueControls />
<Flex w="full" h="full" position="relative">
<Box position="absolute" top={0} left={0} right={0} bottom={0}>
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
<Flex gap={2} flexDirection="column" h="full" w="full">
<UpscaleSettingsAccordion />
{isSDXL ? <SDXLPrompts /> : <Prompts />}
<GenerationSettingsAccordion />
<AdvancedSettingsAccordion />
</Flex>
</OverlayScrollbarsComponent>
</Box>
</Flex>
</Flex>
);
};
export default memo(ParametersPanelUpscale);

View File

@ -205,6 +205,10 @@ type CanvasInitialImageAction = {
type: 'SET_CANVAS_INITIAL_IMAGE';
};
type UpscaleInitialImageAction = {
type: 'SET_UPSCALE_INITIAL_IMAGE';
};
type ToastAction = {
type: 'TOAST';
title?: string;
@ -223,4 +227,5 @@ export type PostUploadAction =
| CALayerImagePostUploadAction
| IPALayerImagePostUploadAction
| RGLayerIPAdapterImagePostUploadAction
| IILayerImagePostUploadAction;
| IILayerImagePostUploadAction
| UpscaleInitialImageAction;