From d0435575c1024f753fe79817c07fc8a83fb189f6 Mon Sep 17 00:00:00 2001 From: Eugene Brodsky Date: Sun, 21 Jul 2024 23:18:36 -0400 Subject: [PATCH 01/55] chore(deps): bump fastapi-events to the next minor version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9953c1c1a04..9acaa17e44d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ dependencies = [ "transformers==4.41.1", # Core application dependencies, pinned for reproducible builds. - "fastapi-events==0.11.0", + "fastapi-events==0.11.1", "fastapi==0.111.0", "huggingface-hub==0.23.1", "pydantic-settings==2.2.1", From 339dddd018442c4efe49686be871f09724ca476f Mon Sep 17 00:00:00 2001 From: chainchompa Date: Mon, 22 Jul 2024 16:03:01 -0400 Subject: [PATCH 02/55] update uncategorized board totals when deleting and moving images --- .../web/src/services/api/endpoints/images.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/invokeai/frontend/web/src/services/api/endpoints/images.ts b/invokeai/frontend/web/src/services/api/endpoints/images.ts index 2040021d6d4..06ec9fc162c 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/images.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/images.ts @@ -104,6 +104,10 @@ export const imagesApi = api.injectEndpoints({ type: 'Board', id: boardId, }, + { + type: 'BoardImagesTotal', + id: boardId, + }, ]; }, }), @@ -136,6 +140,10 @@ export const imagesApi = api.injectEndpoints({ type: 'Board', id: boardId, }, + { + type: 'BoardImagesTotal', + id: boardId, + }, ]; return tags; @@ -169,6 +177,10 @@ export const imagesApi = api.injectEndpoints({ type: 'Board', id: boardId, }, + { + type: 'BoardImagesTotal', + id: boardId, + }, ]; }, }), @@ -300,6 +312,10 @@ export const imagesApi = api.injectEndpoints({ type: 'Board', id: boardId, }, + { + type: 'BoardImagesTotal', + id: boardId, + }, ]; }, }), @@ -362,6 +378,10 @@ export const imagesApi = api.injectEndpoints({ }, { type: 'Board', id: board_id }, { type: 'Board', id: imageDTO.board_id ?? 'none' }, + { + type: 'BoardImagesTotal', + id: imageDTO.board_id ?? 'none', + }, ]; }, }), @@ -393,6 +413,10 @@ export const imagesApi = api.injectEndpoints({ }, { type: 'Board', id: imageDTO.board_id ?? 'none' }, { type: 'Board', id: 'none' }, + { + type: 'BoardImagesTotal', + id: imageDTO.board_id ?? 'none', + }, ]; }, }), @@ -434,6 +458,10 @@ export const imagesApi = api.injectEndpoints({ tags.push({ type: 'Image', id: imageDTO.image_name }); } tags.push({ type: 'Board', id: board_id }); + tags.push({ + type: 'BoardImagesTotal', + id: board_id ?? 'none', + }); return tags; }, }), @@ -480,6 +508,10 @@ export const imagesApi = api.injectEndpoints({ } tags.push({ type: 'Image', id: image_name }); tags.push({ type: 'Board', id: board_id }); + tags.push({ + type: 'BoardImagesTotal', + id: board_id ?? 'none', + }); }); return tags; From e92af52fb8bf227c8fca6a94265aba39035613ad Mon Sep 17 00:00:00 2001 From: chainchompa Date: Mon, 22 Jul 2024 16:11:36 -0400 Subject: [PATCH 03/55] fix moving items to uncategorized updating --- invokeai/frontend/web/src/services/api/endpoints/images.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/invokeai/frontend/web/src/services/api/endpoints/images.ts b/invokeai/frontend/web/src/services/api/endpoints/images.ts index 06ec9fc162c..6f36866dce6 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/images.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/images.ts @@ -417,6 +417,7 @@ export const imagesApi = api.injectEndpoints({ type: 'BoardImagesTotal', id: imageDTO.board_id ?? 'none', }, + { type: 'BoardImagesTotal', id: 'none' }, ]; }, }), From 43b3e242b0b61ba74c2733bd19ba156b47c59590 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Tue, 16 Jul 2024 15:58:40 -0400 Subject: [PATCH 04/55] tidy(ui): refactor parameters panel components to be 1:1 with tabs --- .../src/features/ui/components/InvokeTabs.tsx | 37 +++++++++---------- .../ParametersPanelCanvas.tsx} | 10 ++--- .../ParametersPanelTextToImage.tsx | 0 .../ui/components/tabs/UpscalingTab.tsx | 15 ++++++++ .../web/src/features/ui/store/tabMap.tsx | 2 +- 5 files changed, 37 insertions(+), 27 deletions(-) rename invokeai/frontend/web/src/features/ui/components/{ParametersPanel.tsx => ParametersPanels/ParametersPanelCanvas.tsx} (86%) rename invokeai/frontend/web/src/features/ui/components/{ => ParametersPanels}/ParametersPanelTextToImage.tsx (100%) create mode 100644 invokeai/frontend/web/src/features/ui/components/tabs/UpscalingTab.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx index 1968c64161f..2774079ed47 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -11,7 +11,7 @@ import StatusIndicator from 'features/system/components/StatusIndicator'; import { selectConfigSlice } from 'features/system/store/configSlice'; import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton'; import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons'; -import ParametersPanelTextToImage from 'features/ui/components/ParametersPanelTextToImage'; +import ParametersPanelTextToImage from 'features/ui/components/ParametersPanels/ParametersPanelTextToImage'; import ModelManagerTab from 'features/ui/components/tabs/ModelManagerTab'; import NodesTab from 'features/ui/components/tabs/NodesTab'; import QueueTab from 'features/ui/components/tabs/QueueTab'; @@ -28,19 +28,22 @@ import type { CSSProperties, MouseEvent, ReactElement, ReactNode } from 'react'; import { memo, useCallback, useMemo, useRef } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; +import { MdZoomOutMap } from 'react-icons/md'; import { PiFlowArrowBold } from 'react-icons/pi'; import { RiBox2Line, RiBrushLine, RiInputMethodLine, RiPlayList2Fill } from 'react-icons/ri'; import type { ImperativePanelGroupHandle } from 'react-resizable-panels'; import { Panel, PanelGroup } from 'react-resizable-panels'; -import ParametersPanel from './ParametersPanel'; +import ParametersPanelCanvas from './ParametersPanels/ParametersPanelCanvas'; import ResizeHandle from './tabs/ResizeHandle'; +import UpscalingTab from './tabs/UpscalingTab'; type TabData = { id: InvokeTabName; translationKey: string; icon: ReactElement; content: ReactNode; + parametersPanel?: ReactNode; }; const TAB_DATA: Record = { @@ -49,18 +52,27 @@ const TAB_DATA: Record = { translationKey: 'ui.tabs.generation', icon: , content: , + parametersPanel: , }, canvas: { id: 'canvas', translationKey: 'ui.tabs.canvas', icon: , content: , + parametersPanel: , + }, + upscaling: { + id: 'upscaling', + translationKey: 'ui.tabs.upscaling', + icon: , + content: , }, workflows: { id: 'workflows', translationKey: 'ui.tabs.workflows', icon: , content: , + parametersPanel: , }, models: { id: 'models', @@ -81,7 +93,6 @@ const enabledTabsSelector = createMemoizedSelector(selectConfigSlice, (config) = ); const NO_GALLERY_PANEL_TABS: InvokeTabName[] = ['models', 'queue']; -const NO_OPTIONS_PANEL_TABS: InvokeTabName[] = ['models', 'queue']; const panelStyles: CSSProperties = { height: '100%', width: '100%' }; const GALLERY_MIN_SIZE_PX = 310; const GALLERY_MIN_SIZE_PCT = 20; @@ -103,7 +114,6 @@ const InvokeTabs = () => { e.target.blur(); } }, []); - const shouldShowOptionsPanel = useMemo(() => !NO_OPTIONS_PANEL_TABS.includes(activeTabName), [activeTabName]); const shouldShowGalleryPanel = useMemo(() => !NO_GALLERY_PANEL_TABS.includes(activeTabName), [activeTabName]); const tabs = useMemo( @@ -232,7 +242,7 @@ const InvokeTabs = () => { style={panelStyles} storage={panelStorage} > - {shouldShowOptionsPanel && ( + {!!TAB_DATA[activeTabName].parametersPanel && ( <> { onExpand={optionsPanel.onExpand} collapsible > - + {TAB_DATA[activeTabName].parametersPanel} { )} - {shouldShowOptionsPanel && } + {!!TAB_DATA[activeTabName].parametersPanel && } {shouldShowGalleryPanel && } ); }; export default memo(InvokeTabs); - -const ParametersPanelComponent = memo(() => { - const activeTabName = useAppSelector(activeTabNameSelector); - - if (activeTabName === 'workflows') { - return ; - } - if (activeTabName === 'generation') { - return ; - } - return ; -}); -ParametersPanelComponent.displayName = 'ParametersPanelComponent'; diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelCanvas.tsx similarity index 86% rename from invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx rename to invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelCanvas.tsx index e8f73fd7868..622ed966961 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelCanvas.tsx @@ -10,7 +10,6 @@ import { ControlSettingsAccordion } from 'features/settingsAccordions/components import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion'; import { ImageSettingsAccordion } from 'features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion'; import { RefinerSettingsAccordion } from 'features/settingsAccordions/components/RefinerSettingsAccordion/RefinerSettingsAccordion'; -import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import type { CSSProperties } from 'react'; import { memo } from 'react'; @@ -20,8 +19,7 @@ const overlayScrollbarsStyles: CSSProperties = { width: '100%', }; -const ParametersPanel = () => { - const activeTabName = useAppSelector(activeTabNameSelector); +const ParametersPanelCanvas = () => { const isSDXL = useAppSelector((s) => s.generation.model?.base === 'sdxl'); return ( @@ -34,8 +32,8 @@ const ParametersPanel = () => { {isSDXL ? : } - {activeTabName !== 'generation' && } - {activeTabName === 'canvas' && } + + {isSDXL && } @@ -46,4 +44,4 @@ const ParametersPanel = () => { ); }; -export default memo(ParametersPanel); +export default memo(ParametersPanelCanvas); diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelTextToImage.tsx similarity index 100% rename from invokeai/frontend/web/src/features/ui/components/ParametersPanelTextToImage.tsx rename to invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelTextToImage.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UpscalingTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UpscalingTab.tsx new file mode 100644 index 00000000000..8d4c916c2aa --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UpscalingTab.tsx @@ -0,0 +1,15 @@ +import { Box } from '@invoke-ai/ui-library'; +import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer'; +import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer'; +import { memo } from 'react'; + +const UpscalingTab = () => { + const imageViewer = useImageViewer(); + return ( + + {imageViewer.isOpen && } + + ); +}; + +export default memo(UpscalingTab); diff --git a/invokeai/frontend/web/src/features/ui/store/tabMap.tsx b/invokeai/frontend/web/src/features/ui/store/tabMap.tsx index 526a55b069f..5cf97b2d3e2 100644 --- a/invokeai/frontend/web/src/features/ui/store/tabMap.tsx +++ b/invokeai/frontend/web/src/features/ui/store/tabMap.tsx @@ -1,3 +1,3 @@ -export const TAB_NUMBER_MAP = ['generation', 'canvas', 'workflows', 'models', 'queue'] as const; +export const TAB_NUMBER_MAP = ['generation', 'canvas', 'upscaling', 'workflows', 'models', 'queue'] as const; export type InvokeTabName = (typeof TAB_NUMBER_MAP)[number]; From a0a54348e8e704a45fd5179c4f4d4d48199b7be3 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Wed, 17 Jul 2024 10:50:33 -0400 Subject: [PATCH 05/55] removed upscale button, created spandrel model dropdown, created upscale initial image that works with dnd --- .../listeners/imageDropped.ts | 15 +++++ .../listeners/imageUploaded.ts | 10 ++++ .../listeners/modelsLoaded.ts | 25 ++++++++- invokeai/frontend/web/src/app/store/store.ts | 3 + .../src/common/hooks/useFullscreenDropzone.ts | 4 ++ .../web/src/features/dnd/types/index.ts | 19 +++++-- .../web/src/features/dnd/util/isValidDrop.ts | 2 + .../ImageViewer/CurrentImageButtons.tsx | 27 +-------- .../Upscale/ParamRealESRGANModel.tsx | 2 +- .../components/Upscale/ParamSpandrelModel.tsx | 47 ++++++++++++++++ .../Upscale/ParamUpscaleSettings.tsx | 9 ++- .../parameters/hooks/useIsAllowedToUpscale.ts | 1 + .../src/features/parameters/store/types.ts | 2 +- .../features/parameters/store/upscaleSlice.ts | 50 +++++++++++++++++ .../parameters/types/parameterSchemas.ts | 5 ++ .../UpscaleInitialImage.tsx | 55 +++++++++++++++++++ .../UpscaleSettingsAccordion.tsx | 27 +++++++++ .../src/features/ui/components/InvokeTabs.tsx | 2 + .../ParametersPanelUpscale.tsx | 44 +++++++++++++++ .../frontend/web/src/services/api/types.ts | 7 ++- 20 files changed, 317 insertions(+), 39 deletions(-) create mode 100644 invokeai/frontend/web/src/features/parameters/components/Upscale/ParamSpandrelModel.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/store/upscaleSlice.ts create mode 100644 invokeai/frontend/web/src/features/settingsAccordions/components/UpscaleSettingsAccordion/UpscaleInitialImage.tsx create mode 100644 invokeai/frontend/web/src/features/settingsAccordions/components/UpscaleSettingsAccordion/UpscaleSettingsAccordion.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelUpscale.tsx diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts index 0cd77dc2e75..eac8922a796 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts @@ -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 */ diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts index bd1ee478255..5a4d5dc0784 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts @@ -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( diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts index eb86f54c84f..eef1f8f49f3 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts @@ -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)) + +}; diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 062cdc1cbf4..804d3629ab0 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -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) => { diff --git a/invokeai/frontend/web/src/common/hooks/useFullscreenDropzone.ts b/invokeai/frontend/web/src/common/hooks/useFullscreenDropzone.ts index 5b1bf1f5b39..d8e7d70a8c5 100644 --- a/invokeai/frontend/web/src/common/hooks/useFullscreenDropzone.ts +++ b/invokeai/frontend/web/src/common/hooks/useFullscreenDropzone.ts @@ -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; }); diff --git a/invokeai/frontend/web/src/features/dnd/types/index.ts b/invokeai/frontend/web/src/features/dnd/types/index.ts index 6fcf18421ef..1e72123b75a 100644 --- a/invokeai/frontend/web/src/features/dnd/types/index.ts +++ b/invokeai/frontend/web/src/features/dnd/types/index.ts @@ -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 {} -interface DragMoveEvent extends DragEvent {} -interface DragOverEvent extends DragMoveEvent {} -export interface DragEndEvent extends DragEvent {} -interface DragCancelEvent extends DragEndEvent {} +export interface DragStartEvent extends Pick { } +interface DragMoveEvent extends DragEvent { } +interface DragOverEvent extends DragMoveEvent { } +export interface DragEndEvent extends DragEvent { } +interface DragCancelEvent extends DragEndEvent { } export interface DndContextTypesafeProps extends Omit { diff --git a/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts b/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts index 6dec862345a..3f8fe5ab734 100644 --- a/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts +++ b/invokeai/frontend/web/src/features/dnd/util/isValidDrop.ts @@ -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': diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx index d500d692fe7..da0253cc36d 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx @@ -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 = () => { /> - {isUpscalingEnabled && ( - - {isUpscalingEnabled && } - - )} - diff --git a/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamRealESRGANModel.tsx b/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamRealESRGANModel.tsx index 6c1a3ab3a7e..d02bfd2b038 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamRealESRGANModel.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamRealESRGANModel.tsx @@ -63,7 +63,7 @@ const ParamESRGANModel = () => { return ( - {t('models.esrganModel')} + {t('models.esrganModel')} ); diff --git a/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamSpandrelModel.tsx b/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamSpandrelModel.tsx new file mode 100644 index 00000000000..b16a77feaea --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamSpandrelModel.tsx @@ -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 ( + + Upscale Model + + + ); +}; + +export default memo(ParamSpandrelModel); diff --git a/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamUpscaleSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamUpscaleSettings.tsx index c0309bebe40..15fec1d214e 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamUpscaleSettings.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamUpscaleSettings.tsx @@ -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={} aria-label={t('parameters.upscale')} + isDisabled={!modelConfigs.length} /> - +