From dae2b80b4fa5d3a48867e5e19fc39ed13f09c1d3 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Mon, 13 Oct 2025 13:54:45 +1100
Subject: [PATCH 01/20] tidy: removing unused code paths 1
---
invokeai/app/api/routers/app_info.py | 3 -
invokeai/app/api/routers/board_videos.py | 39 -
invokeai/app/api/routers/videos.py | 119 -
invokeai/app/api/routers/workflows.py | 2 -
invokeai/app/api_app.py | 4 -
invokeai/app/invocations/fields.py | 25 -
invokeai/app/invocations/primitives.py | 25 -
invokeai/app/services/boards/boards_common.py | 5 +-
.../app/services/boards/boards_default.py | 12 +-
invokeai/app/services/videos_common.py | 179 -
.../workflow_records/workflow_records_base.py | 3 -
.../workflow_records_common.py | 2 -
.../workflow_records_sqlite.py | 3 -
.../backend/model_manager/configs/base.py | 15 +-
.../backend/model_manager/configs/factory.py | 16 -
.../backend/model_manager/configs/main.py | 46 -
invokeai/backend/model_manager/taxonomy.py | 17 -
invokeai/frontend/web/.storybook/preview.tsx | 2 -
.../frontend/web/src/app/components/App.tsx | 17 +-
.../components/AppErrorBoundaryFallback.tsx | 28 +-
.../src/app/components/GlobalHookIsolator.tsx | 84 +-
.../app/components/GlobalModalIsolator.tsx | 6 -
.../web/src/app/components/InvokeAIUI.tsx | 296 +-
.../frontend/web/src/app/components/types.ts | 43 -
.../web/src/app/hooks/useStudioInitAction.ts | 262 -
.../frontend/web/src/app/logging/logger.ts | 9 +
.../store/enhancers/reduxRemember/driver.ts | 29 +-
.../store/enhancers/reduxRemember/errors.ts | 7 +-
.../listeners/boardIdSelected.ts | 69 +-
.../listeners/getOpenAPISchema.ts | 5 +-
.../listeners/imageUploaded.ts | 43 +-
.../listeners/modelSelected.ts | 27 +-
.../listeners/modelsLoaded.ts | 33 -
.../listeners/socketConnected.ts | 13 +-
.../store/nanostores/accountSettingsLink.ts | 3 -
.../app/store/nanostores/accountTypeText.ts | 3 -
.../web/src/app/store/nanostores/authToken.ts | 11 -
.../web/src/app/store/nanostores/baseUrl.ts | 6 -
.../store/nanostores/customNavComponent.ts | 4 -
.../src/app/store/nanostores/customStarUI.ts | 14 -
.../src/app/store/nanostores/isDebugging.ts | 3 -
.../web/src/app/store/nanostores/logo.ts | 4 -
.../nanostores/onClickGoToModelManager.ts | 3 -
.../app/store/nanostores/openAPISchemaUrl.ts | 3 -
.../web/src/app/store/nanostores/projectId.ts | 9 -
.../web/src/app/store/nanostores/queueId.ts | 5 -
.../web/src/app/store/nanostores/toastMap.ts | 4 -
.../store/nanostores/videoUpsellComponent.ts | 4 -
.../web/src/app/store/nanostores/whatsNew.ts | 4 -
invokeai/frontend/web/src/app/store/store.ts | 10 -
.../frontend/web/src/app/types/invokeai.ts | 12 +-
.../src/common/hooks/useClientSideUpload.ts | 121 -
.../common/hooks/useCopyImageToClipboard.ts | 6 +-
.../web/src/common/hooks/useDownloadImage.ts | 18 +-
.../web/src/common/hooks/useGlobalHotkeys.ts | 26 +-
.../src/common/hooks/useImageUploadButton.tsx | 40 +-
.../src/common/util/convertImageUrlToBlob.ts | 4 +-
.../components/ChangeBoardModal.tsx | 24 +-
.../CanvasAlertsInvocationProgress.tsx | 8 +-
.../components/Filters/FilterTypeSelect.tsx | 14 +-
.../components/ParamDenoisingStrength.tsx | 32 +-
.../components/common/Weight.tsx | 36 +-
.../controlLayers/hooks/addLayerHooks.ts | 17 +-
.../hooks/useIsEntityTypeEnabled.ts | 26 +-
.../konva/CanvasBackgroundModule.ts | 3 +-
.../CanvasEntityObjectRenderer.ts | 3 +-
.../konva/CanvasObject/CanvasObjectImage.ts | 4 +-
.../konva/CanvasTool/CanvasBboxToolModule.ts | 12 +-
.../src/features/controlLayers/konva/util.ts | 16 +-
.../controlLayers/store/canvasSlice.ts | 48 -
.../controlLayers/store/paramsSlice.ts | 67 -
.../controlLayers/store/refImagesSlice.ts | 43 +-
.../src/features/controlLayers/store/types.ts | 74 -
.../web/src/features/cropper/lib/editor.ts | 3 +-
.../hooks/use-delete-video.ts | 28 -
.../components/DeleteVideoButton.tsx | 36 -
.../components/DeleteVideoModal.tsx | 43 -
.../features/deleteVideoModal/store/state.ts | 111 -
.../dnd/DndDragPreviewMultipleVideo.tsx | 63 -
.../dnd/DndDragPreviewSingleVideo.tsx | 69 -
.../web/src/features/dnd/DndImage.tsx | 4 -
.../src/features/dnd/FullscreenDropzone.tsx | 32 +-
invokeai/frontend/web/src/features/dnd/dnd.ts | 135 +-
.../web/src/features/dnd/useDndMonitor.ts | 15 +-
.../ParamDynamicPromptsMaxPrompts.tsx | 24 +-
.../hooks/useDynamicPromptsWatcher.tsx | 9 +-
.../components/Boards/BoardContextMenu.tsx | 12 +-
.../Boards/BoardsList/AddBoardButton.tsx | 29 +-
.../Boards/BoardsList/BoardTooltip.tsx | 4 -
.../Boards/BoardsList/BoardsList.tsx | 66 +-
.../Boards/BoardsList/BoardsListWrapper.tsx | 6 +-
.../Boards/BoardsList/GalleryBoard.tsx | 6 +-
.../Boards/BoardsList/NoBoardBoard.tsx | 24 +-
.../Boards/NoBoardBoardContextMenu.tsx | 11 +-
.../components/BoardsListPanelContent.tsx | 4 -
.../MenuItems/ContextMenuItemChangeBoard.tsx | 14 +-
.../MenuItems/ContextMenuItemCopy.tsx | 13 +-
.../MenuItems/ContextMenuItemDeleteImage.tsx | 11 +-
.../MenuItems/ContextMenuItemDeleteVideo.tsx | 35 -
.../MenuItems/ContextMenuItemDownload.tsx | 13 +-
.../MenuItems/ContextMenuItemLoadWorkflow.tsx | 20 +-
.../ContextMenuItemLocateInGalery.tsx | 31 +-
...etadataRecallActionsCanvasGenerateTabs.tsx | 18 +-
...enuItemMetadataRecallActionsUpscaleTab.tsx | 10 +-
...ntextMenuItemNewCanvasFromImageSubMenu.tsx | 4 +-
...ontextMenuItemNewLayerFromImageSubMenu.tsx | 10 +-
.../MenuItems/ContextMenuItemOpenInNewTab.tsx | 17 +-
.../MenuItems/ContextMenuItemOpenInViewer.tsx | 17 +-
.../ContextMenuItemSelectForCompare.tsx | 20 +-
.../ContextMenuItemSendToUpscale.tsx | 4 +-
.../MenuItems/ContextMenuItemSendToVideo.tsx | 28 -
.../MenuItems/ContextMenuItemStarUnstar.tsx | 37 +-
.../ContextMenuItemUseAsPromptTemplate.tsx | 4 +-
.../ContextMenuItemUseAsRefImage.tsx | 4 +-
.../ContextMenuItemUseForPromptGeneration.tsx | 46 -
.../MultipleSelectionMenuItems.tsx | 22 +-
.../MultipleSelectionVideoMenuItems.tsx | 58 -
.../ContextMenu/SingleSelectionMenuItems.tsx | 12 +-
.../SingleSelectionVideoMenuItems.tsx | 33 -
.../ContextMenu/VideoContextMenu.tsx | 279 -
.../features/gallery/components/Gallery.tsx | 19 +-
.../gallery/components/GalleryHeader.tsx | 21 -
.../components/ImageGrid/GalleryImage.tsx | 2 +-
.../ImageGrid/GalleryItemDeleteIconButton.tsx | 20 +-
.../ImageGrid/GalleryItemHoverIcons.tsx | 14 +-
.../GalleryItemOpenInViewerIconButton.tsx | 17 +-
.../ImageGrid/GalleryItemSizeBadge.tsx | 8 +-
.../ImageGrid/GalleryItemStarIconButton.tsx | 45 +-
.../components/ImageGrid/GalleryVideo.tsx | 218 -
.../ImageGrid/GalleryVideoPlaceholder.tsx | 11 -
.../ImageMetadataActions.tsx | 23 -
.../VideoMetadataViewer.tsx | 80 -
.../ImageViewer/CurrentImageButtons.tsx | 5 +-
.../ImageViewer/CurrentVideoButtons.tsx | 116 -
.../ImageViewer/CurrentVideoPreview.tsx | 85 -
.../ImageViewer/ImageComparisonHover.tsx | 6 -
.../ImageViewer/ImageComparisonSideBySide.tsx | 5 -
.../ImageViewer/ImageComparisonSlider.tsx | 5 -
.../ImageViewer/ImageViewerPanel.tsx | 4 +-
.../ImageViewer/NoContentForViewer.tsx | 47 +-
.../components/ImageViewer/VideoViewer.tsx | 28 -
.../ImageViewer/VideoViewerToolbar.tsx | 22 -
.../components/NextPrevItemButtons.tsx | 24 +-
.../gallery/components/VideoGallery.tsx | 390 -
.../components/use-gallery-video-ids.ts | 21 -
.../gallery/contexts/ImageDTOContext.ts | 13 +
.../gallery/contexts/ItemDTOContext.ts | 20 -
.../hooks/useRangeBasedVideoFetching.ts | 78 -
.../web/src/features/gallery/store/actions.ts | 16 -
.../gallery/store/gallerySelectors.ts | 13 +-
.../web/src/features/imageActions/actions.ts | 13 -
.../features/lora/components/LoRASelect.tsx | 6 +-
.../web/src/features/metadata/parsing.tsx | 116 +-
.../hooks/useMainModelDefaultSettings.ts | 60 +-
.../hooks/useStarterModelsToast.tsx | 6 +-
.../web/src/features/modelManagerV2/models.ts | 81 +-
.../HuggingFaceFolder/HFToken.tsx | 5 +-
.../HuggingFaceFolder/HuggingFaceForm.tsx | 4 +-
.../ModelManagerPanel/ModelFormatBadge.tsx | 2 -
.../DefaultCfgRescaleMultiplier.tsx | 26 +-
.../DefaultCfgScale.tsx | 26 +-
.../DefaultGuidance.tsx | 30 +-
.../DefaultHeight.tsx | 25 +-
.../MainModelDefaultSettings/DefaultSteps.tsx | 26 +-
.../MainModelDefaultSettings/DefaultWidth.tsx | 25 +-
.../flow/AddNodeCmdk/AddNodeCmdk.tsx | 6 +-
.../features/nodes/components/flow/Flow.tsx | 53 +-
.../nodes/Invocation/InvocationNodeFooter.tsx | 4 +-
.../Invocation/fields/InputFieldHandle.tsx | 19 +-
.../Invocation/fields/OutputFieldHandle.tsx | 20 +-
.../flow/nodes/common/NodeWrapper.tsx | 3 -
.../nodes/common/NonInvocationNodeWrapper.tsx | 3 -
.../components/flow/nodes/common/shared.ts | 6 -
.../flow/panels/TopPanel/TopLeftPanel.tsx | 56 +-
.../flow/panels/TopPanel/TopRightPanel.tsx | 6 -
.../ActiveWorkflowNameAndActions.tsx | 4 +-
.../sidePanel/WorkflowsTabLeftPanel.tsx | 16 +-
.../IsolatedWorkflowBuilderWatcher.tsx | 3 +-
.../workflow/PublishWorkflowPanelContent.tsx | 475 -
.../WorkflowLibrary/ShareWorkflowModal.tsx | 93 -
.../ShareWorkflow.tsx | 35 -
.../WorkflowLibrarySideNav.tsx | 5 -
.../workflow/WorkflowLibrary/WorkflowList.tsx | 1 -
.../WorkflowLibrary/WorkflowListItem.tsx | 24 +-
.../sidePanel/workflow/WorkflowPanel.tsx | 5 -
.../components/sidePanel/workflow/publish.ts | 157 -
.../nodes/hooks/useIsWorkflowEditorLocked.ts | 15 -
.../src/features/nodes/hooks/useWithFooter.ts | 9 +-
.../src/features/nodes/store/nodesSlice.ts | 2 +-
.../web/src/features/nodes/types/common.ts | 35 +-
.../web/src/features/nodes/types/workflow.ts | 1 -
.../util/graph/buildLinearBatchConfig.ts | 8 +-
.../nodes/util/graph/generation/Graph.test.ts | 1 -
.../graph/generation/buildChatGPT4oGraph.ts | 143 -
.../graph/generation/buildFluxKontextGraph.ts | 124 -
.../graph/generation/buildGemini2_5Graph.ts | 81 -
.../graph/generation/buildImagen3Graph.ts | 76 -
.../graph/generation/buildImagen4Graph.ts | 75 -
.../graph/generation/buildRunwayVideoGraph.ts | 87 -
.../graph/generation/buildVeo3VideoGraph.ts | 89 -
.../Advanced/ParamCFGRescaleMultiplier.tsx | 32 +-
.../components/Advanced/ParamClipSkip.tsx | 26 +-
.../components/Bbox/BboxAspectRatioSelect.tsx | 27 +-
.../parameters/components/Bbox/BboxHeight.tsx | 29 +-
.../components/Bbox/BboxScaledHeight.tsx | 28 +-
.../components/Bbox/BboxScaledWidth.tsx | 28 +-
.../components/Bbox/BboxSettings.tsx | 30 +-
.../parameters/components/Bbox/BboxWidth.tsx | 29 +-
.../Bbox/use-is-bbox-size-locked.ts | 5 +-
.../ParamCanvasCoherenceEdgeSize.tsx | 32 +-
.../MaskAdjustment/ParamMaskBlur.tsx | 32 +-
.../ParamInfillPatchmatchDownscaleSize.tsx | 32 +-
.../InfillAndScaling/ParamInfillTilesize.tsx | 32 +-
.../components/Core/ParamCFGScale.tsx | 42 +-
.../components/Core/ParamGuidance.tsx | 50 +-
.../components/Core/ParamPositivePrompt.tsx | 31 +-
.../parameters/components/Core/ParamSteps.tsx | 42 +-
.../components/Dimensions/Dimensions.tsx | 30 +-
.../DimensionsAspectRatioSelect.tsx | 29 +-
.../Dimensions/DimensionsHeight.tsx | 34 +-
.../DimensionsLockAspectRatioButton.tsx | 8 +-
.../DimensionsSetOptimalSizeButton.tsx | 9 +-
.../components/Dimensions/DimensionsWidth.tsx | 36 +-
.../MainModel/DisabledModelWarning.tsx | 39 -
.../NavigateToModelManagerButton.tsx | 13 +-
.../parameters/components/ModelPicker.tsx | 59 +-
.../PixelDimensionsUnsupportedAlert.tsx | 14 -
.../parameters/components/Prompts/Prompts.tsx | 6 +-
.../Upscale/ParamUpscaleCFGScale.tsx | 42 +-
.../components/Video/ParamDuration.tsx | 56 -
.../components/Video/ParamResolution.tsx | 50 -
.../components/Video/VideoDimensions.tsx | 22 -
.../VideoDimensionsAspectRatioSelect.tsx | 54 -
.../Video/VideoDimensionsPreview.tsx | 88 -
.../hooks/useCurrentVideoDimensions.ts | 54 -
.../parameters/hooks/useIsModelDisabled.ts | 16 -
.../hooks/useIsTooLargeToUpscale.ts | 29 -
.../features/parameters/store/videoSlice.ts | 168 -
.../features/parameters/types/constants.ts | 20 -
.../parameters/types/parameterSchemas.ts | 9 +
.../parameters/util/optimalDimension.ts | 8 -
.../PromptExpansion/PromptExpansionMenu.tsx | 80 -
.../PromptExpansionOverlay.tsx | 68 -
.../PromptExpansionResultOverlay.tsx | 76 -
.../features/prompt/PromptExpansion/expand.ts | 42 -
.../features/prompt/PromptExpansion/graph.ts | 43 -
.../features/prompt/PromptExpansion/state.ts | 98 -
.../components/ClearModelCacheButton.tsx | 6 -
.../InvokeButtonTooltip.tsx | 15 +-
.../components/QueueActionsMenuButton.tsx | 60 +-
.../components/QueueIterationsNumberInput.tsx | 8 +-
.../QueueList/QueueItemComponent.tsx | 15 +-
.../components/QueueList/QueueItemDetail.tsx | 6 +-
.../components/QueueList/QueueListHeader.tsx | 11 -
.../queue/components/QueueTabContent.tsx | 5 +-
.../components/QueueTabQueueControls.tsx | 18 +-
.../features/queue/hooks/useEnqueueCanvas.ts | 15 -
.../queue/hooks/useEnqueueGenerate.ts | 15 -
.../features/queue/hooks/useEnqueueVideo.ts | 127 -
.../queue/hooks/useEnqueueWorkflows.ts | 68 +-
.../web/src/features/queue/hooks/useInvoke.ts | 18 +-
.../web/src/features/queue/store/readiness.ts | 161 +-
.../SDXLRefiner/ParamSDXLRefinerCFGScale.tsx | 42 +-
.../SDXLRefiner/ParamSDXLRefinerSteps.tsx | 43 +-
.../GenerationSettingsAccordion.tsx | 47 +-
.../UpscaleTabGenerationSettingsAccordion.tsx | 42 +-
.../CanvasTabImageSettingsAccordion.tsx | 100 +-
.../GenerateTabImageSettingsAccordion.tsx | 54 +-
.../UpscaleWarning.tsx | 33 +-
.../VideoSettingsAccordion/CreditEstimate.tsx | 55 -
.../StartingFrameImage.tsx | 182 -
.../VideoModelPicker.tsx | 40 -
.../VideoSettingsAccordion.tsx | 45 -
.../StylePresetForm/StylePresetForm.tsx | 6 +-
.../components/StylePresetImage.tsx | 6 -
.../components/StylePresetMenu.tsx | 5 -
.../components/HotkeysModal/useHotkeyData.ts | 30 +-
.../components/InvokeAILogoComponent.tsx | 7 -
.../SettingsModal/SettingsLanguageSelect.tsx | 4 +-
.../components/SettingsModal/SettingsMenu.tsx | 42 +-
.../features/system/hooks/useFeatureStatus.ts | 24 -
.../features/system/store/configSelectors.ts | 4 -
.../src/features/system/store/configSlice.ts | 101 -
.../features/toast/ErrorToastDescription.tsx | 62 +-
.../src/features/ui/components/AppContent.tsx | 30 +-
.../features/ui/components/Notifications.tsx | 17 +-
.../ParametersPanelCanvas.tsx | 8 +-
.../ParametersPanelGenerate.tsx | 6 +-
.../ParametersPanels/ParametersPanelVideo.tsx | 45 -
.../features/ui/components/VerticalNavBar.tsx | 40 +-
.../src/features/ui/components/WhatsNew.tsx | 25 +-
.../ui/layouts/DockviewTabLaunchpad.tsx | 2 -
.../layouts/LaunchpadStartingFrameButton.tsx | 50 -
.../ui/layouts/VideoLaunchpadPanel.tsx | 48 -
.../features/ui/layouts/VideoTabLeftPanel.tsx | 16 -
.../ui/layouts/video-tab-auto-layout.tsx | 278 -
.../web/src/features/ui/store/uiTypes.ts | 2 +-
.../features/video/components/VideoPlayer.tsx | 53 -
.../video/components/VideoPlayerControls.tsx | 66 -
.../features/video/components/VideoView.tsx | 26 -
.../video/context/VideoViewerContext.tsx | 24 -
.../video/hooks/useCaptureVideoFrame.ts | 90 -
.../components/SaveWorkflowAsDialog.tsx | 1 -
.../SaveWorkflowMenuItem.tsx | 4 +-
.../hooks/useSaveOrSaveAsWorkflow.ts | 6 +-
invokeai/frontend/web/src/index.ts | 76 -
.../src/services/api/authToastMiddleware.ts | 81 -
.../web/src/services/api/endpoints/appInfo.ts | 7 +-
.../web/src/services/api/endpoints/boards.ts | 14 -
.../web/src/services/api/endpoints/images.ts | 5 -
.../web/src/services/api/endpoints/queue.ts | 3 +-
.../web/src/services/api/endpoints/videos.ts | 239 -
.../src/services/api/hooks/modelsByType.ts | 21 +-
.../api/hooks/useDebouncedImageWorkflow.ts | 6 +-
.../api/hooks/useDebouncedMetadata.ts | 20 +-
.../api/hooks/useSelectedModelConfig.ts | 9 -
.../frontend/web/src/services/api/index.ts | 22 +-
.../frontend/web/src/services/api/schema.ts | 8682 +++++++----------
.../frontend/web/src/services/api/types.ts | 44 -
.../services/api/util/optimisticUpdates.ts | 59 +-
.../services/events/onInvocationComplete.tsx | 176 +-
.../services/events/onModelInstallError.tsx | 5 +-
.../src/services/events/setEventListeners.tsx | 74 +-
.../web/src/services/events/stores.ts | 4 +-
.../web/src/services/events/useSocketIO.ts | 31 +-
invokeai/frontend/web/vite.config.mts | 59 -
invokeai/invocation_api/__init__.py | 4 -
327 files changed, 4628 insertions(+), 16492 deletions(-)
delete mode 100644 invokeai/app/api/routers/board_videos.py
delete mode 100644 invokeai/app/api/routers/videos.py
delete mode 100644 invokeai/app/services/videos_common.py
delete mode 100644 invokeai/frontend/web/src/app/components/types.ts
delete mode 100644 invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts
delete mode 100644 invokeai/frontend/web/src/app/store/nanostores/accountSettingsLink.ts
delete mode 100644 invokeai/frontend/web/src/app/store/nanostores/accountTypeText.ts
delete mode 100644 invokeai/frontend/web/src/app/store/nanostores/authToken.ts
delete mode 100644 invokeai/frontend/web/src/app/store/nanostores/baseUrl.ts
delete mode 100644 invokeai/frontend/web/src/app/store/nanostores/customNavComponent.ts
delete mode 100644 invokeai/frontend/web/src/app/store/nanostores/customStarUI.ts
delete mode 100644 invokeai/frontend/web/src/app/store/nanostores/isDebugging.ts
delete mode 100644 invokeai/frontend/web/src/app/store/nanostores/logo.ts
delete mode 100644 invokeai/frontend/web/src/app/store/nanostores/onClickGoToModelManager.ts
delete mode 100644 invokeai/frontend/web/src/app/store/nanostores/openAPISchemaUrl.ts
delete mode 100644 invokeai/frontend/web/src/app/store/nanostores/projectId.ts
delete mode 100644 invokeai/frontend/web/src/app/store/nanostores/queueId.ts
delete mode 100644 invokeai/frontend/web/src/app/store/nanostores/toastMap.ts
delete mode 100644 invokeai/frontend/web/src/app/store/nanostores/videoUpsellComponent.ts
delete mode 100644 invokeai/frontend/web/src/app/store/nanostores/whatsNew.ts
delete mode 100644 invokeai/frontend/web/src/common/hooks/useClientSideUpload.ts
delete mode 100644 invokeai/frontend/web/src/features/deleteImageModal/hooks/use-delete-video.ts
delete mode 100644 invokeai/frontend/web/src/features/deleteVideoModal/components/DeleteVideoButton.tsx
delete mode 100644 invokeai/frontend/web/src/features/deleteVideoModal/components/DeleteVideoModal.tsx
delete mode 100644 invokeai/frontend/web/src/features/deleteVideoModal/store/state.ts
delete mode 100644 invokeai/frontend/web/src/features/dnd/DndDragPreviewMultipleVideo.tsx
delete mode 100644 invokeai/frontend/web/src/features/dnd/DndDragPreviewSingleVideo.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemDeleteVideo.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemSendToVideo.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemUseForPromptGeneration.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ContextMenu/MultipleSelectionVideoMenuItems.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ContextMenu/SingleSelectionVideoMenuItems.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ContextMenu/VideoContextMenu.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/GalleryHeader.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryVideo.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryVideoPlaceholder.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/VideoMetadataViewer.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentVideoButtons.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentVideoPreview.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageViewer/VideoViewer.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageViewer/VideoViewerToolbar.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/VideoGallery.tsx
delete mode 100644 invokeai/frontend/web/src/features/gallery/components/use-gallery-video-ids.ts
create mode 100644 invokeai/frontend/web/src/features/gallery/contexts/ImageDTOContext.ts
delete mode 100644 invokeai/frontend/web/src/features/gallery/contexts/ItemDTOContext.ts
delete mode 100644 invokeai/frontend/web/src/features/gallery/hooks/useRangeBasedVideoFetching.ts
delete mode 100644 invokeai/frontend/web/src/features/gallery/store/actions.ts
delete mode 100644 invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/PublishWorkflowPanelContent.tsx
delete mode 100644 invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/ShareWorkflowModal.tsx
delete mode 100644 invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryListItemActions/ShareWorkflow.tsx
delete mode 100644 invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/publish.ts
delete mode 100644 invokeai/frontend/web/src/features/nodes/hooks/useIsWorkflowEditorLocked.ts
delete mode 100644 invokeai/frontend/web/src/features/nodes/util/graph/generation/buildChatGPT4oGraph.ts
delete mode 100644 invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFluxKontextGraph.ts
delete mode 100644 invokeai/frontend/web/src/features/nodes/util/graph/generation/buildGemini2_5Graph.ts
delete mode 100644 invokeai/frontend/web/src/features/nodes/util/graph/generation/buildImagen3Graph.ts
delete mode 100644 invokeai/frontend/web/src/features/nodes/util/graph/generation/buildImagen4Graph.ts
delete mode 100644 invokeai/frontend/web/src/features/nodes/util/graph/generation/buildRunwayVideoGraph.ts
delete mode 100644 invokeai/frontend/web/src/features/nodes/util/graph/generation/buildVeo3VideoGraph.ts
delete mode 100644 invokeai/frontend/web/src/features/parameters/components/MainModel/DisabledModelWarning.tsx
delete mode 100644 invokeai/frontend/web/src/features/parameters/components/PixelDimensionsUnsupportedAlert.tsx
delete mode 100644 invokeai/frontend/web/src/features/parameters/components/Video/ParamDuration.tsx
delete mode 100644 invokeai/frontend/web/src/features/parameters/components/Video/ParamResolution.tsx
delete mode 100644 invokeai/frontend/web/src/features/parameters/components/Video/VideoDimensions.tsx
delete mode 100644 invokeai/frontend/web/src/features/parameters/components/Video/VideoDimensionsAspectRatioSelect.tsx
delete mode 100644 invokeai/frontend/web/src/features/parameters/components/Video/VideoDimensionsPreview.tsx
delete mode 100644 invokeai/frontend/web/src/features/parameters/hooks/useCurrentVideoDimensions.ts
delete mode 100644 invokeai/frontend/web/src/features/parameters/hooks/useIsModelDisabled.ts
delete mode 100644 invokeai/frontend/web/src/features/parameters/hooks/useIsTooLargeToUpscale.ts
delete mode 100644 invokeai/frontend/web/src/features/parameters/store/videoSlice.ts
delete mode 100644 invokeai/frontend/web/src/features/prompt/PromptExpansion/PromptExpansionMenu.tsx
delete mode 100644 invokeai/frontend/web/src/features/prompt/PromptExpansion/PromptExpansionOverlay.tsx
delete mode 100644 invokeai/frontend/web/src/features/prompt/PromptExpansion/PromptExpansionResultOverlay.tsx
delete mode 100644 invokeai/frontend/web/src/features/prompt/PromptExpansion/expand.ts
delete mode 100644 invokeai/frontend/web/src/features/prompt/PromptExpansion/graph.ts
delete mode 100644 invokeai/frontend/web/src/features/prompt/PromptExpansion/state.ts
delete mode 100644 invokeai/frontend/web/src/features/queue/hooks/useEnqueueVideo.ts
delete mode 100644 invokeai/frontend/web/src/features/settingsAccordions/components/VideoSettingsAccordion/CreditEstimate.tsx
delete mode 100644 invokeai/frontend/web/src/features/settingsAccordions/components/VideoSettingsAccordion/StartingFrameImage.tsx
delete mode 100644 invokeai/frontend/web/src/features/settingsAccordions/components/VideoSettingsAccordion/VideoModelPicker.tsx
delete mode 100644 invokeai/frontend/web/src/features/settingsAccordions/components/VideoSettingsAccordion/VideoSettingsAccordion.tsx
delete mode 100644 invokeai/frontend/web/src/features/system/hooks/useFeatureStatus.ts
delete mode 100644 invokeai/frontend/web/src/features/system/store/configSelectors.ts
delete mode 100644 invokeai/frontend/web/src/features/system/store/configSlice.ts
delete mode 100644 invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelVideo.tsx
delete mode 100644 invokeai/frontend/web/src/features/ui/layouts/LaunchpadStartingFrameButton.tsx
delete mode 100644 invokeai/frontend/web/src/features/ui/layouts/VideoLaunchpadPanel.tsx
delete mode 100644 invokeai/frontend/web/src/features/ui/layouts/VideoTabLeftPanel.tsx
delete mode 100644 invokeai/frontend/web/src/features/ui/layouts/video-tab-auto-layout.tsx
delete mode 100644 invokeai/frontend/web/src/features/video/components/VideoPlayer.tsx
delete mode 100644 invokeai/frontend/web/src/features/video/components/VideoPlayerControls.tsx
delete mode 100644 invokeai/frontend/web/src/features/video/components/VideoView.tsx
delete mode 100644 invokeai/frontend/web/src/features/video/context/VideoViewerContext.tsx
delete mode 100644 invokeai/frontend/web/src/features/video/hooks/useCaptureVideoFrame.ts
delete mode 100644 invokeai/frontend/web/src/index.ts
delete mode 100644 invokeai/frontend/web/src/services/api/authToastMiddleware.ts
delete mode 100644 invokeai/frontend/web/src/services/api/endpoints/videos.ts
diff --git a/invokeai/app/api/routers/app_info.py b/invokeai/app/api/routers/app_info.py
index 5d66c2559ec..c71739fbd2a 100644
--- a/invokeai/app/api/routers/app_info.py
+++ b/invokeai/app/api/routers/app_info.py
@@ -2,7 +2,6 @@
from enum import Enum
from importlib.metadata import distributions
from pathlib import Path
-from typing import Optional
import torch
from fastapi import Body
@@ -40,8 +39,6 @@ class AppVersion(BaseModel):
version: str = Field(description="App version")
- highlights: Optional[list[str]] = Field(default=None, description="Highlights of release")
-
class AppConfig(BaseModel):
"""App Config Response"""
diff --git a/invokeai/app/api/routers/board_videos.py b/invokeai/app/api/routers/board_videos.py
deleted file mode 100644
index 1db8f2784be..00000000000
--- a/invokeai/app/api/routers/board_videos.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from fastapi import Body, HTTPException
-from fastapi.routing import APIRouter
-
-from invokeai.app.services.videos_common import AddVideosToBoardResult, RemoveVideosFromBoardResult
-
-board_videos_router = APIRouter(prefix="/v1/board_videos", tags=["boards"])
-
-
-@board_videos_router.post(
- "/batch",
- operation_id="add_videos_to_board",
- responses={
- 201: {"description": "Videos were added to board successfully"},
- },
- status_code=201,
- response_model=AddVideosToBoardResult,
-)
-async def add_videos_to_board(
- board_id: str = Body(description="The id of the board to add to"),
- video_ids: list[str] = Body(description="The ids of the videos to add", embed=True),
-) -> AddVideosToBoardResult:
- """Adds a list of videos to a board"""
- raise HTTPException(status_code=501, detail="Not implemented")
-
-
-@board_videos_router.post(
- "/batch/delete",
- operation_id="remove_videos_from_board",
- responses={
- 201: {"description": "Videos were removed from board successfully"},
- },
- status_code=201,
- response_model=RemoveVideosFromBoardResult,
-)
-async def remove_videos_from_board(
- video_ids: list[str] = Body(description="The ids of the videos to remove", embed=True),
-) -> RemoveVideosFromBoardResult:
- """Removes a list of videos from their board, if they had one"""
- raise HTTPException(status_code=501, detail="Not implemented")
diff --git a/invokeai/app/api/routers/videos.py b/invokeai/app/api/routers/videos.py
deleted file mode 100644
index 36ead345c9a..00000000000
--- a/invokeai/app/api/routers/videos.py
+++ /dev/null
@@ -1,119 +0,0 @@
-from typing import Optional
-
-from fastapi import Body, HTTPException, Path, Query
-from fastapi.routing import APIRouter
-
-from invokeai.app.services.shared.pagination import OffsetPaginatedResults
-from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection
-from invokeai.app.services.videos_common import (
- DeleteVideosResult,
- StarredVideosResult,
- UnstarredVideosResult,
- VideoDTO,
- VideoIdsResult,
- VideoRecordChanges,
-)
-
-videos_router = APIRouter(prefix="/v1/videos", tags=["videos"])
-
-
-@videos_router.patch(
- "/i/{video_id}",
- operation_id="update_video",
- response_model=VideoDTO,
-)
-async def update_video(
- video_id: str = Path(description="The id of the video to update"),
- video_changes: VideoRecordChanges = Body(description="The changes to apply to the video"),
-) -> VideoDTO:
- """Updates a video"""
-
- raise HTTPException(status_code=501, detail="Not implemented")
-
-
-@videos_router.get(
- "/i/{video_id}",
- operation_id="get_video_dto",
- response_model=VideoDTO,
-)
-async def get_video_dto(
- video_id: str = Path(description="The id of the video to get"),
-) -> VideoDTO:
- """Gets a video's DTO"""
-
- raise HTTPException(status_code=501, detail="Not implemented")
-
-
-@videos_router.post("/delete", operation_id="delete_videos_from_list", response_model=DeleteVideosResult)
-async def delete_videos_from_list(
- video_ids: list[str] = Body(description="The list of ids of videos to delete", embed=True),
-) -> DeleteVideosResult:
- raise HTTPException(status_code=501, detail="Not implemented")
-
-
-@videos_router.post("/star", operation_id="star_videos_in_list", response_model=StarredVideosResult)
-async def star_videos_in_list(
- video_ids: list[str] = Body(description="The list of ids of videos to star", embed=True),
-) -> StarredVideosResult:
- raise HTTPException(status_code=501, detail="Not implemented")
-
-
-@videos_router.post("/unstar", operation_id="unstar_videos_in_list", response_model=UnstarredVideosResult)
-async def unstar_videos_in_list(
- video_ids: list[str] = Body(description="The list of ids of videos to unstar", embed=True),
-) -> UnstarredVideosResult:
- raise HTTPException(status_code=501, detail="Not implemented")
-
-
-@videos_router.delete("/uncategorized", operation_id="delete_uncategorized_videos", response_model=DeleteVideosResult)
-async def delete_uncategorized_videos() -> DeleteVideosResult:
- """Deletes all videos that are uncategorized"""
-
- raise HTTPException(status_code=501, detail="Not implemented")
-
-
-@videos_router.get("/", operation_id="list_video_dtos", response_model=OffsetPaginatedResults[VideoDTO])
-async def list_video_dtos(
- is_intermediate: Optional[bool] = Query(default=None, description="Whether to list intermediate videos."),
- board_id: Optional[str] = Query(
- default=None,
- description="The board id to filter by. Use 'none' to find videos without a board.",
- ),
- offset: int = Query(default=0, description="The page offset"),
- limit: int = Query(default=10, description="The number of videos per page"),
- order_dir: SQLiteDirection = Query(default=SQLiteDirection.Descending, description="The order of sort"),
- starred_first: bool = Query(default=True, description="Whether to sort by starred videos first"),
- search_term: Optional[str] = Query(default=None, description="The term to search for"),
-) -> OffsetPaginatedResults[VideoDTO]:
- """Lists video DTOs"""
-
- raise HTTPException(status_code=501, detail="Not implemented")
-
-
-@videos_router.get("/ids", operation_id="get_video_ids")
-async def get_video_ids(
- is_intermediate: Optional[bool] = Query(default=None, description="Whether to list intermediate videos."),
- board_id: Optional[str] = Query(
- default=None,
- description="The board id to filter by. Use 'none' to find videos without a board.",
- ),
- order_dir: SQLiteDirection = Query(default=SQLiteDirection.Descending, description="The order of sort"),
- starred_first: bool = Query(default=True, description="Whether to sort by starred videos first"),
- search_term: Optional[str] = Query(default=None, description="The term to search for"),
-) -> VideoIdsResult:
- """Gets ordered list of video ids with metadata for optimistic updates"""
-
- raise HTTPException(status_code=501, detail="Not implemented")
-
-
-@videos_router.post(
- "/videos_by_ids",
- operation_id="get_videos_by_ids",
- responses={200: {"model": list[VideoDTO]}},
-)
-async def get_videos_by_ids(
- video_ids: list[str] = Body(embed=True, description="Object containing list of video ids to fetch DTOs for"),
-) -> list[VideoDTO]:
- """Gets video DTOs for the specified video ids. Maintains order of input ids."""
-
- raise HTTPException(status_code=501, detail="Not implemented")
diff --git a/invokeai/app/api/routers/workflows.py b/invokeai/app/api/routers/workflows.py
index 35b928a45af..5a37a75dcf9 100644
--- a/invokeai/app/api/routers/workflows.py
+++ b/invokeai/app/api/routers/workflows.py
@@ -106,7 +106,6 @@ async def list_workflows(
tags: Optional[list[str]] = Query(default=None, description="The tags of workflow to get"),
query: Optional[str] = Query(default=None, description="The text to query by (matches name and description)"),
has_been_opened: Optional[bool] = Query(default=None, description="Whether to include/exclude recent workflows"),
- is_published: Optional[bool] = Query(default=None, description="Whether to include/exclude published workflows"),
) -> PaginatedResults[WorkflowRecordListItemWithThumbnailDTO]:
"""Gets a page of workflows"""
workflows_with_thumbnails: list[WorkflowRecordListItemWithThumbnailDTO] = []
@@ -119,7 +118,6 @@ async def list_workflows(
categories=categories,
tags=tags,
has_been_opened=has_been_opened,
- is_published=is_published,
)
for workflow in workflows.items:
workflows_with_thumbnails.append(
diff --git a/invokeai/app/api_app.py b/invokeai/app/api_app.py
index ce1a2193dff..335327f532b 100644
--- a/invokeai/app/api_app.py
+++ b/invokeai/app/api_app.py
@@ -18,7 +18,6 @@
from invokeai.app.api.routers import (
app_info,
board_images,
- board_videos,
boards,
client_state,
download_queue,
@@ -28,7 +27,6 @@
session_queue,
style_presets,
utilities,
- videos,
workflows,
)
from invokeai.app.api.sockets import SocketIO
@@ -127,10 +125,8 @@ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
app.include_router(model_manager.model_manager_router, prefix="/api")
app.include_router(download_queue.download_queue_router, prefix="/api")
app.include_router(images.images_router, prefix="/api")
-app.include_router(videos.videos_router, prefix="/api")
app.include_router(boards.boards_router, prefix="/api")
app.include_router(board_images.board_images_router, prefix="/api")
-app.include_router(board_videos.board_videos_router, prefix="/api")
app.include_router(model_relationships.model_relationships_router, prefix="/api")
app.include_router(app_info.app_router, prefix="/api")
app.include_router(session_queue.session_queue_router, prefix="/api")
diff --git a/invokeai/app/invocations/fields.py b/invokeai/app/invocations/fields.py
index 9e2e982df5a..5a2d0810356 100644
--- a/invokeai/app/invocations/fields.py
+++ b/invokeai/app/invocations/fields.py
@@ -235,10 +235,6 @@ class ImageField(BaseModel):
image_name: str = Field(description="The name of the image")
-class VideoField(BaseModel):
- """A video primitive field"""
-
- video_id: str = Field(description="The id of the video")
class BoardField(BaseModel):
@@ -549,27 +545,6 @@ def migrate_model_ui_type(ui_type: UIType | str, json_schema_extra: dict[str, An
ui_model_type = [ModelType.FluxRedux]
case UIType.LlavaOnevisionModel:
ui_model_type = [ModelType.LlavaOnevision]
- case UIType.Imagen3Model:
- ui_model_base = [BaseModelType.Imagen3]
- ui_model_type = [ModelType.Main]
- case UIType.Imagen4Model:
- ui_model_base = [BaseModelType.Imagen4]
- ui_model_type = [ModelType.Main]
- case UIType.ChatGPT4oModel:
- ui_model_base = [BaseModelType.ChatGPT4o]
- ui_model_type = [ModelType.Main]
- case UIType.Gemini2_5Model:
- ui_model_base = [BaseModelType.Gemini2_5]
- ui_model_type = [ModelType.Main]
- case UIType.FluxKontextModel:
- ui_model_base = [BaseModelType.FluxKontext]
- ui_model_type = [ModelType.Main]
- case UIType.Veo3Model:
- ui_model_base = [BaseModelType.Veo3]
- ui_model_type = [ModelType.Video]
- case UIType.RunwayModel:
- ui_model_base = [BaseModelType.Runway]
- ui_model_type = [ModelType.Video]
case _:
pass
diff --git a/invokeai/app/invocations/primitives.py b/invokeai/app/invocations/primitives.py
index 1dc470b9705..10703a620cd 100644
--- a/invokeai/app/invocations/primitives.py
+++ b/invokeai/app/invocations/primitives.py
@@ -27,7 +27,6 @@
SD3ConditioningField,
TensorField,
UIComponent,
- VideoField,
)
from invokeai.app.services.images.images_common import ImageDTO
from invokeai.app.services.shared.invocation_context import InvocationContext
@@ -288,30 +287,6 @@ def invoke(self, context: InvocationContext) -> ImageCollectionOutput:
return ImageCollectionOutput(collection=self.collection)
-# endregion
-
-# region Video
-
-
-@invocation_output("video_output")
-class VideoOutput(BaseInvocationOutput):
- """Base class for nodes that output a video"""
-
- video: VideoField = OutputField(description="The output video")
- width: int = OutputField(description="The width of the video in pixels")
- height: int = OutputField(description="The height of the video in pixels")
- duration_seconds: float = OutputField(description="The duration of the video in seconds")
-
- @classmethod
- def build(cls, video_id: str, width: int, height: int, duration_seconds: float) -> "VideoOutput":
- return cls(
- video=VideoField(video_id=video_id),
- width=width,
- height=height,
- duration_seconds=duration_seconds,
- )
-
-
# endregion
# region DenoiseMask
diff --git a/invokeai/app/services/boards/boards_common.py b/invokeai/app/services/boards/boards_common.py
index d25bb9d9da8..68cd3603287 100644
--- a/invokeai/app/services/boards/boards_common.py
+++ b/invokeai/app/services/boards/boards_common.py
@@ -14,12 +14,10 @@ class BoardDTO(BoardRecord):
"""The number of images in the board."""
asset_count: int = Field(description="The number of assets in the board.")
"""The number of assets in the board."""
- video_count: int = Field(description="The number of videos in the board.")
- """The number of videos in the board."""
def board_record_to_dto(
- board_record: BoardRecord, cover_image_name: Optional[str], image_count: int, asset_count: int, video_count: int
+ board_record: BoardRecord, cover_image_name: Optional[str], image_count: int, asset_count: int
) -> BoardDTO:
"""Converts a board record to a board DTO."""
return BoardDTO(
@@ -27,5 +25,4 @@ def board_record_to_dto(
cover_image_name=cover_image_name,
image_count=image_count,
asset_count=asset_count,
- video_count=video_count,
)
diff --git a/invokeai/app/services/boards/boards_default.py b/invokeai/app/services/boards/boards_default.py
index df161086459..6efeaa1fea8 100644
--- a/invokeai/app/services/boards/boards_default.py
+++ b/invokeai/app/services/boards/boards_default.py
@@ -28,8 +28,7 @@ def get_dto(self, board_id: str) -> BoardDTO:
cover_image_name = None
image_count = self.__invoker.services.board_image_records.get_image_count_for_board(board_id)
asset_count = self.__invoker.services.board_image_records.get_asset_count_for_board(board_id)
- video_count = 0 # noop for OSS
- return board_record_to_dto(board_record, cover_image_name, image_count, asset_count, video_count)
+ return board_record_to_dto(board_record, cover_image_name, image_count, asset_count)
def update(
self,
@@ -45,8 +44,7 @@ def update(
image_count = self.__invoker.services.board_image_records.get_image_count_for_board(board_id)
asset_count = self.__invoker.services.board_image_records.get_asset_count_for_board(board_id)
- video_count = 0 # noop for OSS
- return board_record_to_dto(board_record, cover_image_name, image_count, asset_count, video_count)
+ return board_record_to_dto(board_record, cover_image_name, image_count, asset_count)
def delete(self, board_id: str) -> None:
self.__invoker.services.board_records.delete(board_id)
@@ -72,8 +70,7 @@ def get_many(
image_count = self.__invoker.services.board_image_records.get_image_count_for_board(r.board_id)
asset_count = self.__invoker.services.board_image_records.get_asset_count_for_board(r.board_id)
- video_count = 0 # noop for OSS
- board_dtos.append(board_record_to_dto(r, cover_image_name, image_count, asset_count, video_count))
+ board_dtos.append(board_record_to_dto(r, cover_image_name, image_count, asset_count))
return OffsetPaginatedResults[BoardDTO](items=board_dtos, offset=offset, limit=limit, total=len(board_dtos))
@@ -91,7 +88,6 @@ def get_all(
image_count = self.__invoker.services.board_image_records.get_image_count_for_board(r.board_id)
asset_count = self.__invoker.services.board_image_records.get_asset_count_for_board(r.board_id)
- video_count = 0 # noop for OSS
- board_dtos.append(board_record_to_dto(r, cover_image_name, image_count, asset_count, video_count))
+ board_dtos.append(board_record_to_dto(r, cover_image_name, image_count, asset_count))
return board_dtos
diff --git a/invokeai/app/services/videos_common.py b/invokeai/app/services/videos_common.py
deleted file mode 100644
index a1b8d762287..00000000000
--- a/invokeai/app/services/videos_common.py
+++ /dev/null
@@ -1,179 +0,0 @@
-import datetime
-from typing import Optional, Union
-
-from pydantic import BaseModel, Field, StrictBool, StrictStr
-
-from invokeai.app.util.misc import get_iso_timestamp
-from invokeai.app.util.model_exclude_null import BaseModelExcludeNull
-
-VIDEO_DTO_COLS = ", ".join(
- [
- "videos." + c
- for c in [
- "video_id",
- "width",
- "height",
- "session_id",
- "node_id",
- "is_intermediate",
- "created_at",
- "updated_at",
- "deleted_at",
- "starred",
- ]
- ]
-)
-
-
-class VideoRecord(BaseModelExcludeNull):
- """Deserialized video record without metadata."""
-
- video_id: str = Field(description="The unique id of the video.")
- """The unique id of the video."""
- width: int = Field(description="The width of the video in px.")
- """The actual width of the video in px. This may be different from the width in metadata."""
- height: int = Field(description="The height of the video in px.")
- """The actual height of the video in px. This may be different from the height in metadata."""
- created_at: Union[datetime.datetime, str] = Field(description="The created timestamp of the video.")
- """The created timestamp of the video."""
- updated_at: Union[datetime.datetime, str] = Field(description="The updated timestamp of the video.")
- """The updated timestamp of the video."""
- deleted_at: Optional[Union[datetime.datetime, str]] = Field(
- default=None, description="The deleted timestamp of the video."
- )
- """The deleted timestamp of the video."""
- is_intermediate: bool = Field(description="Whether this is an intermediate video.")
- """Whether this is an intermediate video."""
- session_id: Optional[str] = Field(
- default=None,
- description="The session ID that generated this video, if it is a generated video.",
- )
- """The session ID that generated this video, if it is a generated video."""
- node_id: Optional[str] = Field(
- default=None,
- description="The node ID that generated this video, if it is a generated video.",
- )
- """The node ID that generated this video, if it is a generated video."""
- starred: bool = Field(description="Whether this video is starred.")
- """Whether this video is starred."""
-
-
-class VideoRecordChanges(BaseModelExcludeNull):
- """A set of changes to apply to a video record.
-
- Only limited changes are valid:
- - `session_id`: change the session associated with a video
- - `is_intermediate`: change the video's `is_intermediate` flag
- - `starred`: change whether the video is starred
- """
-
- session_id: Optional[StrictStr] = Field(
- default=None,
- description="The video's new session ID.",
- )
- """The video's new session ID."""
- is_intermediate: Optional[StrictBool] = Field(default=None, description="The video's new `is_intermediate` flag.")
- """The video's new `is_intermediate` flag."""
- starred: Optional[StrictBool] = Field(default=None, description="The video's new `starred` state")
- """The video's new `starred` state."""
-
-
-def deserialize_video_record(video_dict: dict) -> VideoRecord:
- """Deserializes a video record."""
-
- # Retrieve all the values, setting "reasonable" defaults if they are not present.
- video_id = video_dict.get("video_id", "unknown")
- width = video_dict.get("width", 0)
- height = video_dict.get("height", 0)
- session_id = video_dict.get("session_id", None)
- node_id = video_dict.get("node_id", None)
- created_at = video_dict.get("created_at", get_iso_timestamp())
- updated_at = video_dict.get("updated_at", get_iso_timestamp())
- deleted_at = video_dict.get("deleted_at", get_iso_timestamp())
- is_intermediate = video_dict.get("is_intermediate", False)
- starred = video_dict.get("starred", False)
-
- return VideoRecord(
- video_id=video_id,
- width=width,
- height=height,
- session_id=session_id,
- node_id=node_id,
- created_at=created_at,
- updated_at=updated_at,
- deleted_at=deleted_at,
- is_intermediate=is_intermediate,
- starred=starred,
- )
-
-
-class VideoCollectionCounts(BaseModel):
- starred_count: int = Field(description="The number of starred videos in the collection.")
- unstarred_count: int = Field(description="The number of unstarred videos in the collection.")
-
-
-class VideoIdsResult(BaseModel):
- """Response containing ordered video ids with metadata for optimistic updates."""
-
- video_ids: list[str] = Field(description="Ordered list of video ids")
- starred_count: int = Field(description="Number of starred videos (when starred_first=True)")
- total_count: int = Field(description="Total number of videos matching the query")
-
-
-class VideoUrlsDTO(BaseModelExcludeNull):
- """The URLs for an image and its thumbnail."""
-
- video_id: str = Field(description="The unique id of the video.")
- """The unique id of the video."""
- video_url: str = Field(description="The URL of the video.")
- """The URL of the video."""
- thumbnail_url: str = Field(description="The URL of the video's thumbnail.")
- """The URL of the video's thumbnail."""
-
-
-class VideoDTO(VideoRecord, VideoUrlsDTO):
- """Deserialized video record, enriched for the frontend."""
-
- board_id: Optional[str] = Field(
- default=None, description="The id of the board the image belongs to, if one exists."
- )
- """The id of the board the image belongs to, if one exists."""
-
-
-def video_record_to_dto(
- video_record: VideoRecord,
- video_url: str,
- thumbnail_url: str,
- board_id: Optional[str],
-) -> VideoDTO:
- """Converts a video record to a video DTO."""
- return VideoDTO(
- **video_record.model_dump(),
- video_url=video_url,
- thumbnail_url=thumbnail_url,
- board_id=board_id,
- )
-
-
-class ResultWithAffectedBoards(BaseModel):
- affected_boards: list[str] = Field(description="The ids of boards affected by the delete operation")
-
-
-class DeleteVideosResult(ResultWithAffectedBoards):
- deleted_videos: list[str] = Field(description="The ids of the videos that were deleted")
-
-
-class StarredVideosResult(ResultWithAffectedBoards):
- starred_videos: list[str] = Field(description="The ids of the videos that were starred")
-
-
-class UnstarredVideosResult(ResultWithAffectedBoards):
- unstarred_videos: list[str] = Field(description="The ids of the videos that were unstarred")
-
-
-class AddVideosToBoardResult(ResultWithAffectedBoards):
- added_videos: list[str] = Field(description="The video ids that were added to the board")
-
-
-class RemoveVideosFromBoardResult(ResultWithAffectedBoards):
- removed_videos: list[str] = Field(description="The video ids that were removed from their board")
diff --git a/invokeai/app/services/workflow_records/workflow_records_base.py b/invokeai/app/services/workflow_records/workflow_records_base.py
index bf91363281b..5bf42ed2533 100644
--- a/invokeai/app/services/workflow_records/workflow_records_base.py
+++ b/invokeai/app/services/workflow_records/workflow_records_base.py
@@ -47,7 +47,6 @@ def get_many(
query: Optional[str],
tags: Optional[list[str]],
has_been_opened: Optional[bool],
- is_published: Optional[bool],
) -> PaginatedResults[WorkflowRecordListItemDTO]:
"""Gets many workflows."""
pass
@@ -57,7 +56,6 @@ def counts_by_category(
self,
categories: list[WorkflowCategory],
has_been_opened: Optional[bool] = None,
- is_published: Optional[bool] = None,
) -> dict[str, int]:
"""Gets a dictionary of counts for each of the provided categories."""
pass
@@ -68,7 +66,6 @@ def counts_by_tag(
tags: list[str],
categories: Optional[list[WorkflowCategory]] = None,
has_been_opened: Optional[bool] = None,
- is_published: Optional[bool] = None,
) -> dict[str, int]:
"""Gets a dictionary of counts for each of the provided tags."""
pass
diff --git a/invokeai/app/services/workflow_records/workflow_records_common.py b/invokeai/app/services/workflow_records/workflow_records_common.py
index fe203ab8c96..909ed3b463b 100644
--- a/invokeai/app/services/workflow_records/workflow_records_common.py
+++ b/invokeai/app/services/workflow_records/workflow_records_common.py
@@ -67,7 +67,6 @@ class WorkflowWithoutID(BaseModel):
# This is typed as optional to prevent errors when pulling workflows from the DB. The frontend adds a default form if
# it is None.
form: dict[str, JsonValue] | None = Field(default=None, description="The form of the workflow.")
- is_published: bool | None = Field(default=None, description="Whether the workflow is published or not.")
model_config = ConfigDict(extra="ignore")
@@ -102,7 +101,6 @@ class WorkflowRecordDTOBase(BaseModel):
opened_at: Optional[Union[datetime.datetime, str]] = Field(
default=None, description="The opened timestamp of the workflow."
)
- is_published: bool | None = Field(default=None, description="Whether the workflow is published or not.")
class WorkflowRecordDTO(WorkflowRecordDTOBase):
diff --git a/invokeai/app/services/workflow_records/workflow_records_sqlite.py b/invokeai/app/services/workflow_records/workflow_records_sqlite.py
index 72f37469de8..d6a94d156f0 100644
--- a/invokeai/app/services/workflow_records/workflow_records_sqlite.py
+++ b/invokeai/app/services/workflow_records/workflow_records_sqlite.py
@@ -104,7 +104,6 @@ def get_many(
query: Optional[str] = None,
tags: Optional[list[str]] = None,
has_been_opened: Optional[bool] = None,
- is_published: Optional[bool] = None,
) -> PaginatedResults[WorkflowRecordListItemDTO]:
with self._db.transaction() as cursor:
# sanitize!
@@ -227,7 +226,6 @@ def counts_by_tag(
tags: list[str],
categories: Optional[list[WorkflowCategory]] = None,
has_been_opened: Optional[bool] = None,
- is_published: Optional[bool] = None,
) -> dict[str, int]:
if not tags:
return {}
@@ -279,7 +277,6 @@ def counts_by_category(
self,
categories: list[WorkflowCategory],
has_been_opened: Optional[bool] = None,
- is_published: Optional[bool] = None,
) -> dict[str, int]:
with self._db.transaction() as cursor:
result: dict[str, int] = {}
diff --git a/invokeai/backend/model_manager/configs/base.py b/invokeai/backend/model_manager/configs/base.py
index 8de9a2b8316..43c31c7e4fd 100644
--- a/invokeai/backend/model_manager/configs/base.py
+++ b/invokeai/backend/model_manager/configs/base.py
@@ -28,6 +28,17 @@
pass
+class URLModelSource(BaseModel):
+ type: Literal[ModelSourceType.Url] = Field(default=ModelSourceType.Url)
+ url: str = Field(
+ description="The URL from which the model was installed.",
+ )
+ api_response: str | None = Field(
+ default=None,
+ description="The original API response from the source, as stringified JSON.",
+ )
+
+
class Config_Base(ABC, BaseModel):
"""
Abstract base class for model configurations. A model config describes a specific combination of model base, type and
@@ -81,10 +92,6 @@ class Config_Base(ABC, BaseModel):
default=None,
description="Url for image to preview model",
)
- usage_info: str | None = Field(
- default=None,
- description="Usage information for this model",
- )
CONFIG_CLASSES: ClassVar[set[Type["Config_Base"]]] = set()
"""Set of all non-abstract subclasses of Config_Base, for use during model probing. In other words, this is the set
diff --git a/invokeai/backend/model_manager/configs/factory.py b/invokeai/backend/model_manager/configs/factory.py
index dcd7c4c0edc..6b8d122d615 100644
--- a/invokeai/backend/model_manager/configs/factory.py
+++ b/invokeai/backend/model_manager/configs/factory.py
@@ -64,15 +64,8 @@
Main_Diffusers_SD3_Config,
Main_Diffusers_SDXL_Config,
Main_Diffusers_SDXLRefiner_Config,
- Main_ExternalAPI_ChatGPT4o_Config,
- Main_ExternalAPI_FluxKontext_Config,
- Main_ExternalAPI_Gemini2_5_Config,
- Main_ExternalAPI_Imagen3_Config,
- Main_ExternalAPI_Imagen4_Config,
Main_GGUF_FLUX_Config,
MainModelDefaultSettings,
- Video_ExternalAPI_Runway_Config,
- Video_ExternalAPI_Veo3_Config,
)
from invokeai.backend.model_manager.configs.siglip import SigLIP_Diffusers_Config
from invokeai.backend.model_manager.configs.spandrel import Spandrel_Checkpoint_Config
@@ -218,15 +211,6 @@
Annotated[SigLIP_Diffusers_Config, SigLIP_Diffusers_Config.get_tag()],
Annotated[FLUXRedux_Checkpoint_Config, FLUXRedux_Checkpoint_Config.get_tag()],
Annotated[LlavaOnevision_Diffusers_Config, LlavaOnevision_Diffusers_Config.get_tag()],
- # Main - external API
- Annotated[Main_ExternalAPI_ChatGPT4o_Config, Main_ExternalAPI_ChatGPT4o_Config.get_tag()],
- Annotated[Main_ExternalAPI_Gemini2_5_Config, Main_ExternalAPI_Gemini2_5_Config.get_tag()],
- Annotated[Main_ExternalAPI_Imagen3_Config, Main_ExternalAPI_Imagen3_Config.get_tag()],
- Annotated[Main_ExternalAPI_Imagen4_Config, Main_ExternalAPI_Imagen4_Config.get_tag()],
- Annotated[Main_ExternalAPI_FluxKontext_Config, Main_ExternalAPI_FluxKontext_Config.get_tag()],
- # Video - external API
- Annotated[Video_ExternalAPI_Veo3_Config, Video_ExternalAPI_Veo3_Config.get_tag()],
- Annotated[Video_ExternalAPI_Runway_Config, Video_ExternalAPI_Runway_Config.get_tag()],
# Unknown model (fallback)
Annotated[Unknown_Config, Unknown_Config.get_tag()],
],
diff --git a/invokeai/backend/model_manager/configs/main.py b/invokeai/backend/model_manager/configs/main.py
index dcb948d99bb..03c44e1a778 100644
--- a/invokeai/backend/model_manager/configs/main.py
+++ b/invokeai/backend/model_manager/configs/main.py
@@ -657,49 +657,3 @@ def from_model_on_disk(cls, mod: ModelOnDisk, override_fields: dict[str, Any]) -
**override_fields,
repo_variant=repo_variant,
)
-
-
-class ExternalAPI_Config_Base(ABC, BaseModel):
- """Model config for API-based models."""
-
- format: Literal[ModelFormat.Api] = Field(default=ModelFormat.Api)
-
- @classmethod
- def from_model_on_disk(cls, mod: ModelOnDisk, override_fields: dict[str, Any]) -> Self:
- raise NotAMatchError("External API models cannot be built from disk")
-
-
-class Main_ExternalAPI_ChatGPT4o_Config(ExternalAPI_Config_Base, Main_Config_Base, Config_Base):
- base: Literal[BaseModelType.ChatGPT4o] = Field(default=BaseModelType.ChatGPT4o)
-
-
-class Main_ExternalAPI_Gemini2_5_Config(ExternalAPI_Config_Base, Main_Config_Base, Config_Base):
- base: Literal[BaseModelType.Gemini2_5] = Field(default=BaseModelType.Gemini2_5)
-
-
-class Main_ExternalAPI_Imagen3_Config(ExternalAPI_Config_Base, Main_Config_Base, Config_Base):
- base: Literal[BaseModelType.Imagen3] = Field(default=BaseModelType.Imagen3)
-
-
-class Main_ExternalAPI_Imagen4_Config(ExternalAPI_Config_Base, Main_Config_Base, Config_Base):
- base: Literal[BaseModelType.Imagen4] = Field(default=BaseModelType.Imagen4)
-
-
-class Main_ExternalAPI_FluxKontext_Config(ExternalAPI_Config_Base, Main_Config_Base, Config_Base):
- base: Literal[BaseModelType.FluxKontext] = Field(default=BaseModelType.FluxKontext)
-
-
-class Video_Config_Base(ABC, BaseModel):
- type: Literal[ModelType.Video] = Field(default=ModelType.Video)
- trigger_phrases: set[str] | None = Field(description="Set of trigger phrases for this model", default=None)
- default_settings: MainModelDefaultSettings | None = Field(
- description="Default settings for this model", default=None
- )
-
-
-class Video_ExternalAPI_Veo3_Config(ExternalAPI_Config_Base, Video_Config_Base, Config_Base):
- base: Literal[BaseModelType.Veo3] = Field(default=BaseModelType.Veo3)
-
-
-class Video_ExternalAPI_Runway_Config(ExternalAPI_Config_Base, Video_Config_Base, Config_Base):
- base: Literal[BaseModelType.Runway] = Field(default=BaseModelType.Runway)
diff --git a/invokeai/backend/model_manager/taxonomy.py b/invokeai/backend/model_manager/taxonomy.py
index 99a31f438d1..38afd44fcb6 100644
--- a/invokeai/backend/model_manager/taxonomy.py
+++ b/invokeai/backend/model_manager/taxonomy.py
@@ -48,21 +48,6 @@ class BaseModelType(str, Enum):
"""Indicates the model is associated with FLUX.1 model architecture, including FLUX Dev, Schnell and Fill."""
CogView4 = "cogview4"
"""Indicates the model is associated with CogView 4 model architecture."""
- Imagen3 = "imagen3"
- """Indicates the model is associated with Google Imagen 3 model architecture. This is an external API model."""
- Imagen4 = "imagen4"
- """Indicates the model is associated with Google Imagen 4 model architecture. This is an external API model."""
- Gemini2_5 = "gemini-2.5"
- """Indicates the model is associated with Google Gemini 2.5 Flash Image model architecture. This is an external API model."""
- ChatGPT4o = "chatgpt-4o"
- """Indicates the model is associated with OpenAI ChatGPT 4o Image model architecture. This is an external API model."""
- FluxKontext = "flux-kontext"
- """Indicates the model is associated with FLUX Kontext model architecture. This is an external API model; local FLUX
- Kontext models use the base `Flux`."""
- Veo3 = "veo3"
- """Indicates the model is associated with Google Veo 3 video model architecture. This is an external API model."""
- Runway = "runway"
- """Indicates the model is associated with Runway video model architecture. This is an external API model."""
Unknown = "unknown"
"""Indicates the model's base architecture is unknown."""
@@ -86,7 +71,6 @@ class ModelType(str, Enum):
SigLIP = "siglip"
FluxRedux = "flux_redux"
LlavaOnevision = "llava_onevision"
- Video = "video"
Unknown = "unknown"
@@ -145,7 +129,6 @@ class ModelFormat(str, Enum):
BnbQuantizedLlmInt8b = "bnb_quantized_int8b"
BnbQuantizednf4b = "bnb_quantized_nf4b"
GGUFQuantized = "gguf_quantized"
- Api = "api"
Unknown = "unknown"
diff --git a/invokeai/frontend/web/.storybook/preview.tsx b/invokeai/frontend/web/.storybook/preview.tsx
index 4f1cc0ed9fb..eb3d0391db4 100644
--- a/invokeai/frontend/web/.storybook/preview.tsx
+++ b/invokeai/frontend/web/.storybook/preview.tsx
@@ -10,7 +10,6 @@ import { Provider } from 'react-redux';
// @ts-ignore
import translationEN from '../public/locales/en.json';
import ThemeLocaleProvider from '../src/app/components/ThemeLocaleProvider';
-import { $baseUrl } from '../src/app/store/nanostores/baseUrl';
import { createStore } from '../src/app/store/store';
import { ReduxInit } from './ReduxInit';
@@ -28,7 +27,6 @@ i18n.use(initReactI18next).init({
const store = createStore();
$store.set(store);
-$baseUrl.set('http://localhost:9090');
const preview: Preview = {
decorators: [
diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx
index 0f23e05a71f..bae4f4cf633 100644
--- a/invokeai/frontend/web/src/app/components/App.tsx
+++ b/invokeai/frontend/web/src/app/components/App.tsx
@@ -1,27 +1,15 @@
import { Box } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
import { GlobalHookIsolator } from 'app/components/GlobalHookIsolator';
import { GlobalModalIsolator } from 'app/components/GlobalModalIsolator';
-import { $didStudioInit, type StudioInitAction } from 'app/hooks/useStudioInitAction';
import { clearStorage } from 'app/store/enhancers/reduxRemember/driver';
-import type { PartialAppConfig } from 'app/types/invokeai';
-import Loading from 'common/components/Loading/Loading';
import { AppContent } from 'features/ui/components/AppContent';
import { memo, useCallback } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import AppErrorBoundaryFallback from './AppErrorBoundaryFallback';
import ThemeLocaleProvider from './ThemeLocaleProvider';
-const DEFAULT_CONFIG = {};
-
-interface Props {
- config?: PartialAppConfig;
- studioInitAction?: StudioInitAction;
-}
-
-const App = ({ config = DEFAULT_CONFIG, studioInitAction }: Props) => {
- const didStudioInit = useStore($didStudioInit);
+const App = () => {
const handleReset = useCallback(() => {
clearStorage();
location.reload();
@@ -33,9 +21,8 @@ const App = ({ config = DEFAULT_CONFIG, studioInitAction }: Props) => {
- {!didStudioInit && }
-
+
diff --git a/invokeai/frontend/web/src/app/components/AppErrorBoundaryFallback.tsx b/invokeai/frontend/web/src/app/components/AppErrorBoundaryFallback.tsx
index f061ba15f9b..f22a94c33fc 100644
--- a/invokeai/frontend/web/src/app/components/AppErrorBoundaryFallback.tsx
+++ b/invokeai/frontend/web/src/app/components/AppErrorBoundaryFallback.tsx
@@ -1,8 +1,5 @@
import { Button, Flex, Heading, Image, Link, Text } from '@invoke-ai/ui-library';
-import { createSelector } from '@reduxjs/toolkit';
-import { useAppSelector } from 'app/store/storeHooks';
import { useClipboard } from 'common/hooks/useClipboard';
-import { selectConfigSlice } from 'features/system/store/configSlice';
import { toast } from 'features/toast/toast';
import newGithubIssueUrl from 'new-github-issue-url';
import InvokeLogoYellow from 'public/assets/images/invoke-symbol-ylw-lrg.svg';
@@ -16,11 +13,8 @@ type Props = {
resetErrorBoundary: () => void;
};
-const selectIsLocal = createSelector(selectConfigSlice, (config) => config.isLocal);
-
const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
const { t } = useTranslation();
- const isLocal = useAppSelector(selectIsLocal);
const clipboard = useClipboard();
const handleCopy = useCallback(() => {
@@ -34,17 +28,13 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
}, [clipboard, error, t]);
const url = useMemo(() => {
- if (isLocal) {
- return newGithubIssueUrl({
- user: 'invoke-ai',
- repo: 'InvokeAI',
- template: 'BUG_REPORT.yml',
- title: `[bug]: ${error.name}: ${error.message}`,
- });
- } else {
- return 'https://support.invoke.ai/support/tickets/new';
- }
- }, [error.message, error.name, isLocal]);
+ return newGithubIssueUrl({
+ user: 'invoke-ai',
+ repo: 'InvokeAI',
+ template: 'BUG_REPORT.yml',
+ title: `[bug]: ${error.name}: ${error.message}`,
+ });
+ }, [error.message, error.name]);
return (
@@ -75,9 +65,7 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
{t('common.copyError')}
- }>
- {isLocal ? t('accessibility.createIssue') : t('accessibility.submitSupportTicket')}
-
+ }>{t('accessibility.createIssue')}
diff --git a/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx b/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx
index 0a21348e984..a4345f373a6 100644
--- a/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx
+++ b/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx
@@ -1,14 +1,10 @@
import { useGlobalModifiersInit } from '@invoke-ai/ui-library';
import { setupListeners } from '@reduxjs/toolkit/query';
-import type { StudioInitAction } from 'app/hooks/useStudioInitAction';
-import { useStudioInitAction } from 'app/hooks/useStudioInitAction';
import { useSyncLangDirection } from 'app/hooks/useSyncLangDirection';
import { useSyncQueueStatus } from 'app/hooks/useSyncQueueStatus';
-import { useLogger } from 'app/logging/useLogger';
import { useSyncLoggingConfig } from 'app/logging/useSyncLoggingConfig';
import { appStarted } from 'app/store/middleware/listenerMiddleware/listeners/appStarted';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import type { PartialAppConfig } from 'app/types/invokeai';
import { useFocusRegionWatcher } from 'common/hooks/focus';
import { useCloseChakraTooltipsOnDragFix } from 'common/hooks/useCloseChakraTooltipsOnDragFix';
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
@@ -19,7 +15,6 @@ import { useWorkflowBuilderWatcher } from 'features/nodes/components/sidePanel/w
import { useSyncExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
import { useSyncNodeErrors } from 'features/nodes/store/util/fieldValidators';
import { useReadinessWatcher } from 'features/queue/store/readiness';
-import { configChanged } from 'features/system/store/configSlice';
import { selectLanguage } from 'features/system/store/systemSelectors';
import { useNavigationApi } from 'features/ui/layouts/use-navigation-api';
import i18n from 'i18n';
@@ -34,55 +29,46 @@ const queueCountArg = { destination: 'canvas' };
* GlobalHookIsolator is a logical component that runs global hooks in an isolated component, so that they do not
* cause needless re-renders of any other components.
*/
-export const GlobalHookIsolator = memo(
- ({ config, studioInitAction }: { config: PartialAppConfig; studioInitAction?: StudioInitAction }) => {
- const language = useAppSelector(selectLanguage);
- const logger = useLogger('system');
- const dispatch = useAppDispatch();
+export const GlobalHookIsolator = memo(() => {
+ const language = useAppSelector(selectLanguage);
+ const dispatch = useAppDispatch();
- // singleton!
- useReadinessWatcher();
- useSocketIO();
- useGlobalModifiersInit();
- useGlobalHotkeys();
- useGetOpenAPISchemaQuery();
- useSyncLoggingConfig();
- useCloseChakraTooltipsOnDragFix();
- useNavigationApi();
- useDndMonitor();
- useSyncNodeErrors();
- useSyncLangDirection();
+ // singleton!
+ useReadinessWatcher();
+ useSocketIO();
+ useGlobalModifiersInit();
+ useGlobalHotkeys();
+ useGetOpenAPISchemaQuery();
+ useSyncLoggingConfig();
+ useCloseChakraTooltipsOnDragFix();
+ useNavigationApi();
+ useDndMonitor();
+ useSyncNodeErrors();
+ useSyncLangDirection();
- // Persistent subscription to the queue counts query - canvas relies on this to know if there are pending
- // and/or in progress canvas sessions.
- useGetQueueCountsByDestinationQuery(queueCountArg);
- useSyncExecutionState();
+ // Persistent subscription to the queue counts query - canvas relies on this to know if there are pending
+ // and/or in progress canvas sessions.
+ useGetQueueCountsByDestinationQuery(queueCountArg);
+ useSyncExecutionState();
- useEffect(() => {
- i18n.changeLanguage(language);
- }, [language]);
+ useEffect(() => {
+ i18n.changeLanguage(language);
+ }, [language]);
- useEffect(() => {
- logger.info({ config }, 'Received config');
- dispatch(configChanged(config));
- }, [dispatch, config, logger]);
+ useEffect(() => {
+ dispatch(appStarted());
+ }, [dispatch]);
- useEffect(() => {
- dispatch(appStarted());
- }, [dispatch]);
+ useEffect(() => {
+ return setupListeners(dispatch);
+ }, [dispatch]);
- useEffect(() => {
- return setupListeners(dispatch);
- }, [dispatch]);
+ useStarterModelsToast();
+ useSyncQueueStatus();
+ useFocusRegionWatcher();
+ useWorkflowBuilderWatcher();
+ useDynamicPromptsWatcher();
- useStudioInitAction(studioInitAction);
- useStarterModelsToast();
- useSyncQueueStatus();
- useFocusRegionWatcher();
- useWorkflowBuilderWatcher();
- useDynamicPromptsWatcher();
-
- return null;
- }
-);
+ return null;
+});
GlobalHookIsolator.displayName = 'GlobalHookIsolator';
diff --git a/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx b/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx
index b5aec1dd561..5c1446662ef 100644
--- a/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx
+++ b/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx
@@ -4,13 +4,10 @@ import { CanvasPasteModal } from 'features/controlLayers/components/CanvasPasteM
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { CropImageModal } from 'features/cropper/components/CropImageModal';
import { DeleteImageModal } from 'features/deleteImageModal/components/DeleteImageModal';
-import { DeleteVideoModal } from 'features/deleteVideoModal/components/DeleteVideoModal';
import { FullscreenDropzone } from 'features/dnd/FullscreenDropzone';
import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal';
import DeleteBoardModal from 'features/gallery/components/Boards/DeleteBoardModal';
import { ImageContextMenu } from 'features/gallery/components/ContextMenu/ImageContextMenu';
-import { VideoContextMenu } from 'features/gallery/components/ContextMenu/VideoContextMenu';
-import { ShareWorkflowModal } from 'features/nodes/components/sidePanel/workflow/WorkflowLibrary/ShareWorkflowModal';
import { WorkflowLibraryModal } from 'features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryModal';
import { CancelAllExceptCurrentQueueItemConfirmationAlertDialog } from 'features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog';
import { ClearQueueConfirmationsAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
@@ -34,7 +31,6 @@ export const GlobalModalIsolator = memo(() => {
return (
<>
-
@@ -46,12 +42,10 @@ export const GlobalModalIsolator = memo(() => {
-
-
diff --git a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx
index 21eee66513f..1fa0a5f3cd9 100644
--- a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx
+++ b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx
@@ -1,309 +1,23 @@
import 'i18n';
-import type { InvokeAIUIProps } from 'app/components/types';
-import { $didStudioInit } from 'app/hooks/useStudioInitAction';
-import { $loggingOverrides, configureLogging } from 'app/logging/logger';
import { addStorageListeners } from 'app/store/enhancers/reduxRemember/driver';
-import { $accountSettingsLink } from 'app/store/nanostores/accountSettingsLink';
-import { $accountTypeText } from 'app/store/nanostores/accountTypeText';
-import { $authToken } from 'app/store/nanostores/authToken';
-import { $baseUrl } from 'app/store/nanostores/baseUrl';
-import { $customNavComponent } from 'app/store/nanostores/customNavComponent';
-import { $customStarUI } from 'app/store/nanostores/customStarUI';
-import { $isDebugging } from 'app/store/nanostores/isDebugging';
-import { $logo } from 'app/store/nanostores/logo';
-import { $onClickGoToModelManager } from 'app/store/nanostores/onClickGoToModelManager';
-import { $openAPISchemaUrl } from 'app/store/nanostores/openAPISchemaUrl';
-import { $projectId, $projectName, $projectUrl } from 'app/store/nanostores/projectId';
-import { $queueId, DEFAULT_QUEUE_ID } from 'app/store/nanostores/queueId';
import { $store } from 'app/store/nanostores/store';
-import { $toastMap } from 'app/store/nanostores/toastMap';
-import { $videoUpsellComponent } from 'app/store/nanostores/videoUpsellComponent';
-import { $whatsNew } from 'app/store/nanostores/whatsNew';
import { createStore } from 'app/store/store';
import Loading from 'common/components/Loading/Loading';
-import {
- $workflowLibraryCategoriesOptions,
- $workflowLibrarySortOptions,
- $workflowLibraryTagCategoriesOptions,
- DEFAULT_WORKFLOW_LIBRARY_CATEGORIES,
- DEFAULT_WORKFLOW_LIBRARY_SORT_OPTIONS,
- DEFAULT_WORKFLOW_LIBRARY_TAG_CATEGORIES,
-} from 'features/nodes/store/workflowLibrarySlice';
-import React, { lazy, memo, useEffect, useLayoutEffect, useState } from 'react';
+import React, { lazy, memo, useEffect, useState } from 'react';
import { Provider } from 'react-redux';
-import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares';
-import { $socketOptions } from 'services/events/stores';
const App = lazy(() => import('./App'));
-const InvokeAIUI = ({
- apiUrl,
- openAPISchemaUrl,
- token,
- config,
- customNavComponent,
- accountSettingsLink,
- middleware,
- projectId,
- projectName,
- projectUrl,
- queueId,
- studioInitAction,
- customStarUi,
- socketOptions,
- isDebugging = false,
- logo,
- toastMap,
- accountTypeText,
- videoUpsellComponent,
- workflowCategories,
- workflowTagCategories,
- workflowSortOptions,
- loggingOverrides,
- onClickGoToModelManager,
- whatsNew,
- storagePersistDebounce = 300,
-}: InvokeAIUIProps) => {
+const InvokeAIUI = () => {
const [store, setStore] = useState | undefined>(undefined);
const [didRehydrate, setDidRehydrate] = useState(false);
- useLayoutEffect(() => {
- /*
- * We need to configure logging before anything else happens - useLayoutEffect ensures we set this at the first
- * possible opportunity.
- *
- * Once redux initializes, we will check the user's settings and update the logging config accordingly. See
- * `useSyncLoggingConfig`.
- */
- $loggingOverrides.set(loggingOverrides);
-
- // Until we get the user's settings, we will use the overrides OR default values.
- configureLogging(
- loggingOverrides?.logIsEnabled ?? true,
- loggingOverrides?.logLevel ?? 'debug',
- loggingOverrides?.logNamespaces ?? '*'
- );
- }, [loggingOverrides]);
-
- useLayoutEffect(() => {
- if (studioInitAction) {
- $didStudioInit.set(false);
- }
- }, [studioInitAction]);
-
- useEffect(() => {
- // configure API client token
- if (token) {
- $authToken.set(token);
- }
-
- // configure API client base url
- if (apiUrl) {
- $baseUrl.set(apiUrl);
- }
-
- // configure API client project header
- if (projectId) {
- $projectId.set(projectId);
- }
-
- // configure API client project header
- if (queueId) {
- $queueId.set(queueId);
- }
-
- // reset dynamically added middlewares
- resetMiddlewares();
-
- // TODO: at this point, after resetting the middleware, we really ought to clean up the socket
- // stuff by calling `dispatch(socketReset())`. but we cannot dispatch from here as we are
- // outside the provider. it's not needed until there is the possibility that we will change
- // the `apiUrl`/`token` dynamically.
-
- // rebuild socket middleware with token and apiUrl
- if (middleware && middleware.length > 0) {
- addMiddleware(...middleware);
- }
-
- return () => {
- // Reset the API client token and base url on unmount
- $baseUrl.set(undefined);
- $authToken.set(undefined);
- $projectId.set(undefined);
- $queueId.set(DEFAULT_QUEUE_ID);
- };
- }, [apiUrl, token, middleware, projectId, queueId, projectName, projectUrl]);
-
- useEffect(() => {
- if (customStarUi) {
- $customStarUI.set(customStarUi);
- }
-
- return () => {
- $customStarUI.set(undefined);
- };
- }, [customStarUi]);
-
- useEffect(() => {
- if (accountTypeText) {
- $accountTypeText.set(accountTypeText);
- }
-
- return () => {
- $accountTypeText.set('');
- };
- }, [accountTypeText]);
-
- useEffect(() => {
- if (videoUpsellComponent) {
- $videoUpsellComponent.set(videoUpsellComponent);
- }
-
- return () => {
- $videoUpsellComponent.set(undefined);
- };
- }, [videoUpsellComponent]);
-
- useEffect(() => {
- if (customNavComponent) {
- $customNavComponent.set(customNavComponent);
- }
-
- return () => {
- $customNavComponent.set(undefined);
- };
- }, [customNavComponent]);
-
- useEffect(() => {
- if (accountSettingsLink) {
- $accountSettingsLink.set(accountSettingsLink);
- }
-
- return () => {
- $accountSettingsLink.set(undefined);
- };
- }, [accountSettingsLink]);
-
- useEffect(() => {
- if (openAPISchemaUrl) {
- $openAPISchemaUrl.set(openAPISchemaUrl);
- }
-
- return () => {
- $openAPISchemaUrl.set(undefined);
- };
- }, [openAPISchemaUrl]);
-
- useEffect(() => {
- $projectName.set(projectName);
-
- return () => {
- $projectName.set(undefined);
- };
- }, [projectName]);
-
- useEffect(() => {
- $projectUrl.set(projectUrl);
-
- return () => {
- $projectUrl.set(undefined);
- };
- }, [projectUrl]);
-
- useEffect(() => {
- if (logo) {
- $logo.set(logo);
- }
-
- return () => {
- $logo.set(undefined);
- };
- }, [logo]);
-
- useEffect(() => {
- if (toastMap) {
- $toastMap.set(toastMap);
- }
-
- return () => {
- $toastMap.set(undefined);
- };
- }, [toastMap]);
-
- useEffect(() => {
- if (whatsNew) {
- $whatsNew.set(whatsNew);
- }
-
- return () => {
- $whatsNew.set(undefined);
- };
- }, [whatsNew]);
-
- useEffect(() => {
- if (onClickGoToModelManager) {
- $onClickGoToModelManager.set(onClickGoToModelManager);
- }
-
- return () => {
- $onClickGoToModelManager.set(undefined);
- };
- }, [onClickGoToModelManager]);
-
- useEffect(() => {
- if (workflowCategories) {
- $workflowLibraryCategoriesOptions.set(workflowCategories);
- }
-
- return () => {
- $workflowLibraryCategoriesOptions.set(DEFAULT_WORKFLOW_LIBRARY_CATEGORIES);
- };
- }, [workflowCategories]);
-
- useEffect(() => {
- if (workflowTagCategories) {
- $workflowLibraryTagCategoriesOptions.set(workflowTagCategories);
- }
-
- return () => {
- $workflowLibraryTagCategoriesOptions.set(DEFAULT_WORKFLOW_LIBRARY_TAG_CATEGORIES);
- };
- }, [workflowTagCategories]);
-
- useEffect(() => {
- if (workflowSortOptions) {
- $workflowLibrarySortOptions.set(workflowSortOptions);
- }
-
- return () => {
- $workflowLibrarySortOptions.set(DEFAULT_WORKFLOW_LIBRARY_SORT_OPTIONS);
- };
- }, [workflowSortOptions]);
-
- useEffect(() => {
- if (socketOptions) {
- $socketOptions.set(socketOptions);
- }
- return () => {
- $socketOptions.set({});
- };
- }, [socketOptions]);
-
- useEffect(() => {
- if (isDebugging) {
- $isDebugging.set(isDebugging);
- }
- return () => {
- $isDebugging.set(false);
- };
- }, [isDebugging]);
-
useEffect(() => {
const onRehydrated = () => {
setDidRehydrate(true);
};
- const store = createStore({ persist: true, persistDebounce: storagePersistDebounce, onRehydrated });
+ const store = createStore({ persist: true, persistDebounce: 300, onRehydrated });
setStore(store);
$store.set(store);
if (import.meta.env.MODE === 'development') {
@@ -318,7 +32,7 @@ const InvokeAIUI = ({
window.$store = undefined;
}
};
- }, [storagePersistDebounce]);
+ }, []);
if (!store || !didRehydrate) {
return ;
@@ -328,7 +42,7 @@ const InvokeAIUI = ({
}>
-
+
diff --git a/invokeai/frontend/web/src/app/components/types.ts b/invokeai/frontend/web/src/app/components/types.ts
deleted file mode 100644
index dbec6a72a86..00000000000
--- a/invokeai/frontend/web/src/app/components/types.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { Middleware } from '@reduxjs/toolkit';
-import type { StudioInitAction } from 'app/hooks/useStudioInitAction';
-import type { LoggingOverrides } from 'app/logging/logger';
-import type { CustomStarUi } from 'app/store/nanostores/customStarUI';
-import type { PartialAppConfig } from 'app/types/invokeai';
-import type { SocketOptions } from 'dgram';
-import type { WorkflowSortOption, WorkflowTagCategory } from 'features/nodes/store/workflowLibrarySlice';
-import type { WorkflowCategory } from 'features/nodes/types/workflow';
-import type { ToastConfig } from 'features/toast/toast';
-import type { PropsWithChildren, ReactNode } from 'react';
-import type { ManagerOptions } from 'socket.io-client';
-
-export interface InvokeAIUIProps extends PropsWithChildren {
- apiUrl?: string;
- openAPISchemaUrl?: string;
- token?: string;
- config?: PartialAppConfig;
- customNavComponent?: ReactNode;
- accountSettingsLink?: string;
- middleware?: Middleware[];
- projectId?: string;
- projectName?: string;
- projectUrl?: string;
- queueId?: string;
- studioInitAction?: StudioInitAction;
- customStarUi?: CustomStarUi;
- socketOptions?: Partial;
- isDebugging?: boolean;
- logo?: ReactNode;
- toastMap?: Record;
- accountTypeText?: string;
- videoUpsellComponent?: ReactNode;
- whatsNew?: ReactNode[];
- workflowCategories?: WorkflowCategory[];
- workflowTagCategories?: WorkflowTagCategory[];
- workflowSortOptions?: WorkflowSortOption[];
- loggingOverrides?: LoggingOverrides;
- /**
- * If provided, overrides in-app navigation to the model manager
- */
- onClickGoToModelManager?: () => void;
- storagePersistDebounce?: number;
-}
diff --git a/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts b/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts
deleted file mode 100644
index 80c76e31484..00000000000
--- a/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts
+++ /dev/null
@@ -1,262 +0,0 @@
-import { useStore } from '@nanostores/react';
-import { useAppStore } from 'app/store/storeHooks';
-import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
-import { withResultAsync } from 'common/util/result';
-import { canvasReset } from 'features/controlLayers/store/actions';
-import { rasterLayerAdded } from 'features/controlLayers/store/canvasSlice';
-import type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
-import { imageDTOToImageObject } from 'features/controlLayers/store/util';
-import { sentImageToCanvas } from 'features/gallery/store/actions';
-import { MetadataUtils } from 'features/metadata/parsing';
-import { $hasTemplates } from 'features/nodes/store/nodesSlice';
-import { $isWorkflowLibraryModalOpen } from 'features/nodes/store/workflowLibraryModal';
-import {
- $workflowLibraryTagOptions,
- workflowLibraryTagsReset,
- workflowLibraryTagToggled,
- workflowLibraryViewChanged,
-} from 'features/nodes/store/workflowLibrarySlice';
-import { $isStylePresetsMenuOpen, activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice';
-import { toast } from 'features/toast/toast';
-import { navigationApi } from 'features/ui/layouts/navigation-api';
-import { LAUNCHPAD_PANEL_ID, WORKSPACE_PANEL_ID } from 'features/ui/layouts/shared';
-import { useLoadWorkflowWithDialog } from 'features/workflowLibrary/components/LoadWorkflowConfirmationAlertDialog';
-import { atom } from 'nanostores';
-import { useCallback, useEffect } from 'react';
-import { useTranslation } from 'react-i18next';
-import { getImageDTO, getImageMetadata } from 'services/api/endpoints/images';
-import { getStylePreset } from 'services/api/endpoints/stylePresets';
-
-type _StudioInitAction = { type: T; data: U };
-
-type LoadWorkflowAction = _StudioInitAction<'loadWorkflow', { workflowId: string }>;
-type SelectStylePresetAction = _StudioInitAction<'selectStylePreset', { stylePresetId: string }>;
-type SendToCanvasAction = _StudioInitAction<'sendToCanvas', { imageName: string }>;
-type UseAllParametersAction = _StudioInitAction<'useAllParameters', { imageName: string }>;
-type StudioDestinationAction = _StudioInitAction<
- 'goToDestination',
- {
- destination:
- | 'generation'
- | 'canvas'
- | 'workflows'
- | 'upscaling'
- | 'video'
- | 'viewAllWorkflows'
- | 'viewAllWorkflowsRecommended'
- | 'viewAllStylePresets';
- }
->;
-// Use global state to show loader until we are ready to render the studio.
-export const $didStudioInit = atom(false);
-
-export type StudioInitAction =
- | LoadWorkflowAction
- | SelectStylePresetAction
- | SendToCanvasAction
- | UseAllParametersAction
- | StudioDestinationAction;
-
-/**
- * A hook that performs an action when the studio is initialized. This is useful for deep linking into the studio.
- *
- * The action is performed only once, when the hook is first run.
- *
- * In this hook, we prefer to use imperative APIs over hooks to avoid re-rendering the parent component. For example:
- * - Use `getImageDTO` helper instead of `useGetImageDTO`
- * - Usee the `$imageViewer` atom instead of `useImageViewer`
- */
-export const useStudioInitAction = (action?: StudioInitAction) => {
- useAssertSingleton('useStudioInitAction');
- const { t } = useTranslation();
- const didParseOpenAPISchema = useStore($hasTemplates);
- const store = useAppStore();
- const loadWorkflowWithDialog = useLoadWorkflowWithDialog();
- const workflowLibraryTagOptions = useStore($workflowLibraryTagOptions);
-
- const handleSendToCanvas = useCallback(
- async (imageName: string) => {
- // Try to the image DTO - use an imperative helper, rather than `useGetImageDTO`, so that we aren't re-rendering
- // the parent of this hook whenever the image name changes
- const getImageDTOResult = await withResultAsync(() => getImageDTO(imageName));
- if (getImageDTOResult.isErr()) {
- toast({
- title: t('toast.unableToLoadImage'),
- status: 'error',
- });
- return;
- }
- const imageDTO = getImageDTOResult.value;
- const imageObject = imageDTOToImageObject(imageDTO);
- const overrides: Partial = {
- objects: [imageObject],
- };
- await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID);
- store.dispatch(canvasReset());
- store.dispatch(rasterLayerAdded({ overrides, isSelected: true }));
- store.dispatch(sentImageToCanvas());
- toast({
- title: t('toast.sentToCanvas'),
- status: 'info',
- });
- },
- [store, t]
- );
-
- const handleUseAllMetadata = useCallback(
- async (imageName: string) => {
- // Try to the image metadata - use an imperative helper, rather than `useGetImageMetadata`, so that we aren't
- // re-rendering the parent of this hook whenever the image name changes
- const getImageMetadataResult = await withResultAsync(() => getImageMetadata(imageName));
- if (getImageMetadataResult.isErr()) {
- toast({
- title: t('toast.unableToLoadImageMetadata'),
- status: 'error',
- });
- return;
- }
- const metadata = getImageMetadataResult.value;
- store.dispatch(canvasReset());
- // This shows a toast
- await MetadataUtils.recallAllImageMetadata(metadata, store);
- },
- [store, t]
- );
-
- const handleLoadWorkflow = useCallback(
- (workflowId: string) => {
- // This shows a toast
- loadWorkflowWithDialog({
- type: 'library',
- data: workflowId,
- onSuccess: () => {
- navigationApi.switchToTab('workflows');
- },
- });
- },
- [loadWorkflowWithDialog]
- );
-
- const handleSelectStylePreset = useCallback(
- async (stylePresetId: string) => {
- const getStylePresetResult = await withResultAsync(() => getStylePreset(stylePresetId));
- if (getStylePresetResult.isErr()) {
- toast({
- title: t('toast.unableToLoadStylePreset'),
- status: 'error',
- });
- return;
- }
- store.dispatch(activeStylePresetIdChanged(stylePresetId));
- navigationApi.switchToTab('canvas');
- toast({
- title: t('toast.stylePresetLoaded'),
- status: 'info',
- });
- },
- [store, t]
- );
-
- const handleGoToDestination = useCallback(
- async (destination: StudioDestinationAction['data']['destination']) => {
- switch (destination) {
- case 'generation':
- // Go to the generate tab, open the launchpad
- await navigationApi.focusPanel('generate', LAUNCHPAD_PANEL_ID);
- break;
- case 'canvas':
- // Go to the canvas tab, open the launchpad
- await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID);
- break;
- case 'workflows':
- // Go to the workflows tab
- navigationApi.switchToTab('workflows');
- break;
- case 'upscaling':
- // Go to the upscaling tab
- navigationApi.switchToTab('upscaling');
- break;
- case 'video':
- // Go to the video tab
- await navigationApi.focusPanel('video', LAUNCHPAD_PANEL_ID);
- break;
- case 'viewAllWorkflows':
- // Go to the workflows tab and open the workflow library modal
- navigationApi.switchToTab('workflows');
- $isWorkflowLibraryModalOpen.set(true);
- break;
- case 'viewAllWorkflowsRecommended':
- // Go to the workflows tab and open the workflow library modal with the recommended workflows view
- navigationApi.switchToTab('workflows');
- $isWorkflowLibraryModalOpen.set(true);
- store.dispatch(workflowLibraryViewChanged('defaults'));
- store.dispatch(workflowLibraryTagsReset());
- for (const tag of workflowLibraryTagOptions) {
- if (tag.recommended) {
- store.dispatch(workflowLibraryTagToggled(tag.label));
- }
- }
- break;
- case 'viewAllStylePresets':
- // Go to the canvas tab and open the style presets menu
- navigationApi.switchToTab('canvas');
- $isStylePresetsMenuOpen.set(true);
- break;
- }
- },
- [store, workflowLibraryTagOptions]
- );
-
- const handleStudioInitAction = useCallback(
- async (action: StudioInitAction) => {
- // This cannot be in the useEffect below because we need to await some of the actions before setting didStudioInit.
- switch (action.type) {
- case 'loadWorkflow':
- await handleLoadWorkflow(action.data.workflowId);
- break;
- case 'selectStylePreset':
- await handleSelectStylePreset(action.data.stylePresetId);
- break;
-
- case 'sendToCanvas':
- await handleSendToCanvas(action.data.imageName);
- break;
-
- case 'useAllParameters':
- await handleUseAllMetadata(action.data.imageName);
- break;
-
- case 'goToDestination':
- handleGoToDestination(action.data.destination);
- break;
-
- default:
- break;
- }
- $didStudioInit.set(true);
- },
- [handleGoToDestination, handleLoadWorkflow, handleSelectStylePreset, handleSendToCanvas, handleUseAllMetadata]
- );
-
- useEffect(() => {
- if ($didStudioInit.get() || !didParseOpenAPISchema) {
- return;
- }
-
- if (!action) {
- $didStudioInit.set(true);
- return;
- }
-
- handleStudioInitAction(action);
- }, [
- handleSendToCanvas,
- handleUseAllMetadata,
- action,
- handleSelectStylePreset,
- handleGoToDestination,
- handleLoadWorkflow,
- didParseOpenAPISchema,
- handleStudioInitAction,
- ]);
-};
diff --git a/invokeai/frontend/web/src/app/logging/logger.ts b/invokeai/frontend/web/src/app/logging/logger.ts
index 1f753f97bb7..2428638dd12 100644
--- a/invokeai/frontend/web/src/app/logging/logger.ts
+++ b/invokeai/frontend/web/src/app/logging/logger.ts
@@ -98,3 +98,12 @@ export const configureLogging = (
ROARR.write = createLogWriter({ styleOutput });
};
+
+/*
+ * We need to configure logging before anything else happens - useLayoutEffect ensures we set this at the first
+ * possible opportunity.
+ *
+ * Once redux initializes, we will check the user's settings and update the logging config accordingly. See
+ * `useSyncLoggingConfig`.
+ */
+configureLogging(true, 'debug', '*');
diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/driver.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/driver.ts
index ef42a5fa2db..9e67770b436 100644
--- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/driver.ts
+++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/driver.ts
@@ -1,8 +1,5 @@
import { logger } from 'app/logging/logger';
import { StorageError } from 'app/store/enhancers/reduxRemember/errors';
-import { $authToken } from 'app/store/nanostores/authToken';
-import { $projectId } from 'app/store/nanostores/projectId';
-import { $queueId } from 'app/store/nanostores/queueId';
import type { UseStore } from 'idb-keyval';
import { createStore as idbCreateStore, del as idbDel, get as idbGet } from 'idb-keyval';
import type { Driver } from 'redux-remember';
@@ -19,24 +16,11 @@ const getUrl = (endpoint: 'get_by_key' | 'set_by_key' | 'delete', key?: string)
query['key'] = key;
}
- const path = buildV1Url(`client_state/${$queueId.get()}/${endpoint}`, query);
+ const path = buildV1Url(`client_state/default/${endpoint}`, query);
const url = `${baseUrl}/${path}`;
return url;
};
-const getHeaders = () => {
- const headers = new Headers();
- const authToken = $authToken.get();
- const projectId = $projectId.get();
- if (authToken) {
- headers.set('Authorization', `Bearer ${authToken}`);
- }
- if (projectId) {
- headers.set('project-id', projectId);
- }
- return headers;
-};
-
// Persistence happens per slice. To track when persistence is in progress, maintain a ref count, incrementing
// it when a slice is being persisted and decrementing it when the persistence is done.
let persistRefCount = 0;
@@ -87,8 +71,7 @@ const getIdbKey = (key: string) => {
const getItem = async (key: string) => {
try {
const url = getUrl('get_by_key', key);
- const headers = getHeaders();
- const res = await fetch(url, { method: 'GET', headers });
+ const res = await fetch(url, { method: 'GET' });
if (!res.ok) {
throw new Error(`Response status: ${res.status}`);
}
@@ -130,7 +113,6 @@ const getItem = async (key: string) => {
} catch (originalError) {
throw new StorageError({
key,
- projectId: $projectId.get(),
originalError,
});
}
@@ -148,8 +130,7 @@ const setItem = async (key: string, value: string) => {
}
log.trace({ key, last: lastPersistedState.get(key), next: value }, `Persisting state for ${key}`);
const url = getUrl('set_by_key', key);
- const headers = getHeaders();
- const res = await fetch(url, { method: 'POST', headers, body: value });
+ const res = await fetch(url, { method: 'POST', body: value });
if (!res.ok) {
throw new Error(`Response status: ${res.status}`);
}
@@ -160,7 +141,6 @@ const setItem = async (key: string, value: string) => {
throw new StorageError({
key,
value,
- projectId: $projectId.get(),
originalError,
});
} finally {
@@ -178,8 +158,7 @@ export const clearStorage = async () => {
try {
persistRefCount++;
const url = getUrl('delete');
- const headers = getHeaders();
- const res = await fetch(url, { method: 'POST', headers });
+ const res = await fetch(url, { method: 'POST' });
if (!res.ok) {
throw new Error(`Response status: ${res.status}`);
}
diff --git a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/errors.ts b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/errors.ts
index 9266ee478ff..87c89b27f51 100644
--- a/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/errors.ts
+++ b/invokeai/frontend/web/src/app/store/enhancers/reduxRemember/errors.ts
@@ -7,7 +7,6 @@ type StorageErrorArgs = {
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ // any is correct
value?: any;
originalError?: unknown;
- projectId?: string;
};
export class StorageError extends Error {
@@ -15,18 +14,14 @@ export class StorageError extends Error {
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ // any is correct
value?: any;
originalError?: Error;
- projectId?: string;
- constructor({ key, value, originalError, projectId }: StorageErrorArgs) {
+ constructor({ key, value, originalError }: StorageErrorArgs) {
super(`Error setting ${key}`);
this.name = 'StorageSetError';
this.key = key;
if (value !== undefined) {
this.value = value;
}
- if (projectId !== undefined) {
- this.projectId = projectId;
- }
if (originalError instanceof Error) {
this.originalError = originalError;
}
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts
index 3a42213e535..a408a94c041 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts
@@ -1,14 +1,8 @@
import { isAnyOf } from '@reduxjs/toolkit';
import type { AppStartListening } from 'app/store/store';
-import {
- selectGalleryView,
- selectGetImageNamesQueryArgs,
- selectGetVideoIdsQueryArgs,
- selectSelectedBoardId,
-} from 'features/gallery/store/gallerySelectors';
+import { selectGetImageNamesQueryArgs, selectSelectedBoardId } from 'features/gallery/store/gallerySelectors';
import { boardIdSelected, galleryViewChanged, itemSelected } from 'features/gallery/store/gallerySlice';
import { imagesApi } from 'services/api/endpoints/images';
-import { videosApi } from 'services/api/endpoints/videos';
export const addBoardIdSelectedListener = (startAppListening: AppStartListening) => {
startAppListening({
@@ -25,56 +19,29 @@ export const addBoardIdSelectedListener = (startAppListening: AppStartListening)
const state = getState();
const board_id = selectSelectedBoardId(state);
- const view = selectGalleryView(state);
- if (view === 'images' || view === 'assets') {
- const queryArgs = { ...selectGetImageNamesQueryArgs(state), board_id };
- // wait until the board has some images - maybe it already has some from a previous fetch
- // must use getState() to ensure we do not have stale state
- const isSuccess = await condition(
- () => imagesApi.endpoints.getImageNames.select(queryArgs)(getState()).isSuccess,
- 5000
- );
+ const queryArgs = { ...selectGetImageNamesQueryArgs(state), board_id };
+ // wait until the board has some images - maybe it already has some from a previous fetch
+ // must use getState() to ensure we do not have stale state
+ const isSuccess = await condition(
+ () => imagesApi.endpoints.getImageNames.select(queryArgs)(getState()).isSuccess,
+ 5000
+ );
- if (!isSuccess) {
- dispatch(itemSelected(null));
- return;
- }
+ if (!isSuccess) {
+ dispatch(itemSelected(null));
+ return;
+ }
- // the board was just changed - we can select the first image
- const imageNames = imagesApi.endpoints.getImageNames.select(queryArgs)(getState()).data?.image_names;
+ // the board was just changed - we can select the first image
+ const imageNames = imagesApi.endpoints.getImageNames.select(queryArgs)(getState()).data?.image_names;
- const imageToSelect = imageNames && imageNames.length > 0 ? imageNames[0] : null;
+ const imageToSelect = imageNames && imageNames.length > 0 ? imageNames[0] : null;
- if (imageToSelect) {
- dispatch(itemSelected({ type: 'image', id: imageToSelect }));
- } else {
- dispatch(itemSelected(null));
- }
+ if (imageToSelect) {
+ dispatch(itemSelected({ type: 'image', id: imageToSelect }));
} else {
- const queryArgs = { ...selectGetVideoIdsQueryArgs(state), board_id };
- // wait until the board has some images - maybe it already has some from a previous fetch
- // must use getState() to ensure we do not have stale state
- const isSuccess = await condition(
- () => videosApi.endpoints.getVideoIds.select(queryArgs)(getState()).isSuccess,
- 5000
- );
-
- if (!isSuccess) {
- dispatch(itemSelected(null));
- return;
- }
-
- // the board was just changed - we can select the first image
- const videoIds = videosApi.endpoints.getVideoIds.select(queryArgs)(getState()).data?.video_ids;
-
- const videoToSelect = videoIds && videoIds.length > 0 ? videoIds[0] : null;
-
- if (videoToSelect) {
- dispatch(itemSelected({ type: 'video', id: videoToSelect }));
- } else {
- dispatch(itemSelected(null));
- }
+ dispatch(itemSelected(null));
}
},
});
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/getOpenAPISchema.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/getOpenAPISchema.ts
index f266f147ee8..416c77b9dd7 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/getOpenAPISchema.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/getOpenAPISchema.ts
@@ -13,13 +13,12 @@ const log = logger('system');
export const addGetOpenAPISchemaListener = (startAppListening: AppStartListening) => {
startAppListening({
matcher: appInfoApi.endpoints.getOpenAPISchema.matchFulfilled,
- effect: (action, { getState }) => {
+ effect: (action) => {
const schemaJSON = action.payload;
log.debug({ schemaJSON: parseify(schemaJSON) } as JsonObject, 'Received OpenAPI schema');
- const { nodesAllowlist, nodesDenylist } = getState().config;
- const nodeTemplates = parseSchema(schemaJSON, nodesAllowlist, nodesDenylist);
+ const nodeTemplates = parseSchema(schemaJSON);
log.debug({ nodeTemplates } as JsonObject, `Built ${size(nodeTemplates)} node templates`);
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 562dd7c27f3..f421009030f 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
@@ -1,8 +1,6 @@
-import { isAnyOf } from '@reduxjs/toolkit';
import { logger } from 'app/logging/logger';
import type { AppStartListening, RootState } from 'app/store/store';
import { omit } from 'es-toolkit/compat';
-import { imageUploadedClientSide } from 'features/gallery/store/actions';
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
import { boardIdSelected, galleryViewChanged } from 'features/gallery/store/gallerySlice';
import { toast } from 'features/toast/toast';
@@ -10,7 +8,6 @@ import { t } from 'i18next';
import { boardsApi } from 'services/api/endpoints/boards';
import { imagesApi } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types';
-import { getCategories, getListImagesUrl } from 'services/api/util';
const log = logger('gallery');
/**
@@ -36,52 +33,20 @@ let lastUploadedToastTimeout: number | null = null;
export const addImageUploadedFulfilledListener = (startAppListening: AppStartListening) => {
startAppListening({
- matcher: isAnyOf(imagesApi.endpoints.uploadImage.matchFulfilled, imageUploadedClientSide),
+ matcher: imagesApi.endpoints.uploadImage.matchFulfilled,
effect: (action, { dispatch, getState }) => {
let imageDTO: ImageDTO;
let silent;
let isFirstUploadOfBatch = true;
-
- if (imageUploadedClientSide.match(action)) {
- imageDTO = action.payload.imageDTO;
- silent = action.payload.silent;
- isFirstUploadOfBatch = action.payload.isFirstUploadOfBatch;
- } else if (imagesApi.endpoints.uploadImage.matchFulfilled(action)) {
- imageDTO = action.payload;
- silent = action.meta.arg.originalArgs.silent;
- isFirstUploadOfBatch = action.meta.arg.originalArgs.isFirstUploadOfBatch ?? true;
- } else {
- return;
- }
+ imageDTO = action.payload;
+ silent = action.meta.arg.originalArgs.silent;
+ isFirstUploadOfBatch = action.meta.arg.originalArgs.isFirstUploadOfBatch ?? true;
if (silent || imageDTO.is_intermediate) {
// If the image is silent or intermediate, we don't want to show a toast
return;
}
- if (imageUploadedClientSide.match(action)) {
- const categories = getCategories(imageDTO);
- const boardId = imageDTO.board_id ?? 'none';
- dispatch(
- imagesApi.util.invalidateTags([
- {
- type: 'ImageList',
- id: getListImagesUrl({
- board_id: boardId,
- categories,
- }),
- },
- {
- type: 'Board',
- id: boardId,
- },
- {
- type: 'BoardImagesTotal',
- id: boardId,
- },
- ])
- );
- }
const state = getState();
log.debug({ imageDTO }, 'Image uploaded');
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts
index e53fc977b98..d5c58528cb9 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts
@@ -17,14 +17,8 @@ import { zParameterModel } from 'features/parameters/types/parameterSchemas';
import { toast } from 'features/toast/toast';
import { t } from 'i18next';
import { selectGlobalRefImageModels, selectRegionalRefImageModels } from 'services/api/hooks/modelsByType';
-import type { AnyModelConfig } from 'services/api/types';
-import {
- isChatGPT4oModelConfig,
- isFluxKontextApiModelConfig,
- isFluxKontextModelConfig,
- isFluxReduxModelConfig,
- isGemini2_5ModelConfig,
-} from 'services/api/types';
+import type { FLUXKontextModelConfig, FLUXReduxModelConfig, IPAdapterModelConfig } from 'services/api/types';
+import { isFluxKontextModelConfig, isFluxReduxModelConfig } from 'services/api/types';
const log = logger('models');
@@ -68,26 +62,19 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
// to choose the best available model based on the new main model.
const allRefImageModels = selectGlobalRefImageModels(state).filter(({ base }) => base === newBase);
- let newGlobalRefImageModel = null;
+ let newGlobalRefImageModel: IPAdapterModelConfig | FLUXKontextModelConfig | FLUXReduxModelConfig | null =
+ null;
// Certain models require the ref image model to be the same as the main model - others just need a matching
// base. Helper to grab the first exact match or the first available model if no exact match is found.
- const exactMatchOrFirst = (candidates: T[]): T | null =>
- candidates.find(({ key }) => key === newModel.key) ?? candidates[0] ?? null;
+ const exactMatchOrFirst = (
+ candidates: T[]
+ ): T | null => candidates.find(({ key }) => key === newModel.key) ?? candidates[0] ?? null;
// The only way we can differentiate between FLUX and FLUX Kontext is to check for "kontext" in the name
if (newModel.base === 'flux' && newModel.name.toLowerCase().includes('kontext')) {
const fluxKontextDevModels = allRefImageModels.filter(isFluxKontextModelConfig);
newGlobalRefImageModel = exactMatchOrFirst(fluxKontextDevModels);
- } else if (newModel.base === 'chatgpt-4o') {
- const chatGPT4oModels = allRefImageModels.filter(isChatGPT4oModelConfig);
- newGlobalRefImageModel = exactMatchOrFirst(chatGPT4oModels);
- } else if (newModel.base === 'gemini-2.5') {
- const gemini2_5Models = allRefImageModels.filter(isGemini2_5ModelConfig);
- newGlobalRefImageModel = exactMatchOrFirst(gemini2_5Models);
- } else if (newModel.base === 'flux-kontext') {
- const fluxKontextApiModels = allRefImageModels.filter(isFluxKontextApiModelConfig);
- newGlobalRefImageModel = exactMatchOrFirst(fluxKontextApiModels);
} else if (newModel.base === 'flux') {
const fluxReduxModels = allRefImageModels.filter(isFluxReduxModelConfig);
newGlobalRefImageModel = fluxReduxModels[0] ?? null;
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 63602339a9b..8cbbc72343b 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
@@ -19,14 +19,12 @@ import {
isRegionalGuidanceFLUXReduxConfig,
isRegionalGuidanceIPAdapterConfig,
} from 'features/controlLayers/store/types';
-import { zModelIdentifierField } from 'features/nodes/types/common';
import { modelSelected } from 'features/parameters/store/actions';
import {
postProcessingModelChanged,
tileControlnetModelChanged,
upscaleModelChanged,
} from 'features/parameters/store/upscaleSlice';
-import { videoModelChanged } from 'features/parameters/store/videoSlice';
import {
zParameterCLIPEmbedModel,
zParameterSpandrelImageToImageModel,
@@ -49,7 +47,6 @@ import {
isRefinerMainModelModelConfig,
isSpandrelImageToImageModelConfig,
isT5EncoderModelConfigOrSubmodel,
- isVideoModelConfig,
} from 'services/api/types';
import type { JsonObject } from 'type-fest';
@@ -90,7 +87,6 @@ export const addModelsLoadedListener = (startAppListening: AppStartListening) =>
handleCLIPEmbedModels(models, state, dispatch, log);
handleFLUXVAEModels(models, state, dispatch, log);
handleFLUXReduxModels(models, state, dispatch, log);
- handleVideoModels(models, state, dispatch, log);
},
});
};
@@ -123,19 +119,6 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
return;
}
- // If we have a default model, try to use it
- if (state.config.sd.defaultModel) {
- const defaultModel = allMainModels.find((m) => m.key === state.config.sd.defaultModel);
- if (defaultModel) {
- log.debug(
- { selectedMainModel, defaultModel },
- 'No selected main model or selected main model is not available, selecting default model'
- );
- dispatch(modelSelected(defaultModel));
- return;
- }
- }
-
log.debug(
{ selectedMainModel, firstModel },
'No selected main model or selected main model is not available, selecting first available model'
@@ -203,22 +186,6 @@ const handleLoRAModels: ModelHandler = (models, state, dispatch, log) => {
});
};
-const handleVideoModels: ModelHandler = (models, state, dispatch, log) => {
- const videoModels = models.filter(isVideoModelConfig);
- const selectedVideoModel = state.video.videoModel;
-
- if (selectedVideoModel && videoModels.some((m) => m.key === selectedVideoModel.key)) {
- return;
- }
-
- const firstModel = videoModels[0] || null;
- if (firstModel) {
- log.debug({ firstModel }, 'No video model selected, selecting first available video model');
- dispatch(videoModelChanged({ videoModel: zModelIdentifierField.parse(firstModel) }));
- return;
- }
-};
-
const handleControlAdapterModels: ModelHandler = (models, state, dispatch, log) => {
const caModels = models.filter(isControlLayerModelConfig);
selectCanvasSlice(state).controlLayers.entities.forEach((entity) => {
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketConnected.ts
index 64370eee8bc..f8f7d1659a1 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketConnected.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketConnected.ts
@@ -1,7 +1,6 @@
import { objectEquals } from '@observ33r/object-equals';
import { createAction } from '@reduxjs/toolkit';
import { logger } from 'app/logging/logger';
-import { $baseUrl } from 'app/store/nanostores/baseUrl';
import type { AppStartListening } from 'app/store/store';
import { atom } from 'nanostores';
import { api } from 'services/api';
@@ -16,7 +15,7 @@ export const socketConnected = createAction('socket/connected');
export const addSocketConnectedEventListener = (startAppListening: AppStartListening) => {
startAppListening({
actionCreator: socketConnected,
- effect: async (action, { dispatch, getState, cancelActiveListeners, delay }) => {
+ effect: async (action, { dispatch, getState }) => {
/**
* The rest of this listener has recovery logic for when the socket disconnects and reconnects.
*
@@ -43,20 +42,12 @@ export const addSocketConnectedEventListener = (startAppListening: AppStartListe
// Else, we need to compare the last-known queue status with the current queue status, re-fetching
// everything if it has changed.
-
- if ($baseUrl.get()) {
- // If we have a baseUrl (e.g. not localhost), we need to debounce the re-fetch to not hammer server
- cancelActiveListeners();
- // Add artificial jitter to the debounce
- await delay(1000 + Math.random() * 1000);
- }
-
const prevQueueStatusData = selectQueueStatus(getState()).data;
try {
// Fetch the queue status again
const queueStatusRequest = dispatch(
- await queueApi.endpoints.getQueueStatus.initiate(undefined, {
+ queueApi.endpoints.getQueueStatus.initiate(undefined, {
forceRefetch: true,
subscribe: false,
})
diff --git a/invokeai/frontend/web/src/app/store/nanostores/accountSettingsLink.ts b/invokeai/frontend/web/src/app/store/nanostores/accountSettingsLink.ts
deleted file mode 100644
index cf41facb7c3..00000000000
--- a/invokeai/frontend/web/src/app/store/nanostores/accountSettingsLink.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { atom } from 'nanostores';
-
-export const $accountSettingsLink = atom(undefined);
diff --git a/invokeai/frontend/web/src/app/store/nanostores/accountTypeText.ts b/invokeai/frontend/web/src/app/store/nanostores/accountTypeText.ts
deleted file mode 100644
index 4008b86cef2..00000000000
--- a/invokeai/frontend/web/src/app/store/nanostores/accountTypeText.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { atom } from 'nanostores';
-
-export const $accountTypeText = atom('');
diff --git a/invokeai/frontend/web/src/app/store/nanostores/authToken.ts b/invokeai/frontend/web/src/app/store/nanostores/authToken.ts
deleted file mode 100644
index 1b1e2137309..00000000000
--- a/invokeai/frontend/web/src/app/store/nanostores/authToken.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { atom, computed } from 'nanostores';
-
-/**
- * The user's auth token.
- */
-export const $authToken = atom();
-
-/**
- * The crossOrigin value to use for all image loading. Depends on whether the user is authenticated.
- */
-export const $crossOrigin = computed($authToken, (token) => (token ? 'use-credentials' : 'anonymous'));
diff --git a/invokeai/frontend/web/src/app/store/nanostores/baseUrl.ts b/invokeai/frontend/web/src/app/store/nanostores/baseUrl.ts
deleted file mode 100644
index 19bebab0ef8..00000000000
--- a/invokeai/frontend/web/src/app/store/nanostores/baseUrl.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { atom } from 'nanostores';
-
-/**
- * The OpenAPI base url.
- */
-export const $baseUrl = atom();
diff --git a/invokeai/frontend/web/src/app/store/nanostores/customNavComponent.ts b/invokeai/frontend/web/src/app/store/nanostores/customNavComponent.ts
deleted file mode 100644
index 1a6a5571a03..00000000000
--- a/invokeai/frontend/web/src/app/store/nanostores/customNavComponent.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { atom } from 'nanostores';
-import type { ReactNode } from 'react';
-
-export const $customNavComponent = atom(undefined);
diff --git a/invokeai/frontend/web/src/app/store/nanostores/customStarUI.ts b/invokeai/frontend/web/src/app/store/nanostores/customStarUI.ts
deleted file mode 100644
index 9f6628ac9cd..00000000000
--- a/invokeai/frontend/web/src/app/store/nanostores/customStarUI.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import type { MenuItemProps } from '@invoke-ai/ui-library';
-import { atom } from 'nanostores';
-
-export type CustomStarUi = {
- on: {
- icon: MenuItemProps['icon'];
- text: string;
- };
- off: {
- icon: MenuItemProps['icon'];
- text: string;
- };
-};
-export const $customStarUI = atom(undefined);
diff --git a/invokeai/frontend/web/src/app/store/nanostores/isDebugging.ts b/invokeai/frontend/web/src/app/store/nanostores/isDebugging.ts
deleted file mode 100644
index b71cab53088..00000000000
--- a/invokeai/frontend/web/src/app/store/nanostores/isDebugging.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { atom } from 'nanostores';
-
-export const $isDebugging = atom(false);
diff --git a/invokeai/frontend/web/src/app/store/nanostores/logo.ts b/invokeai/frontend/web/src/app/store/nanostores/logo.ts
deleted file mode 100644
index 5fd94ebd901..00000000000
--- a/invokeai/frontend/web/src/app/store/nanostores/logo.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { atom } from 'nanostores';
-import type { ReactNode } from 'react';
-
-export const $logo = atom(undefined);
diff --git a/invokeai/frontend/web/src/app/store/nanostores/onClickGoToModelManager.ts b/invokeai/frontend/web/src/app/store/nanostores/onClickGoToModelManager.ts
deleted file mode 100644
index fdc0d8a788b..00000000000
--- a/invokeai/frontend/web/src/app/store/nanostores/onClickGoToModelManager.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { atom } from 'nanostores';
-
-export const $onClickGoToModelManager = atom<(() => void) | undefined>(undefined);
diff --git a/invokeai/frontend/web/src/app/store/nanostores/openAPISchemaUrl.ts b/invokeai/frontend/web/src/app/store/nanostores/openAPISchemaUrl.ts
deleted file mode 100644
index 124815f7ead..00000000000
--- a/invokeai/frontend/web/src/app/store/nanostores/openAPISchemaUrl.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { atom } from 'nanostores';
-
-export const $openAPISchemaUrl = atom(undefined);
diff --git a/invokeai/frontend/web/src/app/store/nanostores/projectId.ts b/invokeai/frontend/web/src/app/store/nanostores/projectId.ts
deleted file mode 100644
index c2b14e91acb..00000000000
--- a/invokeai/frontend/web/src/app/store/nanostores/projectId.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { atom } from 'nanostores';
-
-/**
- * The optional project-id header.
- */
-export const $projectId = atom();
-
-export const $projectName = atom();
-export const $projectUrl = atom();
diff --git a/invokeai/frontend/web/src/app/store/nanostores/queueId.ts b/invokeai/frontend/web/src/app/store/nanostores/queueId.ts
deleted file mode 100644
index 462cf69d0a6..00000000000
--- a/invokeai/frontend/web/src/app/store/nanostores/queueId.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { atom } from 'nanostores';
-
-export const DEFAULT_QUEUE_ID = 'default';
-
-export const $queueId = atom(DEFAULT_QUEUE_ID);
diff --git a/invokeai/frontend/web/src/app/store/nanostores/toastMap.ts b/invokeai/frontend/web/src/app/store/nanostores/toastMap.ts
deleted file mode 100644
index 10f7795a8a0..00000000000
--- a/invokeai/frontend/web/src/app/store/nanostores/toastMap.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import type { ToastConfig } from 'features/toast/toast';
-import { atom } from 'nanostores';
-
-export const $toastMap = atom | undefined>(undefined);
diff --git a/invokeai/frontend/web/src/app/store/nanostores/videoUpsellComponent.ts b/invokeai/frontend/web/src/app/store/nanostores/videoUpsellComponent.ts
deleted file mode 100644
index f36512d7441..00000000000
--- a/invokeai/frontend/web/src/app/store/nanostores/videoUpsellComponent.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { atom } from 'nanostores';
-import type { ReactNode } from 'react';
-
-export const $videoUpsellComponent = atom(undefined);
diff --git a/invokeai/frontend/web/src/app/store/nanostores/whatsNew.ts b/invokeai/frontend/web/src/app/store/nanostores/whatsNew.ts
deleted file mode 100644
index 5e8361412e2..00000000000
--- a/invokeai/frontend/web/src/app/store/nanostores/whatsNew.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { atom } from 'nanostores';
-import type { ReactNode } from 'react';
-
-export const $whatsNew = atom(undefined);
diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts
index 12fcfa5a406..90c6b47b195 100644
--- a/invokeai/frontend/web/src/app/store/store.ts
+++ b/invokeai/frontend/web/src/app/store/store.ts
@@ -34,20 +34,16 @@ import { nodesSliceConfig } from 'features/nodes/store/nodesSlice';
import { workflowLibrarySliceConfig } from 'features/nodes/store/workflowLibrarySlice';
import { workflowSettingsSliceConfig } from 'features/nodes/store/workflowSettingsSlice';
import { upscaleSliceConfig } from 'features/parameters/store/upscaleSlice';
-import { videoSliceConfig } from 'features/parameters/store/videoSlice';
import { queueSliceConfig } from 'features/queue/store/queueSlice';
import { stylePresetSliceConfig } from 'features/stylePresets/store/stylePresetSlice';
-import { configSliceConfig } from 'features/system/store/configSlice';
import { systemSliceConfig } from 'features/system/store/systemSlice';
import { uiSliceConfig } from 'features/ui/store/uiSlice';
import { diff } from 'jsondiffpatch';
-import dynamicMiddlewares from 'redux-dynamic-middlewares';
import type { SerializeFunction, UnserializeFunction } from 'redux-remember';
import { REMEMBER_REHYDRATED, rememberEnhancer, rememberReducer } from 'redux-remember';
import undoable, { newHistory } from 'redux-undo';
import { serializeError } from 'serialize-error';
import { api } from 'services/api';
-import { authToastMiddleware } from 'services/api/authToastMiddleware';
import type { JsonObject } from 'type-fest';
import { reduxRememberDriver } from './enhancers/reduxRemember/driver';
@@ -67,7 +63,6 @@ const SLICE_CONFIGS = {
[canvasSettingsSliceConfig.slice.reducerPath]: canvasSettingsSliceConfig,
[canvasSliceConfig.slice.reducerPath]: canvasSliceConfig,
[changeBoardModalSliceConfig.slice.reducerPath]: changeBoardModalSliceConfig,
- [configSliceConfig.slice.reducerPath]: configSliceConfig,
[dynamicPromptsSliceConfig.slice.reducerPath]: dynamicPromptsSliceConfig,
[gallerySliceConfig.slice.reducerPath]: gallerySliceConfig,
[lorasSliceConfig.slice.reducerPath]: lorasSliceConfig,
@@ -80,7 +75,6 @@ const SLICE_CONFIGS = {
[systemSliceConfig.slice.reducerPath]: systemSliceConfig,
[uiSliceConfig.slice.reducerPath]: uiSliceConfig,
[upscaleSliceConfig.slice.reducerPath]: upscaleSliceConfig,
- [videoSliceConfig.slice.reducerPath]: videoSliceConfig,
[workflowLibrarySliceConfig.slice.reducerPath]: workflowLibrarySliceConfig,
[workflowSettingsSliceConfig.slice.reducerPath]: workflowSettingsSliceConfig,
};
@@ -97,7 +91,6 @@ const ALL_REDUCERS = {
canvasSliceConfig.undoableConfig?.reduxUndoOptions
),
[changeBoardModalSliceConfig.slice.reducerPath]: changeBoardModalSliceConfig.slice.reducer,
- [configSliceConfig.slice.reducerPath]: configSliceConfig.slice.reducer,
[dynamicPromptsSliceConfig.slice.reducerPath]: dynamicPromptsSliceConfig.slice.reducer,
[gallerySliceConfig.slice.reducerPath]: gallerySliceConfig.slice.reducer,
[lorasSliceConfig.slice.reducerPath]: lorasSliceConfig.slice.reducer,
@@ -114,7 +107,6 @@ const ALL_REDUCERS = {
[systemSliceConfig.slice.reducerPath]: systemSliceConfig.slice.reducer,
[uiSliceConfig.slice.reducerPath]: uiSliceConfig.slice.reducer,
[upscaleSliceConfig.slice.reducerPath]: upscaleSliceConfig.slice.reducer,
- [videoSliceConfig.slice.reducerPath]: videoSliceConfig.slice.reducer,
[workflowLibrarySliceConfig.slice.reducerPath]: workflowLibrarySliceConfig.slice.reducer,
[workflowSettingsSliceConfig.slice.reducerPath]: workflowSettingsSliceConfig.slice.reducer,
};
@@ -197,8 +189,6 @@ export const createStore = (options?: { persist?: boolean; persistDebounce?: num
immutableCheck: import.meta.env.MODE === 'development',
})
.concat(api.middleware)
- .concat(dynamicMiddlewares)
- .concat(authToastMiddleware)
// .concat(getDebugLoggerMiddleware({ withDiff: true, withNextState: true }))
.prepend(listenerMiddleware.middleware),
enhancers: (getDefaultEnhancers) => {
diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts
index b24f83a1b15..664ac002e95 100644
--- a/invokeai/frontend/web/src/app/types/invokeai.ts
+++ b/invokeai/frontend/web/src/app/types/invokeai.ts
@@ -60,6 +60,16 @@ const zNumericalParameterConfig = z.object({
});
export type NumericalParameterConfig = z.infer;
+const CONSTRAINTS = {
+ initial: 512,
+ sliderMin: 64,
+ sliderMax: 1536,
+ numberInputMin: 64,
+ numberInputMax: 4096,
+ fineStep: 8,
+ coarseStep: 64,
+};
+
/**
* Configuration options for the InvokeAI UI.
* Distinct from system settings which may be changed inside the app.
@@ -143,7 +153,7 @@ export const getDefaultAppConfig = (): AppConfig => ({
allowPromptExpansion: false,
allowVideo: false, // used to determine if video is enabled vs upsell
shouldShowCredits: false,
- disabledTabs: ['video'], // used to determine if video functionality is visible
+ disabledTabs: [], // used to determine if video functionality is visible
disabledFeatures: ['lightbox', 'faceRestore', 'batches'] satisfies AppFeature[],
disabledSDFeatures: ['variation', 'symmetry', 'hires', 'perlinNoise', 'noiseThreshold'] satisfies SDFeature[],
sd: {
diff --git a/invokeai/frontend/web/src/common/hooks/useClientSideUpload.ts b/invokeai/frontend/web/src/common/hooks/useClientSideUpload.ts
deleted file mode 100644
index f5cc5a7f3f2..00000000000
--- a/invokeai/frontend/web/src/common/hooks/useClientSideUpload.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import { useStore } from '@nanostores/react';
-import { $authToken } from 'app/store/nanostores/authToken';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { imageUploadedClientSide } from 'features/gallery/store/actions';
-import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
-import { useCallback } from 'react';
-import { useCreateImageUploadEntryMutation } from 'services/api/endpoints/images';
-import type { ImageDTO } from 'services/api/types';
-
-type PresignedUrlResponse = {
- fullUrl: string;
- thumbnailUrl: string;
-};
-
-const isPresignedUrlResponse = (response: unknown): response is PresignedUrlResponse => {
- return typeof response === 'object' && response !== null && 'fullUrl' in response && 'thumbnailUrl' in response;
-};
-
-export const useClientSideUpload = () => {
- const dispatch = useAppDispatch();
- const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
- const authToken = useStore($authToken);
- const [createImageUploadEntry] = useCreateImageUploadEntryMutation();
-
- const clientSideUpload = useCallback(
- async (file: File, i: number): Promise => {
- const image = new Image();
- const objectURL = URL.createObjectURL(file);
- image.src = objectURL;
- let width = 0;
- let height = 0;
- let thumbnail: Blob | undefined;
-
- await new Promise((resolve) => {
- image.onload = () => {
- width = image.naturalWidth;
- height = image.naturalHeight;
-
- // Calculate thumbnail dimensions maintaining aspect ratio
- let thumbWidth = width;
- let thumbHeight = height;
- if (width > height && width > 256) {
- thumbWidth = 256;
- thumbHeight = Math.round((height * 256) / width);
- } else if (height > 256) {
- thumbHeight = 256;
- thumbWidth = Math.round((width * 256) / height);
- }
-
- const canvas = document.createElement('canvas');
- canvas.width = thumbWidth;
- canvas.height = thumbHeight;
- const ctx = canvas.getContext('2d');
- ctx?.drawImage(image, 0, 0, thumbWidth, thumbHeight);
-
- canvas.toBlob(
- (blob) => {
- if (blob) {
- thumbnail = blob;
- // Clean up resources
- URL.revokeObjectURL(objectURL);
- image.src = ''; // Clear image source
- image.remove(); // Remove the image element
- canvas.width = 0; // Clear canvas
- canvas.height = 0;
- resolve();
- }
- },
- 'image/webp',
- 0.8
- );
- };
-
- // Handle load errors
- image.onerror = () => {
- URL.revokeObjectURL(objectURL);
- image.remove();
- resolve();
- };
- });
- const { presigned_url, image_dto } = await createImageUploadEntry({
- width,
- height,
- board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
- }).unwrap();
-
- const response = await fetch(presigned_url, {
- method: 'GET',
- ...(authToken && {
- headers: {
- Authorization: `Bearer ${authToken}`,
- },
- }),
- }).then((res) => res.json());
-
- if (!isPresignedUrlResponse(response)) {
- throw new Error('Invalid response');
- }
-
- const fullUrl = response.fullUrl;
- const thumbnailUrl = response.thumbnailUrl;
-
- await fetch(fullUrl, {
- method: 'PUT',
- body: file,
- });
-
- await fetch(thumbnailUrl, {
- method: 'PUT',
- body: thumbnail,
- });
-
- dispatch(imageUploadedClientSide({ imageDTO: image_dto, silent: false, isFirstUploadOfBatch: i === 0 }));
-
- return image_dto;
- },
- [autoAddBoardId, authToken, createImageUploadEntry, dispatch]
- );
-
- return clientSideUpload;
-};
diff --git a/invokeai/frontend/web/src/common/hooks/useCopyImageToClipboard.ts b/invokeai/frontend/web/src/common/hooks/useCopyImageToClipboard.ts
index d90ec3f2ed1..e46227b2f5d 100644
--- a/invokeai/frontend/web/src/common/hooks/useCopyImageToClipboard.ts
+++ b/invokeai/frontend/web/src/common/hooks/useCopyImageToClipboard.ts
@@ -1,7 +1,5 @@
-import { useAppDispatch } from 'app/store/storeHooks';
import { useClipboard } from 'common/hooks/useClipboard';
import { convertImageUrlToBlob } from 'common/util/convertImageUrlToBlob';
-import { imageCopiedToClipboard } from 'features/gallery/store/actions';
import { toast } from 'features/toast/toast';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@@ -9,7 +7,6 @@ import { useTranslation } from 'react-i18next';
export const useCopyImageToClipboard = () => {
const { t } = useTranslation();
const clipboard = useClipboard();
- const dispatch = useAppDispatch();
const copyImageToClipboard = useCallback(
async (image_url: string) => {
@@ -26,7 +23,6 @@ export const useCopyImageToClipboard = () => {
title: t('toast.imageCopied'),
status: 'success',
});
- dispatch(imageCopiedToClipboard());
});
} catch (err) {
toast({
@@ -37,7 +33,7 @@ export const useCopyImageToClipboard = () => {
});
}
},
- [clipboard, t, dispatch]
+ [clipboard, t]
);
return copyImageToClipboard;
diff --git a/invokeai/frontend/web/src/common/hooks/useDownloadImage.ts b/invokeai/frontend/web/src/common/hooks/useDownloadImage.ts
index 1309afdbf56..33b90e1d7fe 100644
--- a/invokeai/frontend/web/src/common/hooks/useDownloadImage.ts
+++ b/invokeai/frontend/web/src/common/hooks/useDownloadImage.ts
@@ -1,27 +1,14 @@
-import { useStore } from '@nanostores/react';
-import { $authToken } from 'app/store/nanostores/authToken';
-import { useAppDispatch } from 'app/store/storeHooks';
-import { imageDownloaded } from 'features/gallery/store/actions';
import { toast } from 'features/toast/toast';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
export const useDownloadItem = () => {
const { t } = useTranslation();
- const dispatch = useAppDispatch();
- const authToken = useStore($authToken);
const downloadItem = useCallback(
async (item_url: string, item_id: string) => {
try {
- const requestOpts = authToken
- ? {
- headers: {
- Authorization: `Bearer ${authToken}`,
- },
- }
- : {};
- const blob = await fetch(item_url, requestOpts).then((resp) => resp.blob());
+ const blob = await fetch(item_url).then((resp) => resp.blob());
if (!blob) {
throw new Error('Unable to create Blob');
}
@@ -34,7 +21,6 @@ export const useDownloadItem = () => {
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
- dispatch(imageDownloaded());
} catch (err) {
toast({
id: 'PROBLEM_DOWNLOADING_IMAGE',
@@ -44,7 +30,7 @@ export const useDownloadItem = () => {
});
}
},
- [t, dispatch, authToken]
+ [t]
);
return { downloadItem };
diff --git a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts
index a3ccdc01f2a..69b0b2b0fc2 100644
--- a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts
+++ b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts
@@ -1,20 +1,16 @@
import { useAppStore } from 'app/store/storeHooks';
import { useDeleteImageModalApi } from 'features/deleteImageModal/store/state';
-import { useDeleteVideoModalApi } from 'features/deleteVideoModal/store/state';
import { selectSelection } from 'features/gallery/store/gallerySelectors';
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
import { useDeleteCurrentQueueItem } from 'features/queue/hooks/useDeleteCurrentQueueItem';
import { useInvoke } from 'features/queue/hooks/useInvoke';
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { navigationApi } from 'features/ui/layouts/navigation-api';
import { getFocusedRegion } from './focus';
export const useGlobalHotkeys = () => {
const { dispatch, getState } = useAppStore();
- const isVideoEnabled = useFeatureStatus('video');
- const isModelManagerEnabled = useFeatureStatus('modelManager');
const queue = useInvoke();
useRegisteredHotkeys({
@@ -94,18 +90,6 @@ export const useGlobalHotkeys = () => {
dependencies: [dispatch],
});
- useRegisteredHotkeys({
- id: 'selectVideoTab',
- category: 'app',
- callback: () => {
- navigationApi.switchToTab('video');
- },
- options: {
- enabled: isVideoEnabled,
- },
- dependencies: [dispatch],
- });
-
useRegisteredHotkeys({
id: 'selectWorkflowsTab',
category: 'app',
@@ -121,10 +105,7 @@ export const useGlobalHotkeys = () => {
callback: () => {
navigationApi.switchToTab('models');
},
- options: {
- enabled: isModelManagerEnabled,
- },
- dependencies: [dispatch, isModelManagerEnabled],
+ dependencies: [dispatch],
});
useRegisteredHotkeys({
@@ -133,11 +114,10 @@ export const useGlobalHotkeys = () => {
callback: () => {
navigationApi.switchToTab('queue');
},
- dependencies: [dispatch, isModelManagerEnabled],
+ dependencies: [dispatch],
});
const deleteImageModalApi = useDeleteImageModalApi();
- const deleteVideoModalApi = useDeleteVideoModalApi();
useRegisteredHotkeys({
id: 'deleteSelection',
@@ -153,8 +133,6 @@ export const useGlobalHotkeys = () => {
}
if (selection.every(({ type }) => type === 'image')) {
deleteImageModalApi.delete(selection.map((s) => s.id));
- } else if (selection.every(({ type }) => type === 'video')) {
- deleteVideoModalApi.delete(selection.map((s) => s.id));
} else {
// no-op, we expect selections to always be only images or only video
}
diff --git a/invokeai/frontend/web/src/common/hooks/useImageUploadButton.tsx b/invokeai/frontend/web/src/common/hooks/useImageUploadButton.tsx
index 9a06dab6398..445f58c9a66 100644
--- a/invokeai/frontend/web/src/common/hooks/useImageUploadButton.tsx
+++ b/invokeai/frontend/web/src/common/hooks/useImageUploadButton.tsx
@@ -3,7 +3,6 @@ import { Button, IconButton } from '@invoke-ai/ui-library';
import { logger } from 'app/logging/logger';
import { useAppSelector } from 'app/store/storeHooks';
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
-import { selectIsClientSideUploadEnabled } from 'features/system/store/configSlice';
import { toast } from 'features/toast/toast';
import { memo, useCallback } from 'react';
import type { Accept, FileRejection } from 'react-dropzone';
@@ -27,7 +26,6 @@ export const dropzoneAccept: Accept = {
'image/webp': ['.webp'].reduce(addUpperCaseReducer, [] as string[]),
};
-import { useClientSideUpload } from './useClientSideUpload';
type UseImageUploadButtonArgs =
| {
isDisabled?: boolean;
@@ -73,9 +71,7 @@ export const useImageUploadButton = ({
onError,
}: UseImageUploadButtonArgs) => {
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
- const isClientSideUploadEnabled = useAppSelector(selectIsClientSideUploadEnabled);
const [uploadImage, request] = useUploadImageMutation();
- const clientSideUpload = useClientSideUpload();
const { t } = useTranslation();
const onDropAccepted = useCallback(
@@ -108,20 +104,16 @@ export const useImageUploadButton = ({
onUploadStarted?.(files);
let imageDTOs: ImageDTO[] = [];
- if (isClientSideUploadEnabled && files.length > 1) {
- imageDTOs = await Promise.all(files.map((file, i) => clientSideUpload(file, i)));
- } else {
- imageDTOs = await uploadImages(
- files.map((file, i) => ({
- file,
- image_category: 'user',
- is_intermediate: false,
- board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
- silent: false,
- isFirstUploadOfBatch: i === 0,
- }))
- );
- }
+ imageDTOs = await uploadImages(
+ files.map((file, i) => ({
+ file,
+ image_category: 'user',
+ is_intermediate: false,
+ board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
+ silent: false,
+ isFirstUploadOfBatch: i === 0,
+ }))
+ );
if (onUpload) {
onUpload(imageDTOs);
}
@@ -135,17 +127,7 @@ export const useImageUploadButton = ({
});
}
},
- [
- allowMultiple,
- onUploadStarted,
- uploadImage,
- autoAddBoardId,
- onUpload,
- isClientSideUploadEnabled,
- clientSideUpload,
- onError,
- t,
- ]
+ [allowMultiple, onUploadStarted, uploadImage, autoAddBoardId, onUpload, onError, t]
);
const onDropRejected = useCallback(
diff --git a/invokeai/frontend/web/src/common/util/convertImageUrlToBlob.ts b/invokeai/frontend/web/src/common/util/convertImageUrlToBlob.ts
index 5d1ff434bc9..69816bd0284 100644
--- a/invokeai/frontend/web/src/common/util/convertImageUrlToBlob.ts
+++ b/invokeai/frontend/web/src/common/util/convertImageUrlToBlob.ts
@@ -1,5 +1,3 @@
-import { $authToken } from 'app/store/nanostores/authToken';
-
/**
* Converts an image URL to a Blob by creating an
element, drawing it to canvas
* and then converting the canvas to a Blob.
@@ -40,6 +38,6 @@ export const convertImageUrlToBlob = (url: string) =>
reject(new Error('Image failed to load. The URL may be invalid or the object may not exist.'));
};
- img.crossOrigin = $authToken.get() ? 'use-credentials' : 'anonymous';
+ img.crossOrigin = 'anonymous';
img.src = url;
});
diff --git a/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx b/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx
index 3413a38e520..e8885097d17 100644
--- a/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx
+++ b/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx
@@ -13,7 +13,6 @@ import { memo, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
import { useAddImagesToBoardMutation, useRemoveImagesFromBoardMutation } from 'services/api/endpoints/images';
-import { useAddVideosToBoardMutation, useRemoveVideosFromBoardMutation } from 'services/api/endpoints/videos';
const selectImagesToChange = createSelector(
selectChangeBoardModalSlice,
@@ -41,8 +40,6 @@ const ChangeBoardModal = () => {
const videosToChange = useAppSelector(selectVideosToChange);
const [addImagesToBoard] = useAddImagesToBoardMutation();
const [removeImagesFromBoard] = useRemoveImagesFromBoardMutation();
- const [addVideosToBoard] = useAddVideosToBoardMutation();
- const [removeVideosFromBoard] = useRemoveVideosFromBoardMutation();
const { t } = useTranslation();
const options = useMemo(() => {
@@ -80,27 +77,8 @@ const ChangeBoardModal = () => {
});
}
}
- if (videosToChange.length) {
- if (selectedBoardId === 'none') {
- removeVideosFromBoard({ video_ids: videosToChange });
- } else {
- addVideosToBoard({
- video_ids: videosToChange,
- board_id: selectedBoardId,
- });
- }
- }
dispatch(changeBoardReset());
- }, [
- addImagesToBoard,
- dispatch,
- imagesToChange,
- videosToChange,
- removeImagesFromBoard,
- selectedBoardId,
- addVideosToBoard,
- removeVideosFromBoard,
- ]);
+ }, [addImagesToBoard, dispatch, imagesToChange, videosToChange, removeImagesFromBoard, selectedBoardId]);
const onChange = useCallback((v) => {
if (!v) {
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasAlerts/CanvasAlertsInvocationProgress.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasAlerts/CanvasAlertsInvocationProgress.tsx
index 4e9dd5ec512..eb2a043864b 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasAlerts/CanvasAlertsInvocationProgress.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasAlerts/CanvasAlertsInvocationProgress.tsx
@@ -2,7 +2,6 @@ import { Alert, AlertDescription, AlertIcon, AlertTitle } from '@invoke-ai/ui-li
import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks';
import { useDeferredModelLoadingInvocationProgressMessage } from 'features/controlLayers/hooks/useDeferredModelLoadingInvocationProgressMessage';
-import { selectIsLocal } from 'features/system/store/configSlice';
import { selectSystemShouldShowInvocationProgressDetail } from 'features/system/store/systemSlice';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -44,13 +43,8 @@ CanvasAlertsInvocationProgressContentCommercial.displayName = 'CanvasAlertsInvoc
export const CanvasAlertsInvocationProgress = memo(() => {
const shouldShowInvocationProgressDetail = useAppSelector(selectSystemShouldShowInvocationProgressDetail);
- const isLocal = useAppSelector(selectIsLocal);
- if (!isLocal) {
- return ;
- }
-
- // OSS user setting
+ // user setting
if (!shouldShowInvocationProgressDetail) {
return null;
}
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Filters/FilterTypeSelect.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Filters/FilterTypeSelect.tsx
index a21c303d13e..2b3ff537026 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/Filters/FilterTypeSelect.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/Filters/FilterTypeSelect.tsx
@@ -1,18 +1,13 @@
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
-import { createSelector } from '@reduxjs/toolkit';
-import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
-import { includes, map } from 'es-toolkit/compat';
+import { map } from 'es-toolkit/compat';
import type { FilterConfig } from 'features/controlLayers/store/filters';
import { IMAGE_FILTERS, isFilterType } from 'features/controlLayers/store/filters';
-import { selectConfigSlice } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { assert } from 'tsafe';
-const selectDisabledProcessors = createSelector(selectConfigSlice, (config) => config.sd.disabledControlNetProcessors);
-
type Props = {
filterType: FilterConfig['type'];
onChange: (filterType: FilterConfig['type']) => void;
@@ -20,12 +15,9 @@ type Props = {
export const FilterTypeSelect = memo(({ filterType, onChange }: Props) => {
const { t } = useTranslation();
- const disabledProcessors = useAppSelector(selectDisabledProcessors);
const options = useMemo(() => {
- return map(IMAGE_FILTERS, (data, type) => ({ value: type, label: t(`controlLayers.filter.${type}.label`) })).filter(
- (o) => !includes(disabledProcessors, o.value)
- );
- }, [disabledProcessors, t]);
+ return map(IMAGE_FILTERS, (data, type) => ({ value: type, label: t(`controlLayers.filter.${type}.label`) }));
+ }, [t]);
const _onChange = useCallback(
(v) => {
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ParamDenoisingStrength.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ParamDenoisingStrength.tsx
index 49a289b875c..34fb96f063d 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/ParamDenoisingStrength.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/ParamDenoisingStrength.tsx
@@ -13,12 +13,21 @@ import { InformationalPopover } from 'common/components/InformationalPopover/Inf
import WavyLine from 'common/components/WavyLine';
import { selectImg2imgStrength, setImg2imgStrength } from 'features/controlLayers/store/paramsSlice';
import { selectActiveRasterLayerEntities } from 'features/controlLayers/store/selectors';
-import { selectImg2imgStrengthConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelectedModelConfig } from 'services/api/hooks/useSelectedModelConfig';
import { isFluxFillMainModelModelConfig } from 'services/api/types';
+const CONSTRAINTS = {
+ initial: 0.7,
+ sliderMin: 0,
+ sliderMax: 1,
+ numberInputMin: 0,
+ numberInputMax: 1,
+ fineStep: 0.01,
+ coarseStep: 0.05,
+};
+
const selectHasRasterLayersWithContent = createSelector(
selectActiveRasterLayerEntities,
(entities) => entities.length > 0
@@ -37,7 +46,6 @@ export const ParamDenoisingStrength = memo(() => {
[dispatch]
);
- const config = useAppSelector(selectImg2imgStrengthConfig);
const { t } = useTranslation();
const [invokeBlue300] = useToken('colors', ['invokeBlue.300']);
@@ -67,20 +75,20 @@ export const ParamDenoisingStrength = memo(() => {
{!isDisabled ? (
<>
void;
@@ -14,11 +21,8 @@ type Props = {
const formatValue = (v: number) => v.toFixed(2);
const marks = [0, 1, 2];
-const selectWeightConfig = createSelector(selectConfigSlice, (config) => config.sd.ca.weight);
-
export const Weight = memo(({ weight, onChange }: Props) => {
const { t } = useTranslation();
- const config = useAppSelector(selectWeightConfig);
return (
@@ -28,23 +32,23 @@ export const Weight = memo(({ weight, onChange }: Props) => {
);
diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts
index 062937edcd0..8f0a1a11a6f 100644
--- a/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts
@@ -34,10 +34,8 @@ import type {
T2IAdapterConfig,
} from 'features/controlLayers/store/types';
import {
- initialChatGPT4oReferenceImage,
initialControlNet,
initialFluxKontextReferenceImage,
- initialGemini2_5ReferenceImage,
initialIPAdapter,
initialRegionalGuidanceIPAdapter,
initialT2IAdapter,
@@ -92,25 +90,12 @@ export const getDefaultRefImageConfig = (
const base = mainModelConfig?.base;
- // For ChatGPT-4o, the ref image model is the model itself.
- if (base === 'chatgpt-4o') {
- const config = deepClone(initialChatGPT4oReferenceImage);
- config.model = zModelIdentifierField.parse(mainModelConfig);
- return config;
- }
-
- if (base === 'flux-kontext' || (base === 'flux' && mainModelConfig?.name?.toLowerCase().includes('kontext'))) {
+ if (base === 'flux' && mainModelConfig?.name?.toLowerCase().includes('kontext')) {
const config = deepClone(initialFluxKontextReferenceImage);
config.model = zModelIdentifierField.parse(mainModelConfig);
return config;
}
- if (base === 'gemini-2.5') {
- const config = deepClone(initialGemini2_5ReferenceImage);
- config.model = zModelIdentifierField.parse(mainModelConfig);
- return config;
- }
-
// Otherwise, find the first compatible IP Adapter model.
const modelConfig = ipAdapterModelConfigs.find((m) => m.base === base);
diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/useIsEntityTypeEnabled.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/useIsEntityTypeEnabled.ts
index d31f18ad6c2..b852d119149 100644
--- a/invokeai/frontend/web/src/features/controlLayers/hooks/useIsEntityTypeEnabled.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/hooks/useIsEntityTypeEnabled.ts
@@ -1,13 +1,5 @@
import { useAppSelector } from 'app/store/storeHooks';
-import {
- selectIsChatGPT4o,
- selectIsCogView4,
- selectIsFluxKontext,
- selectIsGemini2_5,
- selectIsImagen3,
- selectIsImagen4,
- selectIsSD3,
-} from 'features/controlLayers/store/paramsSlice';
+import { selectIsCogView4, selectIsFluxKontext, selectIsSD3 } from 'features/controlLayers/store/paramsSlice';
import type { CanvasEntityType } from 'features/controlLayers/store/types';
import { useMemo } from 'react';
import type { Equals } from 'tsafe';
@@ -16,26 +8,24 @@ import { assert } from 'tsafe';
export const useIsEntityTypeEnabled = (entityType: CanvasEntityType) => {
const isSD3 = useAppSelector(selectIsSD3);
const isCogView4 = useAppSelector(selectIsCogView4);
- const isImagen3 = useAppSelector(selectIsImagen3);
- const isImagen4 = useAppSelector(selectIsImagen4);
const isFluxKontext = useAppSelector(selectIsFluxKontext);
- const isChatGPT4o = useAppSelector(selectIsChatGPT4o);
- const isGemini2_5 = useAppSelector(selectIsGemini2_5);
+ // TODO(psyche): consider using a constant to define which entity types are supported by which model,
+ // see invokeai/frontend/web/src/features/modelManagerV2/models.ts for ref
const isEntityTypeEnabled = useMemo(() => {
switch (entityType) {
case 'regional_guidance':
- return !isSD3 && !isCogView4 && !isImagen3 && !isImagen4 && !isFluxKontext && !isChatGPT4o && !isGemini2_5;
+ return !isSD3 && !isCogView4 && !isFluxKontext;
case 'control_layer':
- return !isSD3 && !isCogView4 && !isImagen3 && !isImagen4 && !isFluxKontext && !isChatGPT4o && !isGemini2_5;
+ return !isSD3 && !isCogView4 && !isFluxKontext;
case 'inpaint_mask':
- return !isImagen3 && !isImagen4 && !isFluxKontext && !isChatGPT4o && !isGemini2_5;
+ return !isFluxKontext;
case 'raster_layer':
- return !isImagen3 && !isImagen4 && !isFluxKontext && !isChatGPT4o && !isGemini2_5;
+ return !isFluxKontext;
default:
assert>(false);
}
- }, [entityType, isSD3, isCogView4, isImagen3, isImagen4, isFluxKontext, isChatGPT4o, isGemini2_5]);
+ }, [entityType, isSD3, isCogView4, isFluxKontext]);
return isEntityTypeEnabled;
};
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBackgroundModule.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBackgroundModule.ts
index f57c7038e14..b700392c05f 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBackgroundModule.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBackgroundModule.ts
@@ -1,5 +1,4 @@
import { getArbitraryBaseColor } from '@invoke-ai/ui-library';
-import { $authToken } from 'app/store/nanostores/authToken';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import { TRANSPARENCY_CHECKERBOARD_PATTERN_DARK_DATAURL } from 'features/controlLayers/konva/patterns/transparency-checkerboard-pattern';
@@ -95,7 +94,7 @@ export class CanvasBackgroundModule extends CanvasModuleBase {
this.konva.patternRect.fillPatternImage(this.checkboardPattern);
this.render();
};
- this.checkboardPattern.src = $authToken.get() ? 'use-credentials' : 'anonymous';
+ this.checkboardPattern.src = 'anonymous';
this.checkboardPattern.src = this.config.CHECKERBOARD_PATTERN_DATAURL;
this.render();
};
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityObjectRenderer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityObjectRenderer.ts
index f2d7140d68a..8bb8ec25319 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityObjectRenderer.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityObjectRenderer.ts
@@ -1,4 +1,3 @@
-import { $authToken } from 'app/store/nanostores/authToken';
import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { SyncableMap } from 'common/util/SyncableMap/SyncableMap';
import { throttle } from 'es-toolkit/compat';
@@ -38,7 +37,7 @@ function setFillPatternImage(shape: Konva.Shape, ...args: Parameters {
shape.fillPatternImage(imageElement);
};
- imageElement.crossOrigin = $authToken.get() ? 'use-credentials' : 'anonymous';
+ imageElement.crossOrigin = 'anonymous';
imageElement.src = getPatternSVG(...args);
return imageElement;
}
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObject/CanvasObjectImage.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObject/CanvasObjectImage.ts
index c684739ede9..0aab41ee7e3 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObject/CanvasObjectImage.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObject/CanvasObjectImage.ts
@@ -126,7 +126,7 @@ export class CanvasObjectImage extends CanvasModuleBase {
return;
}
- const imageElementResult = await withResultAsync(() => loadImage(imageDTO.image_url, true));
+ const imageElementResult = await withResultAsync(() => loadImage(imageDTO.image_url));
if (imageElementResult.isErr()) {
// Image loading failed (e.g. the URL to the "physical" image is invalid)
this.onFailedToLoadImage(
@@ -152,7 +152,7 @@ export class CanvasObjectImage extends CanvasModuleBase {
this.konva.placeholder.text.text(t('common.loadingImage', 'Loading Image'));
}
- const imageElementResult = await withResultAsync(() => loadImage(dataURL, false));
+ const imageElementResult = await withResultAsync(() => loadImage(dataURL));
if (imageElementResult.isErr()) {
// Image loading failed (e.g. the URL to the "physical" image is invalid)
this.onFailedToLoadImage(
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasBboxToolModule.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasBboxToolModule.ts
index 088e7f265a4..ecf9a5d1c7c 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasBboxToolModule.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasBboxToolModule.ts
@@ -13,8 +13,6 @@ import { selectBboxOverlay } from 'features/controlLayers/store/canvasSettingsSl
import { selectModel } from 'features/controlLayers/store/paramsSlice';
import { selectBbox } from 'features/controlLayers/store/selectors';
import type { Coordinate, Rect, Tool } from 'features/controlLayers/store/types';
-import { API_BASE_MODELS } from 'features/modelManagerV2/models';
-import type { ModelIdentifierField } from 'features/nodes/types/common';
import Konva from 'konva';
import { atom } from 'nanostores';
import type { Logger } from 'roarr';
@@ -238,22 +236,16 @@ export class CanvasBboxToolModule extends CanvasModuleBase {
this.syncOverlay();
- const model = this.manager.stateApi.runSelector(selectModel);
-
this.konva.transformer.setAttrs({
listening: tool === 'bbox',
- enabledAnchors: this.getEnabledAnchors(tool, model),
+ enabledAnchors: this.getEnabledAnchors(tool),
});
};
- getEnabledAnchors = (tool: Tool, model?: ModelIdentifierField | null): string[] => {
+ getEnabledAnchors = (tool: Tool): string[] => {
if (tool !== 'bbox') {
return NO_ANCHORS;
}
- if (model?.base && API_BASE_MODELS.includes(model.base)) {
- // The bbox is not resizable in these modes
- return NO_ANCHORS;
- }
return ALL_ANCHORS;
};
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/util.ts b/invokeai/frontend/web/src/features/controlLayers/konva/util.ts
index 8e34f2169c5..6189b3eef78 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/util.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/util.ts
@@ -1,5 +1,4 @@
import type { Selector, Store } from '@reduxjs/toolkit';
-import { $authToken, $crossOrigin } from 'app/store/nanostores/authToken';
import { roundDownToMultiple, roundUpToMultiple } from 'common/util/roundDownToMultiple';
import { clamp } from 'es-toolkit/compat';
import type {
@@ -364,7 +363,7 @@ export const dataURLToImageData = (dataURL: string, width: number, height: numbe
reject(e);
};
- image.crossOrigin = $authToken.get() ? 'use-credentials' : 'anonymous';
+ image.crossOrigin = 'anonymous';
image.src = dataURL;
});
};
@@ -478,23 +477,14 @@ export function getImageDataTransparency(imageData: ImageData): Transparency {
/**
* Loads an image from a URL and returns a promise that resolves with the loaded image element.
* @param src The image source URL
- * @param fetchUrlFirst Whether to fetch the image's URL first, assuming the provided `src` will redirect to a different URL. This addresses an issue where CORS headers are dropped during a redirect.
* @returns A promise that resolves with the loaded image element
*/
-export async function loadImage(src: string, fetchUrlFirst?: boolean): Promise {
- const authToken = $authToken.get();
- let url = src;
- if (authToken && fetchUrlFirst) {
- const response = await fetch(`${src}?url_only=true`, { credentials: 'include' });
- const data = await response.json();
- url = data.url;
- }
-
+export function loadImage(url: string): Promise {
return new Promise((resolve, reject) => {
const imageElement = new Image();
imageElement.onload = () => resolve(imageElement);
imageElement.onerror = (error) => reject(error);
- imageElement.crossOrigin = $crossOrigin.get();
+ imageElement.crossOrigin = 'anonymous';
imageElement.src = url;
});
}
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasSlice.ts
index b8664aeb5ed..f7eef4a6454 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/canvasSlice.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasSlice.ts
@@ -35,7 +35,6 @@ import {
getScaledBoundingBoxDimensions,
} from 'features/controlLayers/util/getScaledBoundingBoxDimensions';
import { simplifyFlatNumbersArray } from 'features/controlLayers/util/simplify';
-import { API_BASE_MODELS } from 'features/modelManagerV2/models';
import { isMainModelBase, zModelIdentifierField } from 'features/nodes/types/common';
import { getGridSize, getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
import type { IRect } from 'konva/lib/types';
@@ -73,17 +72,9 @@ import type {
} from './types';
import {
ASPECT_RATIO_MAP,
- CHATGPT_ASPECT_RATIOS,
DEFAULT_ASPECT_RATIO_CONFIG,
- FLUX_KONTEXT_ASPECT_RATIOS,
- GEMINI_2_5_ASPECT_RATIOS,
getEntityIdentifier,
getInitialCanvasState,
- IMAGEN_ASPECT_RATIOS,
- isChatGPT4oAspectRatioID,
- isFluxKontextAspectRatioID,
- isGemini2_5AspectRatioID,
- isImagenAspectRatioID,
isRegionalGuidanceFLUXReduxConfig,
isRegionalGuidanceIPAdapterConfig,
zCanvasState,
@@ -1227,33 +1218,6 @@ const slice = createSlice({
state.bbox.aspectRatio.id = id;
if (id === 'Free') {
state.bbox.aspectRatio.isLocked = false;
- } else if (
- (state.bbox.modelBase === 'imagen3' || state.bbox.modelBase === 'imagen4') &&
- isImagenAspectRatioID(id)
- ) {
- const { width, height } = IMAGEN_ASPECT_RATIOS[id];
- state.bbox.rect.width = width;
- state.bbox.rect.height = height;
- state.bbox.aspectRatio.value = state.bbox.rect.width / state.bbox.rect.height;
- state.bbox.aspectRatio.isLocked = true;
- } else if (state.bbox.modelBase === 'chatgpt-4o' && isChatGPT4oAspectRatioID(id)) {
- const { width, height } = CHATGPT_ASPECT_RATIOS[id];
- state.bbox.rect.width = width;
- state.bbox.rect.height = height;
- state.bbox.aspectRatio.value = state.bbox.rect.width / state.bbox.rect.height;
- state.bbox.aspectRatio.isLocked = true;
- } else if (state.bbox.modelBase === 'gemini-2.5' && isGemini2_5AspectRatioID(id)) {
- const { width, height } = GEMINI_2_5_ASPECT_RATIOS[id];
- state.bbox.rect.width = width;
- state.bbox.rect.height = height;
- state.bbox.aspectRatio.value = state.bbox.rect.width / state.bbox.rect.height;
- state.bbox.aspectRatio.isLocked = true;
- } else if (state.bbox.modelBase === 'flux-kontext' && isFluxKontextAspectRatioID(id)) {
- const { width, height } = FLUX_KONTEXT_ASPECT_RATIOS[id];
- state.bbox.rect.width = width;
- state.bbox.rect.height = height;
- state.bbox.aspectRatio.value = state.bbox.rect.width / state.bbox.rect.height;
- state.bbox.aspectRatio.isLocked = true;
} else {
state.bbox.aspectRatio.isLocked = true;
state.bbox.aspectRatio.value = ASPECT_RATIO_MAP[id].ratio;
@@ -1700,14 +1664,6 @@ const slice = createSlice({
const base = model?.base;
if (isMainModelBase(base) && state.bbox.modelBase !== base) {
state.bbox.modelBase = base;
- if (API_BASE_MODELS.includes(base)) {
- state.bbox.aspectRatio.isLocked = true;
- state.bbox.aspectRatio.value = 1;
- state.bbox.aspectRatio.id = '1:1';
- state.bbox.rect.width = 1024;
- state.bbox.rect.height = 1024;
- }
-
syncScaledSize(state);
}
});
@@ -1832,10 +1788,6 @@ export const {
} = slice.actions;
const syncScaledSize = (state: CanvasState) => {
- if (API_BASE_MODELS.includes(state.bbox.modelBase)) {
- // Imagen3 has fixed sizes. Scaled bbox is not supported.
- return;
- }
if (state.bbox.scaleMethod === 'auto') {
// Sync both aspect ratio and size
const { width, height } = state.bbox.rect;
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts
index 609478b4c0c..90eb53124a1 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts
@@ -9,28 +9,16 @@ import { clamp } from 'es-toolkit/compat';
import type { AspectRatioID, ParamsState, RgbaColor } from 'features/controlLayers/store/types';
import {
ASPECT_RATIO_MAP,
- CHATGPT_ASPECT_RATIOS,
DEFAULT_ASPECT_RATIO_CONFIG,
- FLUX_KONTEXT_ASPECT_RATIOS,
- GEMINI_2_5_ASPECT_RATIOS,
getInitialParamsState,
- IMAGEN_ASPECT_RATIOS,
- isChatGPT4oAspectRatioID,
- isFluxKontextAspectRatioID,
- isGemini2_5AspectRatioID,
- isImagenAspectRatioID,
MAX_POSITIVE_PROMPT_HISTORY,
zParamsState,
} from 'features/controlLayers/store/types';
import { calculateNewSize } from 'features/controlLayers/util/getScaledBoundingBoxDimensions';
import {
- API_BASE_MODELS,
- SUPPORTS_ASPECT_RATIO_BASE_MODELS,
SUPPORTS_NEGATIVE_PROMPT_BASE_MODELS,
SUPPORTS_OPTIMIZED_DENOISING_BASE_MODELS,
- SUPPORTS_PIXEL_DIMENSIONS_BASE_MODELS,
SUPPORTS_REF_IMAGES_BASE_MODELS,
- SUPPORTS_SEED_BASE_MODELS,
} from 'features/modelManagerV2/models';
import { CLIP_SKIP_MAP } from 'features/parameters/types/constants';
import type {
@@ -121,14 +109,6 @@ const slice = createSlice({
return;
}
- if (API_BASE_MODELS.includes(model.base)) {
- state.dimensions.aspectRatio.isLocked = true;
- state.dimensions.aspectRatio.value = 1;
- state.dimensions.aspectRatio.id = '1:1';
- state.dimensions.width = 1024;
- state.dimensions.height = 1024;
- }
-
applyClipSkip(state, model, state.clipSkip);
},
vaeSelected: (state, action: PayloadAction) => {
@@ -318,30 +298,6 @@ const slice = createSlice({
state.dimensions.aspectRatio.id = id;
if (id === 'Free') {
state.dimensions.aspectRatio.isLocked = false;
- } else if ((state.model?.base === 'imagen3' || state.model?.base === 'imagen4') && isImagenAspectRatioID(id)) {
- const { width, height } = IMAGEN_ASPECT_RATIOS[id];
- state.dimensions.width = width;
- state.dimensions.height = height;
- state.dimensions.aspectRatio.value = state.dimensions.width / state.dimensions.height;
- state.dimensions.aspectRatio.isLocked = true;
- } else if (state.model?.base === 'chatgpt-4o' && isChatGPT4oAspectRatioID(id)) {
- const { width, height } = CHATGPT_ASPECT_RATIOS[id];
- state.dimensions.width = width;
- state.dimensions.height = height;
- state.dimensions.aspectRatio.value = state.dimensions.width / state.dimensions.height;
- state.dimensions.aspectRatio.isLocked = true;
- } else if (state.model?.base === 'gemini-2.5' && isGemini2_5AspectRatioID(id)) {
- const { width, height } = GEMINI_2_5_ASPECT_RATIOS[id];
- state.dimensions.width = width;
- state.dimensions.height = height;
- state.dimensions.aspectRatio.value = state.dimensions.width / state.dimensions.height;
- state.dimensions.aspectRatio.isLocked = true;
- } else if (state.model?.base === 'flux-kontext' && isFluxKontextAspectRatioID(id)) {
- const { width, height } = FLUX_KONTEXT_ASPECT_RATIOS[id];
- state.dimensions.width = width;
- state.dimensions.height = height;
- state.dimensions.aspectRatio.value = state.dimensions.width / state.dimensions.height;
- state.dimensions.aspectRatio.isLocked = true;
} else {
state.dimensions.aspectRatio.isLocked = true;
state.dimensions.aspectRatio.value = ASPECT_RATIO_MAP[id].ratio;
@@ -541,19 +497,12 @@ export const selectIsSDXL = createParamsSelector((params) => params.model?.base
export const selectIsFLUX = createParamsSelector((params) => params.model?.base === 'flux');
export const selectIsSD3 = createParamsSelector((params) => params.model?.base === 'sd-3');
export const selectIsCogView4 = createParamsSelector((params) => params.model?.base === 'cogview4');
-export const selectIsImagen3 = createParamsSelector((params) => params.model?.base === 'imagen3');
-export const selectIsImagen4 = createParamsSelector((params) => params.model?.base === 'imagen4');
export const selectIsFluxKontext = createParamsSelector((params) => {
- if (params.model?.base === 'flux-kontext') {
- return true;
- }
if (params.model?.base === 'flux' && params.model?.name.toLowerCase().includes('kontext')) {
return true;
}
return false;
});
-export const selectIsChatGPT4o = createParamsSelector((params) => params.model?.base === 'chatgpt-4o');
-export const selectIsGemini2_5 = createParamsSelector((params) => params.model?.base === 'gemini-2.5');
export const selectModel = createParamsSelector((params) => params.model);
export const selectModelKey = createParamsSelector((params) => params.model?.key);
@@ -592,26 +541,10 @@ export const selectModelSupportsNegativePrompt = createSelector(
selectModel,
(model) => !!model && SUPPORTS_NEGATIVE_PROMPT_BASE_MODELS.includes(model.base)
);
-export const selectModelSupportsSeed = createSelector(
- selectModel,
- (model) => !!model && SUPPORTS_SEED_BASE_MODELS.includes(model.base)
-);
export const selectModelSupportsRefImages = createSelector(
selectModel,
(model) => !!model && SUPPORTS_REF_IMAGES_BASE_MODELS.includes(model.base)
);
-export const selectModelSupportsAspectRatio = createSelector(
- selectModel,
- (model) => !!model && SUPPORTS_ASPECT_RATIO_BASE_MODELS.includes(model.base)
-);
-export const selectModelSupportsPixelDimensions = createSelector(
- selectModel,
- (model) => !!model && SUPPORTS_PIXEL_DIMENSIONS_BASE_MODELS.includes(model.base)
-);
-export const selectIsApiBaseModel = createSelector(
- selectModel,
- (model) => !!model && API_BASE_MODELS.includes(model.base)
-);
export const selectModelSupportsOptimizedDenoising = createSelector(
selectModel,
(model) => !!model && SUPPORTS_OPTIMIZED_DENOISING_BASE_MODELS.includes(model.base)
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/refImagesSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/refImagesSlice.ts
index e787d08fca0..ea440786dda 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/refImagesSlice.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/refImagesSlice.ts
@@ -12,25 +12,13 @@ import type {
RefImagesState,
} from 'features/controlLayers/store/types';
import { zModelIdentifierField } from 'features/nodes/types/common';
-import type {
- ChatGPT4oModelConfig,
- FLUXKontextModelConfig,
- FLUXReduxModelConfig,
- IPAdapterModelConfig,
-} from 'services/api/types';
+import type { FLUXKontextModelConfig, FLUXReduxModelConfig, IPAdapterModelConfig } from 'services/api/types';
import { assert } from 'tsafe';
import type { PartialDeep } from 'type-fest';
import type { CLIPVisionModelV2, IPMethodV2, RefImageState } from './types';
import { getInitialRefImagesState, isFLUXReduxConfig, isIPAdapterConfig, zRefImagesState } from './types';
-import {
- getReferenceImageState,
- initialChatGPT4oReferenceImage,
- initialFluxKontextReferenceImage,
- initialFLUXRedux,
- initialGemini2_5ReferenceImage,
- initialIPAdapter,
-} from './util';
+import { getReferenceImageState, initialFluxKontextReferenceImage, initialFLUXRedux, initialIPAdapter } from './util';
type PayloadActionWithId = T extends void
? PayloadAction<{ id: string }>
@@ -103,7 +91,7 @@ const slice = createSlice({
refImageModelChanged: (
state,
action: PayloadActionWithId<{
- modelConfig: IPAdapterModelConfig | FLUXReduxModelConfig | ChatGPT4oModelConfig | FLUXKontextModelConfig | null;
+ modelConfig: IPAdapterModelConfig | FLUXKontextModelConfig | FLUXReduxModelConfig | null;
}>
) => {
const { id, modelConfig } = action.payload;
@@ -129,30 +117,7 @@ const slice = createSlice({
// The type of ref image depends on the model. When the user switches the model, we rebuild the ref image.
// When we switch the model, we keep the image the same, but change the other parameters.
- if (entity.config.model.base === 'chatgpt-4o') {
- // Switching to chatgpt-4o ref image
- entity.config = {
- ...initialChatGPT4oReferenceImage,
- image: entity.config.image,
- model: entity.config.model,
- };
- return;
- }
-
- if (entity.config.model.base === 'gemini-2.5') {
- // Switching to Gemini 2.5 Flash Preview (nano banana) ref image
- entity.config = {
- ...initialGemini2_5ReferenceImage,
- image: entity.config.image,
- model: entity.config.model,
- };
- return;
- }
-
- if (
- entity.config.model.base === 'flux-kontext' ||
- (entity.config.model.base === 'flux' && entity.config.model.name?.toLowerCase().includes('kontext'))
- ) {
+ if (entity.config.model.base === 'flux' && entity.config.model.name?.toLowerCase().includes('kontext')) {
// Switching to flux-kontext ref image
entity.config = {
...initialFluxKontextReferenceImage,
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts
index 3163bd85b2a..2b327ae8ca9 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts
@@ -555,35 +555,6 @@ export const ASPECT_RATIO_MAP: Record, { ratio: n
'9:21': { ratio: 9 / 21, inverseID: '21:9' },
};
-export const zImagen3AspectRatioID = z.enum(['16:9', '4:3', '1:1', '3:4', '9:16']);
-type ImagenAspectRatio = z.infer;
-export const isImagenAspectRatioID = (v: unknown): v is ImagenAspectRatio => zImagen3AspectRatioID.safeParse(v).success;
-export const IMAGEN_ASPECT_RATIOS: Record = {
- '16:9': { width: 1408, height: 768 },
- '4:3': { width: 1280, height: 896 },
- '1:1': { width: 1024, height: 1024 },
- '3:4': { width: 896, height: 1280 },
- '9:16': { width: 768, height: 1408 },
-};
-
-export const zChatGPT4oAspectRatioID = z.enum(['3:2', '1:1', '2:3']);
-type ChatGPT4oAspectRatio = z.infer;
-export const isChatGPT4oAspectRatioID = (v: unknown): v is ChatGPT4oAspectRatio =>
- zChatGPT4oAspectRatioID.safeParse(v).success;
-export const CHATGPT_ASPECT_RATIOS: Record = {
- '3:2': { width: 1536, height: 1024 },
- '1:1': { width: 1024, height: 1024 },
- '2:3': { width: 1024, height: 1536 },
-} as const;
-
-export const zGemini2_5AspectRatioID = z.enum(['1:1']);
-type Gemini2_5AspectRatio = z.infer;
-export const isGemini2_5AspectRatioID = (v: unknown): v is Gemini2_5AspectRatio =>
- zGemini2_5AspectRatioID.safeParse(v).success;
-export const GEMINI_2_5_ASPECT_RATIOS: Record = {
- '1:1': { width: 1024, height: 1024 },
-} as const;
-
export const zFluxKontextAspectRatioID = z.enum(['21:9', '16:9', '4:3', '1:1', '3:4', '9:16', '9:21']);
type FluxKontextAspectRatio = z.infer;
export const isFluxKontextAspectRatioID = (v: unknown): v is z.infer =>
@@ -598,33 +569,6 @@ export const FLUX_KONTEXT_ASPECT_RATIOS: Record;
-export const isVeo3AspectRatioID = (v: unknown): v is Veo3AspectRatio => zVeo3AspectRatioID.safeParse(v).success;
-
-export const zRunwayAspectRatioID = z.enum(['16:9', '4:3', '1:1', '3:4', '9:16', '21:9']);
-type RunwayAspectRatio = z.infer;
-export const isRunwayAspectRatioID = (v: unknown): v is RunwayAspectRatio => zRunwayAspectRatioID.safeParse(v).success;
-
-export const zVideoAspectRatio = z.union([zVeo3AspectRatioID, zRunwayAspectRatioID]);
-export type VideoAspectRatio = z.infer;
-export const isVideoAspectRatio = (v: unknown): v is VideoAspectRatio => zVideoAspectRatio.safeParse(v).success;
-
-export const zVeo3Resolution = z.enum(['720p', '1080p']);
-type Veo3Resolution = z.infer;
-export const isVeo3Resolution = (v: unknown): v is Veo3Resolution => zVeo3Resolution.safeParse(v).success;
-export const RESOLUTION_MAP: Record = {
- '720p': { width: 1280, height: 720 },
- '1080p': { width: 1920, height: 1080 },
-};
-
-export const zRunwayResolution = z.enum(['720p']);
-type RunwayResolution = z.infer;
-export const isRunwayResolution = (v: unknown): v is RunwayResolution => zRunwayResolution.safeParse(v).success;
-
-export const zVideoResolution = z.union([zVeo3Resolution, zRunwayResolution]);
-export type VideoResolution = z.infer;
-
const zAspectRatioConfig = z.object({
id: zAspectRatioID,
value: z.number().gt(0),
@@ -638,24 +582,6 @@ export const DEFAULT_ASPECT_RATIO_CONFIG: AspectRatioConfig = {
isLocked: false,
};
-const zVeo3DurationID = z.enum(['8']);
-type Veo3Duration = z.infer;
-export const isVeo3DurationID = (v: unknown): v is Veo3Duration => zVeo3DurationID.safeParse(v).success;
-export const VEO3_DURATIONS: Record = {
- '8': '8 seconds',
-};
-
-const zRunwayDurationID = z.enum(['5', '10']);
-type RunwayDuration = z.infer;
-export const isRunwayDurationID = (v: unknown): v is RunwayDuration => zRunwayDurationID.safeParse(v).success;
-export const RUNWAY_DURATIONS: Record = {
- '5': '5 seconds',
- '10': '10 seconds',
-};
-
-export const zVideoDuration = z.union([zVeo3DurationID, zRunwayDurationID]);
-export type VideoDuration = z.infer;
-
const zBboxState = z.object({
rect: z.object({
x: z.number().int(),
diff --git a/invokeai/frontend/web/src/features/cropper/lib/editor.ts b/invokeai/frontend/web/src/features/cropper/lib/editor.ts
index 6249e3bb255..62ce5ca9df9 100644
--- a/invokeai/frontend/web/src/features/cropper/lib/editor.ts
+++ b/invokeai/frontend/web/src/features/cropper/lib/editor.ts
@@ -1,4 +1,3 @@
-import { $crossOrigin } from 'app/store/nanostores/authToken';
import { TRANSPARENCY_CHECKERBOARD_PATTERN_DARK_DATAURL } from 'features/controlLayers/konva/patterns/transparency-checkerboard-pattern';
import Konva from 'konva';
import type { KonvaEventObject } from 'konva/lib/Node';
@@ -1098,7 +1097,7 @@ export class Editor {
return new Promise((resolve, reject) => {
const img = new Image();
- img.crossOrigin = $crossOrigin.get();
+ img.crossOrigin = 'anonymous';
img.onload = () => {
this.originalImage = img;
diff --git a/invokeai/frontend/web/src/features/deleteImageModal/hooks/use-delete-video.ts b/invokeai/frontend/web/src/features/deleteImageModal/hooks/use-delete-video.ts
deleted file mode 100644
index b14cd70ebe8..00000000000
--- a/invokeai/frontend/web/src/features/deleteImageModal/hooks/use-delete-video.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { useDeleteVideoModalApi } from 'features/deleteVideoModal/store/state';
-import { useCallback, useMemo } from 'react';
-import type { VideoDTO } from 'services/api/types';
-
-export const useDeleteVideo = (videoDTO?: VideoDTO | null) => {
- const deleteImageModal = useDeleteVideoModalApi();
-
- const isEnabled = useMemo(() => {
- if (!videoDTO) {
- return;
- }
- return true;
- }, [videoDTO]);
- const _delete = useCallback(() => {
- if (!videoDTO) {
- return;
- }
- if (!isEnabled) {
- return;
- }
- deleteImageModal.delete([videoDTO.video_id]);
- }, [deleteImageModal, videoDTO, isEnabled]);
-
- return {
- delete: _delete,
- isEnabled,
- };
-};
diff --git a/invokeai/frontend/web/src/features/deleteVideoModal/components/DeleteVideoButton.tsx b/invokeai/frontend/web/src/features/deleteVideoModal/components/DeleteVideoButton.tsx
deleted file mode 100644
index 9e56bfba7dc..00000000000
--- a/invokeai/frontend/web/src/features/deleteVideoModal/components/DeleteVideoButton.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import type { IconButtonProps } from '@invoke-ai/ui-library';
-import { IconButton } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { useAppSelector } from 'app/store/storeHooks';
-import { selectSelectionCount } from 'features/gallery/store/gallerySelectors';
-import { memo } from 'react';
-import { useTranslation } from 'react-i18next';
-import { PiTrashSimpleBold } from 'react-icons/pi';
-import { $isConnected } from 'services/events/stores';
-
-type Props = Omit & {
- onClick: () => void;
-};
-
-export const DeleteVideoButton = memo((props: Props) => {
- const { onClick, isDisabled } = props;
- const { t } = useTranslation();
- const isConnected = useStore($isConnected);
- const count = useAppSelector(selectSelectionCount);
- const labelMessage: string = `${t('gallery.deleteVideo', { count })} (Del)`;
-
- return (
- }
- tooltip={labelMessage}
- aria-label={labelMessage}
- isDisabled={isDisabled || !isConnected}
- colorScheme="error"
- variant="link"
- alignSelf="stretch"
- />
- );
-});
-
-DeleteVideoButton.displayName = 'DeleteVideoButton';
diff --git a/invokeai/frontend/web/src/features/deleteVideoModal/components/DeleteVideoModal.tsx b/invokeai/frontend/web/src/features/deleteVideoModal/components/DeleteVideoModal.tsx
deleted file mode 100644
index 662e7e6f773..00000000000
--- a/invokeai/frontend/web/src/features/deleteVideoModal/components/DeleteVideoModal.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { ConfirmationAlertDialog, Flex, FormControl, FormLabel, Switch, Text } from '@invoke-ai/ui-library';
-import { useAppSelector, useAppStore } from 'app/store/storeHooks';
-import { useDeleteVideoModalApi, useDeleteVideoModalState } from 'features/deleteVideoModal/store/state';
-import { selectSystemShouldConfirmOnDelete, setShouldConfirmOnDelete } from 'features/system/store/systemSlice';
-import type { ChangeEvent } from 'react';
-import { memo, useCallback } from 'react';
-import { useTranslation } from 'react-i18next';
-
-export const DeleteVideoModal = memo(() => {
- const state = useDeleteVideoModalState();
- const api = useDeleteVideoModalApi();
- const { dispatch } = useAppStore();
- const { t } = useTranslation();
- const shouldConfirmOnDelete = useAppSelector(selectSystemShouldConfirmOnDelete);
-
- const handleChangeShouldConfirmOnDelete = useCallback(
- (e: ChangeEvent) => dispatch(setShouldConfirmOnDelete(!e.target.checked)),
- [dispatch]
- );
-
- return (
-
-
- {t('gallery.deleteVideoPermanent')}
- {t('common.areYouSure')}
-
- {t('common.dontAskMeAgain')}
-
-
-
-
- );
-});
-DeleteVideoModal.displayName = 'DeleteVideoModal';
diff --git a/invokeai/frontend/web/src/features/deleteVideoModal/store/state.ts b/invokeai/frontend/web/src/features/deleteVideoModal/store/state.ts
deleted file mode 100644
index 4e7580ce301..00000000000
--- a/invokeai/frontend/web/src/features/deleteVideoModal/store/state.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import { useStore } from '@nanostores/react';
-import type { AppStore } from 'app/store/store';
-import { useAppStore } from 'app/store/storeHooks';
-import { intersection } from 'es-toolkit/compat';
-import { selectGetVideoIdsQueryArgs } from 'features/gallery/store/gallerySelectors';
-import { itemSelected } from 'features/gallery/store/gallerySlice';
-import { selectSystemShouldConfirmOnDelete } from 'features/system/store/systemSlice';
-import { atom } from 'nanostores';
-import { useMemo } from 'react';
-import { videosApi } from 'services/api/endpoints/videos';
-
-// Implements an awaitable modal dialog for deleting images
-
-type DeleteVideosModalState = {
- video_ids: string[];
- isOpen: boolean;
- resolve?: () => void;
- reject?: (reason?: string) => void;
-};
-
-const getInitialState = (): DeleteVideosModalState => ({
- video_ids: [],
- isOpen: false,
-});
-
-const $deleteVideosModalState = atom(getInitialState());
-
-const deleteVideosWithDialog = async (video_ids: string[], store: AppStore): Promise => {
- const { getState } = store;
- const shouldConfirmOnDelete = selectSystemShouldConfirmOnDelete(getState());
-
- if (!shouldConfirmOnDelete) {
- // If we don't need to confirm and the resources are not in use, delete them directly
- await handleDeletions(video_ids, store);
- return;
- }
-
- return new Promise((resolve, reject) => {
- $deleteVideosModalState.set({
- video_ids,
- isOpen: true,
- resolve,
- reject,
- });
- });
-};
-
-const handleDeletions = async (video_ids: string[], store: AppStore) => {
- try {
- const { dispatch, getState } = store;
- const state = getState();
- const { data } = videosApi.endpoints.getVideoIds.select(selectGetVideoIdsQueryArgs(state))(state);
- const index = data?.video_ids.findIndex((id) => id === video_ids[0]);
- const { deleted_videos } = await dispatch(
- videosApi.endpoints.deleteVideos.initiate({ video_ids }, { track: false })
- ).unwrap();
-
- const newVideoIds = data?.video_ids.filter((id) => !deleted_videos.includes(id)) || [];
- const newSelectedVideoId = newVideoIds[index ?? 0] || null;
-
- if (
- intersection(
- state.gallery.selection.map((s) => s.id),
- video_ids
- ).length > 0 &&
- newSelectedVideoId
- ) {
- // Some selected images were deleted, clear selection
- dispatch(itemSelected({ type: 'video', id: newSelectedVideoId }));
- }
- } catch {
- // no-op
- }
-};
-
-const confirmDeletion = async (store: AppStore) => {
- const state = $deleteVideosModalState.get();
- await handleDeletions(state.video_ids, store);
- state.resolve?.();
- closeSilently();
-};
-
-const cancelDeletion = () => {
- const state = $deleteVideosModalState.get();
- state.reject?.('User canceled');
- closeSilently();
-};
-
-const closeSilently = () => {
- $deleteVideosModalState.set(getInitialState());
-};
-
-export const useDeleteVideoModalState = () => {
- const state = useStore($deleteVideosModalState);
- return state;
-};
-
-export const useDeleteVideoModalApi = () => {
- const store = useAppStore();
- const api = useMemo(
- () => ({
- delete: (video_ids: string[]) => deleteVideosWithDialog(video_ids, store),
- confirm: () => confirmDeletion(store),
- cancel: cancelDeletion,
- close: closeSilently,
- }),
- [store]
- );
-
- return api;
-};
diff --git a/invokeai/frontend/web/src/features/dnd/DndDragPreviewMultipleVideo.tsx b/invokeai/frontend/web/src/features/dnd/DndDragPreviewMultipleVideo.tsx
deleted file mode 100644
index 6ccb7e48503..00000000000
--- a/invokeai/frontend/web/src/features/dnd/DndDragPreviewMultipleVideo.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import type { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
-import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
-import { Flex, Heading } from '@invoke-ai/ui-library';
-import type { MultipleVideoDndSourceData } from 'features/dnd/dnd';
-import { DND_IMAGE_DRAG_PREVIEW_SIZE, preserveOffsetOnSourceFallbackCentered } from 'features/dnd/util';
-import { memo } from 'react';
-import { createPortal } from 'react-dom';
-import { useTranslation } from 'react-i18next';
-import type { Param0 } from 'tsafe';
-
-const DndDragPreviewMultipleVideo = memo(({ video_ids }: { video_ids: string[] }) => {
- const { t } = useTranslation();
- return (
-
- {video_ids.length}
- {t('parameters.videos_withCount', { count: video_ids.length })}
-
- );
-});
-
-DndDragPreviewMultipleVideo.displayName = 'DndDragPreviewMultipleVideo';
-
-export type DndDragPreviewMultipleVideoState = {
- type: 'multiple-video';
- container: HTMLElement;
- video_ids: string[];
-};
-
-export const createMultipleVideoDragPreview = (arg: DndDragPreviewMultipleVideoState) =>
- createPortal(, arg.container);
-
-type SetMultipleDragPreviewArg = {
- multipleVideoDndData: MultipleVideoDndSourceData;
- setDragPreviewState: (dragPreviewState: DndDragPreviewMultipleVideoState | null) => void;
- onGenerateDragPreviewArgs: Param0['onGenerateDragPreview']>;
-};
-
-export const setMultipleVideoDragPreview = ({
- multipleVideoDndData,
- onGenerateDragPreviewArgs,
- setDragPreviewState,
-}: SetMultipleDragPreviewArg) => {
- const { nativeSetDragImage, source, location } = onGenerateDragPreviewArgs;
- setCustomNativeDragPreview({
- render({ container }) {
- setDragPreviewState({ type: 'multiple-video', container, video_ids: multipleVideoDndData.payload.video_ids });
- return () => setDragPreviewState(null);
- },
- nativeSetDragImage,
- getOffset: preserveOffsetOnSourceFallbackCentered({
- element: source.element,
- input: location.current.input,
- }),
- });
-};
diff --git a/invokeai/frontend/web/src/features/dnd/DndDragPreviewSingleVideo.tsx b/invokeai/frontend/web/src/features/dnd/DndDragPreviewSingleVideo.tsx
deleted file mode 100644
index 0fe0fcec752..00000000000
--- a/invokeai/frontend/web/src/features/dnd/DndDragPreviewSingleVideo.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import type { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
-import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
-import { chakra, Flex } from '@invoke-ai/ui-library';
-import type { SingleVideoDndSourceData } from 'features/dnd/dnd';
-import { DND_IMAGE_DRAG_PREVIEW_SIZE, preserveOffsetOnSourceFallbackCentered } from 'features/dnd/util';
-import { GalleryVideoPlaceholder } from 'features/gallery/components/ImageGrid/GalleryVideoPlaceholder';
-import { memo } from 'react';
-import { createPortal } from 'react-dom';
-import type { VideoDTO } from 'services/api/types';
-import type { Param0 } from 'tsafe';
-
-const ChakraImg = chakra('img');
-
-const DndDragPreviewSingleVideo = memo(({ videoDTO }: { videoDTO: VideoDTO }) => {
- return (
-
-
-
-
- );
-});
-
-DndDragPreviewSingleVideo.displayName = 'DndDragPreviewSingleVideo';
-
-export type DndDragPreviewSingleVideoState = {
- type: 'single-video';
- container: HTMLElement;
- videoDTO: VideoDTO;
-};
-
-export const createSingleVideoDragPreview = (arg: DndDragPreviewSingleVideoState) =>
- createPortal(, arg.container);
-
-type SetSingleDragPreviewArg = {
- singleVideoDndData: SingleVideoDndSourceData;
- setDragPreviewState: (dragPreviewState: DndDragPreviewSingleVideoState | null) => void;
- onGenerateDragPreviewArgs: Param0['onGenerateDragPreview']>;
-};
-
-export const setSingleVideoDragPreview = ({
- singleVideoDndData,
- onGenerateDragPreviewArgs,
- setDragPreviewState,
-}: SetSingleDragPreviewArg) => {
- const { nativeSetDragImage, source, location } = onGenerateDragPreviewArgs;
- setCustomNativeDragPreview({
- render({ container }) {
- setDragPreviewState({ type: 'single-video', container, videoDTO: singleVideoDndData.payload.videoDTO });
- return () => setDragPreviewState(null);
- },
- nativeSetDragImage,
- getOffset: preserveOffsetOnSourceFallbackCentered({
- element: source.element,
- input: location.current.input,
- }),
- });
-};
diff --git a/invokeai/frontend/web/src/features/dnd/DndImage.tsx b/invokeai/frontend/web/src/features/dnd/DndImage.tsx
index 71488500b88..2c7e4e8ad30 100644
--- a/invokeai/frontend/web/src/features/dnd/DndImage.tsx
+++ b/invokeai/frontend/web/src/features/dnd/DndImage.tsx
@@ -2,8 +2,6 @@ import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import type { ImageProps, SystemStyleObject } from '@invoke-ai/ui-library';
import { Image } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { $crossOrigin } from 'app/store/nanostores/authToken';
import { useAppStore } from 'app/store/storeHooks';
import { singleImageDndSource } from 'features/dnd/dnd';
import type { DndDragPreviewSingleImageState } from 'features/dnd/DndDragPreviewSingleImage';
@@ -31,7 +29,6 @@ type Props = {
export const DndImage = memo(
forwardRef(({ imageDTO, asThumbnail, ...rest }: Props, forwardedRef) => {
const store = useAppStore();
- const crossOrigin = useStore($crossOrigin);
const [isDragging, setIsDragging] = useState(false);
const ref = useRef(null);
@@ -80,7 +77,6 @@ export const DndImage = memo(
height={imageDTO.height}
sx={sx}
data-is-dragging={isDragging}
- crossOrigin={!asThumbnail ? crossOrigin : undefined}
{...rest}
/>
{dragPreviewState?.type === 'single-image' ? createSingleImageDragPreview(dragPreviewState) : null}
diff --git a/invokeai/frontend/web/src/features/dnd/FullscreenDropzone.tsx b/invokeai/frontend/web/src/features/dnd/FullscreenDropzone.tsx
index f10b5b9d598..e5d7df68f28 100644
--- a/invokeai/frontend/web/src/features/dnd/FullscreenDropzone.tsx
+++ b/invokeai/frontend/web/src/features/dnd/FullscreenDropzone.tsx
@@ -7,12 +7,10 @@ import { Box, Flex, Heading } from '@invoke-ai/ui-library';
import { getStore } from 'app/store/nanostores/store';
import { useAppSelector } from 'app/store/storeHooks';
import { getFocusedRegion } from 'common/hooks/focus';
-import { useClientSideUpload } from 'common/hooks/useClientSideUpload';
import { setFileToPaste } from 'features/controlLayers/components/CanvasPasteModal';
import { DndDropOverlay } from 'features/dnd/DndDropOverlay';
import type { DndTargetState } from 'features/dnd/types';
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
-import { selectIsClientSideUploadEnabled } from 'features/system/store/configSlice';
import { toast } from 'features/toast/toast';
import { selectActiveTab } from 'features/ui/store/uiSelectors';
import { memo, useCallback, useEffect, useRef, useState } from 'react';
@@ -68,11 +66,9 @@ export const FullscreenDropzone = memo(() => {
const ref = useRef(null);
const [dndState, setDndState] = useState('idle');
const activeTab = useAppSelector(selectActiveTab);
- const isClientSideUploadEnabled = useAppSelector(selectIsClientSideUploadEnabled);
- const clientSideUpload = useClientSideUpload();
const validateAndUploadFiles = useCallback(
- async (files: File[]) => {
+ (files: File[]) => {
const { getState } = getStore();
const parseResult = z.array(zUploadFile).safeParse(files);
@@ -100,23 +96,17 @@ export const FullscreenDropzone = memo(() => {
const autoAddBoardId = selectAutoAddBoardId(getState());
- if (isClientSideUploadEnabled && files.length > 1) {
- for (const [i, file] of files.entries()) {
- await clientSideUpload(file, i);
- }
- } else {
- const uploadArgs: UploadImageArg[] = files.map((file, i) => ({
- file,
- image_category: 'user',
- is_intermediate: false,
- board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
- isFirstUploadOfBatch: i === 0,
- }));
-
- uploadImages(uploadArgs);
- }
+ const uploadArgs: UploadImageArg[] = files.map((file, i) => ({
+ file,
+ image_category: 'user',
+ is_intermediate: false,
+ board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
+ isFirstUploadOfBatch: i === 0,
+ }));
+
+ uploadImages(uploadArgs);
},
- [activeTab, t, isClientSideUploadEnabled, clientSideUpload]
+ [activeTab, t]
);
const onPaste = useCallback(
diff --git a/invokeai/frontend/web/src/features/dnd/dnd.ts b/invokeai/frontend/web/src/features/dnd/dnd.ts
index 0aef104869f..f5e38d4b944 100644
--- a/invokeai/frontend/web/src/features/dnd/dnd.ts
+++ b/invokeai/frontend/web/src/features/dnd/dnd.ts
@@ -9,11 +9,9 @@ import { selectComparisonImages } from 'features/gallery/components/ImageViewer/
import type { BoardId } from 'features/gallery/store/types';
import {
addImagesToBoard,
- addVideosToBoard,
createNewCanvasEntityFromImage,
newCanvasFromImage,
removeImagesFromBoard,
- removeVideosFromBoard,
replaceCanvasEntityObjectsWithImage,
setComparisonImage,
setGlobalReferenceImage,
@@ -24,10 +22,7 @@ import {
import { fieldImageCollectionValueChanged } from 'features/nodes/store/nodesSlice';
import { selectFieldInputInstanceSafe, selectNodesSlice } from 'features/nodes/store/selectors';
import { type FieldIdentifier, isImageFieldCollectionInputInstance } from 'features/nodes/types/field';
-import { startingFrameImageChanged } from 'features/parameters/store/videoSlice';
-import { expandPrompt } from 'features/prompt/PromptExpansion/expand';
-import { promptExpansionApi } from 'features/prompt/PromptExpansion/state';
-import type { ImageDTO, VideoDTO } from 'services/api/types';
+import type { ImageDTO } from 'services/api/types';
import type { JsonObject } from 'type-fest';
const log = logger('dnd');
@@ -74,34 +69,6 @@ type DndSource = {
getData: ReturnType>;
};
-//#region Single Video
-const _singleVideo = buildTypeAndKey('single-video');
-export type SingleVideoDndSourceData = DndData<
- typeof _singleVideo.type,
- typeof _singleVideo.key,
- { videoDTO: VideoDTO }
->;
-export const singleVideoDndSource: DndSource = {
- ..._singleVideo,
- typeGuard: buildTypeGuard(_singleVideo.key),
- getData: buildGetData(_singleVideo.key, _singleVideo.type),
-};
-//#endregion
-
-//#region Multiple Image
-const _multipleVideo = buildTypeAndKey('multiple-video');
-export type MultipleVideoDndSourceData = DndData<
- typeof _multipleVideo.type,
- typeof _multipleVideo.key,
- { video_ids: string[]; board_id: BoardId }
->;
-export const multipleVideoDndSource: DndSource = {
- ..._multipleVideo,
- typeGuard: buildTypeGuard(_multipleVideo.key),
- getData: buildGetData(_multipleVideo.key, _multipleVideo.type),
-};
-//#endregion
-
//#region Single Image
const _singleImage = buildTypeAndKey('single-image');
export type SingleImageDndSourceData = DndData<
@@ -475,22 +442,12 @@ export type AddImageToBoardDndTargetData = DndData<
>;
export const addImageToBoardDndTarget: DndTarget<
AddImageToBoardDndTargetData,
- SingleImageDndSourceData | MultipleImageDndSourceData | SingleVideoDndSourceData | MultipleVideoDndSourceData
+ SingleImageDndSourceData | MultipleImageDndSourceData
> = {
..._addToBoard,
typeGuard: buildTypeGuard(_addToBoard.key),
getData: buildGetData(_addToBoard.key, _addToBoard.type),
isValid: ({ sourceData, targetData }) => {
- if (singleVideoDndSource.typeGuard(sourceData)) {
- const currentBoard = sourceData.payload.videoDTO.board_id ?? 'none';
- const destinationBoard = targetData.payload.boardId;
- return currentBoard !== destinationBoard;
- }
- if (multipleVideoDndSource.typeGuard(sourceData)) {
- const currentBoard = sourceData.payload.board_id;
- const destinationBoard = targetData.payload.boardId;
- return currentBoard !== destinationBoard;
- }
if (singleImageDndSource.typeGuard(sourceData)) {
const currentBoard = sourceData.payload.imageDTO.board_id ?? 'none';
const destinationBoard = targetData.payload.boardId;
@@ -504,18 +461,6 @@ export const addImageToBoardDndTarget: DndTarget<
return false;
},
handler: ({ sourceData, targetData, dispatch }) => {
- if (singleVideoDndSource.typeGuard(sourceData)) {
- const { videoDTO } = sourceData.payload;
- const { boardId } = targetData.payload;
- addVideosToBoard({ video_ids: [videoDTO.video_id], boardId, dispatch });
- }
-
- if (multipleVideoDndSource.typeGuard(sourceData)) {
- const { video_ids } = sourceData.payload;
- const { boardId } = targetData.payload;
- addVideosToBoard({ video_ids, boardId, dispatch });
- }
-
if (singleImageDndSource.typeGuard(sourceData)) {
const { imageDTO } = sourceData.payload;
const { boardId } = targetData.payload;
@@ -541,7 +486,7 @@ export type RemoveImageFromBoardDndTargetData = DndData<
>;
export const removeImageFromBoardDndTarget: DndTarget<
RemoveImageFromBoardDndTargetData,
- SingleImageDndSourceData | MultipleImageDndSourceData | SingleVideoDndSourceData | MultipleVideoDndSourceData
+ SingleImageDndSourceData | MultipleImageDndSourceData
> = {
..._removeFromBoard,
typeGuard: buildTypeGuard(_removeFromBoard.key),
@@ -557,16 +502,6 @@ export const removeImageFromBoardDndTarget: DndTarget<
return currentBoard !== 'none';
}
- if (singleVideoDndSource.typeGuard(sourceData)) {
- const currentBoard = sourceData.payload.videoDTO.board_id ?? 'none';
- return currentBoard !== 'none';
- }
-
- if (multipleVideoDndSource.typeGuard(sourceData)) {
- const currentBoard = sourceData.payload.board_id;
- return currentBoard !== 'none';
- }
-
return false;
},
handler: ({ sourceData, dispatch }) => {
@@ -579,71 +514,9 @@ export const removeImageFromBoardDndTarget: DndTarget<
const { image_names } = sourceData.payload;
removeImagesFromBoard({ image_names, dispatch });
}
-
- if (singleVideoDndSource.typeGuard(sourceData)) {
- const { videoDTO } = sourceData.payload;
- removeVideosFromBoard({ video_ids: [videoDTO.video_id], dispatch });
- }
-
- if (multipleVideoDndSource.typeGuard(sourceData)) {
- const { video_ids } = sourceData.payload;
- removeVideosFromBoard({ video_ids, dispatch });
- }
- },
-};
-
-//#endregion
-
-//#region Prompt Generation From Image
-const _promptGenerationFromImage = buildTypeAndKey('prompt-generation-from-image');
-type PromptGenerationFromImageDndTargetData = DndData<
- typeof _promptGenerationFromImage.type,
- typeof _promptGenerationFromImage.key,
- void
->;
-export const promptGenerationFromImageDndTarget: DndTarget<
- PromptGenerationFromImageDndTargetData,
- SingleImageDndSourceData
-> = {
- ..._promptGenerationFromImage,
- typeGuard: buildTypeGuard(_promptGenerationFromImage.key),
- getData: buildGetData(_promptGenerationFromImage.key, _promptGenerationFromImage.type),
- isValid: ({ sourceData }) => {
- if (singleImageDndSource.typeGuard(sourceData)) {
- return true;
- }
- return false;
- },
- handler: ({ sourceData, dispatch, getState }) => {
- const { imageDTO } = sourceData.payload;
- promptExpansionApi.setPending(imageDTO);
- expandPrompt({ dispatch, getState, imageDTO });
},
};
-//#endregion
-//#region Video Frame From Image
-const _videoFrameFromImage = buildTypeAndKey('video-frame-from-image');
-type VideoFrameFromImageDndTargetData = DndData<
- typeof _videoFrameFromImage.type,
- typeof _videoFrameFromImage.key,
- { frame: 'start' | 'end' }
->;
-export const videoFrameFromImageDndTarget: DndTarget = {
- ..._videoFrameFromImage,
- typeGuard: buildTypeGuard(_videoFrameFromImage.key),
- getData: buildGetData(_videoFrameFromImage.key, _videoFrameFromImage.type),
- isValid: ({ sourceData }) => {
- if (singleImageDndSource.typeGuard(sourceData)) {
- return true;
- }
- return false;
- },
- handler: ({ sourceData, dispatch }) => {
- const { imageDTO } = sourceData.payload;
- dispatch(startingFrameImageChanged(imageDTOToCroppableImage(imageDTO)));
- },
-};
//#endregion
export const dndTargets = [
@@ -659,8 +532,6 @@ export const dndTargets = [
replaceCanvasEntityObjectsWithImageDndTarget,
addImageToBoardDndTarget,
removeImageFromBoardDndTarget,
- promptGenerationFromImageDndTarget,
- videoFrameFromImageDndTarget,
] as const;
export type AnyDndTarget = (typeof dndTargets)[number];
diff --git a/invokeai/frontend/web/src/features/dnd/useDndMonitor.ts b/invokeai/frontend/web/src/features/dnd/useDndMonitor.ts
index 8d2aeb30e74..24d6bea1680 100644
--- a/invokeai/frontend/web/src/features/dnd/useDndMonitor.ts
+++ b/invokeai/frontend/web/src/features/dnd/useDndMonitor.ts
@@ -4,13 +4,7 @@ import { logger } from 'app/logging/logger';
import { getStore } from 'app/store/nanostores/store';
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
import { parseify } from 'common/util/serialize';
-import {
- dndTargets,
- multipleImageDndSource,
- multipleVideoDndSource,
- singleImageDndSource,
- singleVideoDndSource,
-} from 'features/dnd/dnd';
+import { dndTargets, multipleImageDndSource, singleImageDndSource } from 'features/dnd/dnd';
import { useEffect } from 'react';
const log = logger('dnd');
@@ -25,12 +19,7 @@ export const useDndMonitor = () => {
const sourceData = source.data;
// Check for allowed sources
- if (
- !singleImageDndSource.typeGuard(sourceData) &&
- !multipleImageDndSource.typeGuard(sourceData) &&
- !singleVideoDndSource.typeGuard(sourceData) &&
- !multipleVideoDndSource.typeGuard(sourceData)
- ) {
+ if (!singleImageDndSource.typeGuard(sourceData) && !multipleImageDndSource.typeGuard(sourceData)) {
return false;
}
diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx
index cc363c8122c..e17743207fa 100644
--- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx
+++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx
@@ -6,13 +6,21 @@ import {
selectDynamicPromptsCombinatorial,
selectDynamicPromptsMaxPrompts,
} from 'features/dynamicPrompts/store/dynamicPromptsSlice';
-import { selectMaxPromptsConfig } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
+const CONSTRAINTS = {
+ initial: 100,
+ sliderMin: 1,
+ sliderMax: 1000,
+ numberInputMin: 1,
+ numberInputMax: 10000,
+ fineStep: 1,
+ coarseStep: 10,
+};
+
const ParamDynamicPromptsMaxPrompts = () => {
const maxPrompts = useAppSelector(selectDynamicPromptsMaxPrompts);
- const config = useAppSelector(selectMaxPromptsConfig);
const combinatorial = useAppSelector(selectDynamicPromptsCombinatorial);
const dispatch = useAppDispatch();
const { t } = useTranslation();
@@ -30,18 +38,18 @@ const ParamDynamicPromptsMaxPrompts = () => {
{t('dynamicPrompts.maxPrompts')}
diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/hooks/useDynamicPromptsWatcher.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/hooks/useDynamicPromptsWatcher.tsx
index 011ca414f08..2fd6b18b06a 100644
--- a/invokeai/frontend/web/src/features/dynamicPrompts/hooks/useDynamicPromptsWatcher.tsx
+++ b/invokeai/frontend/web/src/features/dynamicPrompts/hooks/useDynamicPromptsWatcher.tsx
@@ -9,7 +9,6 @@ import {
} from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
import { selectPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useEffect, useMemo } from 'react';
import { utilitiesApi } from 'services/api/endpoints/utilities';
@@ -24,8 +23,6 @@ export const useDynamicPromptsWatcher = () => {
const presetModifiedPrompts = useAppSelector(selectPresetModifiedPrompts);
const maxPrompts = useAppSelector(selectDynamicPromptsMaxPrompts);
- const dynamicPrompting = useFeatureStatus('dynamicPrompting');
-
const debouncedUpdateDynamicPrompts = useMemo(
() =>
debounce(async (positivePrompt: string, maxPrompts: number) => {
@@ -55,10 +52,6 @@ export const useDynamicPromptsWatcher = () => {
);
useEffect(() => {
- if (!dynamicPrompting) {
- return;
- }
-
// Before we execute, imperatively check the dynamic prompts query cache to see if we have already fetched this prompt
const state = getState();
@@ -88,5 +81,5 @@ export const useDynamicPromptsWatcher = () => {
}
debouncedUpdateDynamicPrompts(presetModifiedPrompts.positive, maxPrompts);
- }, [debouncedUpdateDynamicPrompts, dispatch, dynamicPrompting, getState, maxPrompts, presetModifiedPrompts]);
+ }, [debouncedUpdateDynamicPrompts, dispatch, getState, maxPrompts, presetModifiedPrompts]);
};
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx
index 721792dbd54..5cc25f6c038 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx
@@ -5,7 +5,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { $boardToDelete } from 'features/gallery/components/Boards/DeleteBoardModal';
import { selectAutoAddBoardId, selectAutoAssignBoardOnClick } from 'features/gallery/store/gallerySelectors';
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { toast } from 'features/toast/toast';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -33,7 +32,6 @@ const BoardContextMenu = ({ board, children }: Props) => {
const isSelectedForAutoAdd = useAppSelector(selectIsSelectedForAutoAdd);
const boardName = useBoardName(board.board_id);
- const isBulkDownloadEnabled = useFeatureStatus('bulkDownload');
const [bulkDownload] = useBulkDownloadImagesMutation();
@@ -79,11 +77,10 @@ const BoardContextMenu = ({ board, children }: Props) => {
{isSelectedForAutoAdd ? t('boards.selectedForAutoAdd') : t('boards.menuItemAutoAdd')}
)}
- {isBulkDownloadEnabled && (
- } onClickCapture={handleBulkDownload}>
- {t('boards.downloadBoard')}
-
- )}
+
+ } onClickCapture={handleBulkDownload}>
+ {t('boards.downloadBoard')}
+
{board.archived && (
} onClick={handleUnarchive}>
@@ -109,7 +106,6 @@ const BoardContextMenu = ({ board, children }: Props) => {
isSelectedForAutoAdd,
handleSetAutoAdd,
t,
- isBulkDownloadEnabled,
handleBulkDownload,
board.archived,
handleUnarchive,
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx
index 6a9e51cb74c..9f59e60fee8 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx
@@ -1,47 +1,32 @@
import { IconButton } from '@invoke-ai/ui-library';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { useAppDispatch } from 'app/store/storeHooks';
import { boardIdSelected, boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
-import { selectAllowPrivateBoards } from 'features/system/store/configSelectors';
-import { memo, useCallback, useMemo } from 'react';
+import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiPlusBold } from 'react-icons/pi';
import { useCreateBoardMutation } from 'services/api/endpoints/boards';
-type Props = {
- isPrivateBoard: boolean;
-};
-
-const AddBoardButton = ({ isPrivateBoard }: Props) => {
+const AddBoardButton = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
- const allowPrivateBoards = useAppSelector(selectAllowPrivateBoards);
const [createBoard, { isLoading }] = useCreateBoardMutation();
- const label = useMemo(() => {
- if (!allowPrivateBoards) {
- return t('boards.addBoard');
- }
- if (isPrivateBoard) {
- return t('boards.addPrivateBoard');
- }
- return t('boards.addSharedBoard');
- }, [allowPrivateBoards, isPrivateBoard, t]);
const handleCreateBoard = useCallback(async () => {
try {
- const board = await createBoard({ board_name: t('boards.myBoard'), is_private: isPrivateBoard }).unwrap();
+ const board = await createBoard({ board_name: t('boards.myBoard') }).unwrap();
dispatch(boardIdSelected({ boardId: board.board_id }));
dispatch(boardSearchTextChanged(''));
} catch {
//no-op
}
- }, [t, createBoard, isPrivateBoard, dispatch]);
+ }, [t, createBoard, dispatch]);
return (
}
isLoading={isLoading}
- tooltip={label}
- aria-label={label}
+ tooltip={t('boards.addBoard')}
+ aria-label={t('boards.addBoard')}
onClick={handleCreateBoard}
size="md"
data-testid="add-board-button"
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTooltip.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTooltip.tsx
index ce524bac801..8877b22612f 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTooltip.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardTooltip.tsx
@@ -1,6 +1,5 @@
import { Flex, Image, Text } from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useTranslation } from 'react-i18next';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import type { BoardDTO } from 'services/api/types';
@@ -10,13 +9,11 @@ type Props = {
boardCounts: {
image_count: number;
asset_count: number;
- video_count: number;
};
};
export const BoardTooltip = ({ board, boardCounts }: Props) => {
const { t } = useTranslation();
- const isVideoEnabled = useFeatureStatus('video');
const { currentData: coverImage } = useGetImageDTOQuery(board?.cover_image_name ?? skipToken);
@@ -39,7 +36,6 @@ export const BoardTooltip = ({ board, boardCounts }: Props) => {
{t('boards.imagesWithCount', { count: boardCounts.image_count })},{' '}
{t('boards.assetsWithCount', { count: boardCounts.asset_count })}
- {isVideoEnabled && {t('boards.videosWithCount', { count: boardCounts.video_count })}}
{board?.archived && ({t('boards.archived')})}
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx
index 3b48882b5c5..2d37a03f69f 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsList.tsx
@@ -1,4 +1,4 @@
-import { Button, Collapse, Flex, Icon, Text, useDisclosure } from '@invoke-ai/ui-library';
+import { Collapse, Flex, Text, useDisclosure } from '@invoke-ai/ui-library';
import { EMPTY_ARRAY } from 'app/store/constants';
import { useAppSelector } from 'app/store/storeHooks';
import { fixTooltipCloseOnScrollStyles } from 'common/util/fixTooltipCloseOnScrollStyles';
@@ -7,50 +7,38 @@ import {
selectListBoardsQueryArgs,
selectSelectedBoardId,
} from 'features/gallery/store/gallerySelectors';
-import { selectAllowPrivateBoards } from 'features/system/store/configSelectors';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
-import { PiCaretDownBold } from 'react-icons/pi';
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
import AddBoardButton from './AddBoardButton';
import GalleryBoard from './GalleryBoard';
import NoBoardBoard from './NoBoardBoard';
-type Props = {
- isPrivate: boolean;
-};
-
-export const BoardsList = memo(({ isPrivate }: Props) => {
+export const BoardsList = memo(() => {
const { t } = useTranslation();
const selectedBoardId = useAppSelector(selectSelectedBoardId);
const boardSearchText = useAppSelector(selectBoardSearchText);
const queryArgs = useAppSelector(selectListBoardsQueryArgs);
const { data: boards } = useListAllBoardsQuery(queryArgs);
- const allowPrivateBoards = useAppSelector(selectAllowPrivateBoards);
- const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
+ const { isOpen } = useDisclosure({ defaultIsOpen: true });
const filteredBoards = useMemo(() => {
if (!boards) {
return EMPTY_ARRAY;
}
- return boards.filter((board) => {
- if (boardSearchText.length) {
- return board.is_private === isPrivate && board.board_name.toLowerCase().includes(boardSearchText.toLowerCase());
- } else {
- return board.is_private === isPrivate;
- }
- });
- }, [boardSearchText, boards, isPrivate]);
+ if (boardSearchText.length) {
+ return boards.filter((board) => board.board_name.toLowerCase().includes(boardSearchText.toLowerCase()));
+ }
+
+ return boards;
+ }, [boardSearchText, boards]);
const boardElements = useMemo(() => {
const elements = [];
- if (allowPrivateBoards && isPrivate && !boardSearchText.length) {
- elements.push();
- }
- if (!allowPrivateBoards && !boardSearchText.length) {
+ if (!boardSearchText.length) {
elements.push();
}
@@ -61,15 +49,7 @@ export const BoardsList = memo(({ isPrivate }: Props) => {
});
return elements;
- }, [allowPrivateBoards, isPrivate, boardSearchText.length, filteredBoards, selectedBoardId]);
-
- const boardListTitle = useMemo(() => {
- if (allowPrivateBoards) {
- return isPrivate ? t('boards.private') : t('boards.shared');
- } else {
- return t('boards.boards');
- }
- }, [isPrivate, allowPrivateBoards, t]);
+ }, [boardSearchText.length, filteredBoards, selectedBoardId]);
return (
@@ -84,26 +64,10 @@ export const BoardsList = memo(({ isPrivate }: Props) => {
top={0}
bg="base.900"
>
- {allowPrivateBoards ? (
-
- ) : (
-
- {boardListTitle}
-
- )}
-
+
+ {t('boards.boards')}
+
+
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsListWrapper.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsListWrapper.tsx
index 4b6c4030205..e7fa512d067 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsListWrapper.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsListWrapper.tsx
@@ -2,9 +2,7 @@ import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
import { autoScrollForExternal } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/external';
import { Box } from '@invoke-ai/ui-library';
-import { useAppSelector } from 'app/store/storeHooks';
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
-import { selectAllowPrivateBoards } from 'features/system/store/configSelectors';
import type { OverlayScrollbarsComponentRef } from 'overlayscrollbars-react';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import type { CSSProperties } from 'react';
@@ -18,7 +16,6 @@ const overlayScrollbarsStyles: CSSProperties = {
};
export const BoardsListWrapper = memo(() => {
- const allowPrivateBoards = useAppSelector(selectAllowPrivateBoards);
const [os, osRef] = useState(null);
useEffect(() => {
const osInstance = os?.osInstance();
@@ -48,8 +45,7 @@ export const BoardsListWrapper = memo(() => {
style={overlayScrollbarsStyles}
options={overlayScrollbarsParams.options}
>
- {allowPrivateBoards && }
-
+
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx
index 772606ec86f..1ddc4b0db36 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GalleryBoard.tsx
@@ -15,7 +15,6 @@ import {
selectSelectedBoardId,
} from 'features/gallery/store/gallerySelectors';
import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArchiveBold, PiImageSquare } from 'react-icons/pi';
@@ -37,7 +36,6 @@ const GalleryBoard = ({ board, isSelected }: GalleryBoardProps) => {
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
const autoAssignBoardOnClick = useAppSelector(selectAutoAssignBoardOnClick);
const selectedBoardId = useAppSelector(selectSelectedBoardId);
- const isVideoEnabled = useFeatureStatus('video');
const onClick = useCallback(() => {
if (selectedBoardId !== board.board_id) {
dispatch(boardIdSelected({ boardId: board.board_id }));
@@ -56,7 +54,6 @@ const GalleryBoard = ({ board, isSelected }: GalleryBoardProps) => {
() => ({
image_count: board.image_count,
asset_count: board.asset_count,
- video_count: board.video_count,
}),
[board]
);
@@ -95,8 +92,7 @@ const GalleryBoard = ({ board, isSelected }: GalleryBoardProps) => {
{board.archived && }
- {board.image_count} | {isVideoEnabled && `${board.video_count} | `}
- {board.asset_count}
+ {board.image_count} | {board.asset_count}
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx
index 22ecb71ae8e..900799f8ed7 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx
@@ -13,14 +13,9 @@ import {
selectBoardSearchText,
} from 'features/gallery/store/gallerySelectors';
import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
-import {
- useGetBoardAssetsTotalQuery,
- useGetBoardImagesTotalQuery,
- useGetBoardVideosTotalQuery,
-} from 'services/api/endpoints/boards';
+import { useGetBoardAssetsTotalQuery, useGetBoardImagesTotalQuery } from 'services/api/endpoints/boards';
import { useBoardName } from 'services/api/hooks/useBoardName';
interface Props {
@@ -33,7 +28,6 @@ const _hover: SystemStyleObject = {
const NoBoardBoard = memo(({ isSelected }: Props) => {
const dispatch = useAppDispatch();
- const isVideoEnabled = useFeatureStatus('video');
const { imagesTotal } = useGetBoardImagesTotalQuery('none', {
selectFromResult: ({ data }) => {
return { imagesTotal: data?.total ?? 0 };
@@ -44,12 +38,6 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
return { assetsTotal: data?.total ?? 0 };
},
});
- const { videoTotal } = useGetBoardVideosTotalQuery('none', {
- skip: !isVideoEnabled,
- selectFromResult: ({ data }) => {
- return { videoTotal: data?.total ?? 0 };
- },
- });
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
const autoAssignBoardOnClick = useAppSelector(selectAutoAssignBoardOnClick);
const boardSearchText = useAppSelector(selectBoardSearchText);
@@ -74,12 +62,7 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
{(ref) => (
- }
+ label={}
openDelay={1000}
placement="right"
closeOnScroll
@@ -120,8 +103,7 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
{autoAddBoardId === 'none' && }
- {imagesTotal} | {isVideoEnabled && `${videoTotal} | `}
- {assetsTotal}
+ {imagesTotal} | {assetsTotal}
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/NoBoardBoardContextMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/NoBoardBoardContextMenu.tsx
index d4c71580836..b77cfb05ea0 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/NoBoardBoardContextMenu.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/NoBoardBoardContextMenu.tsx
@@ -4,7 +4,6 @@ import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectAutoAddBoardId, selectAutoAssignBoardOnClick } from 'features/gallery/store/gallerySelectors';
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiDownloadBold, PiPlusBold, PiTrashSimpleBold } from 'react-icons/pi';
@@ -23,7 +22,6 @@ const NoBoardBoardContextMenu = ({ children }: Props) => {
const dispatch = useAppDispatch();
const autoAssignBoardOnClick = useAppSelector(selectAutoAssignBoardOnClick);
const isSelectedForAutoAdd = useAppSelector(selectIsSelectedForAutoAdd);
- const isBulkDownloadEnabled = useFeatureStatus('bulkDownload');
const [bulkDownload] = useBulkDownloadImagesMutation();
@@ -48,11 +46,9 @@ const NoBoardBoardContextMenu = ({ children }: Props) => {
{isSelectedForAutoAdd ? t('boards.selectedForAutoAdd') : t('boards.menuItemAutoAdd')}
)}
- {isBulkDownloadEnabled && (
- } onClickCapture={handleBulkDownload}>
- {t('boards.downloadBoard')}
-
- )}
+ } onClickCapture={handleBulkDownload}>
+ {t('boards.downloadBoard')}
+
}
@@ -68,7 +64,6 @@ const NoBoardBoardContextMenu = ({ children }: Props) => {
autoAssignBoardOnClick,
handleBulkDownload,
handleSetAutoAdd,
- isBulkDownloadEnabled,
isSelectedForAutoAdd,
t,
setUncategorizedImagesAsToBeDeleted,
diff --git a/invokeai/frontend/web/src/features/gallery/components/BoardsListPanelContent.tsx b/invokeai/frontend/web/src/features/gallery/components/BoardsListPanelContent.tsx
index f414df82f1e..fa93c597f3e 100644
--- a/invokeai/frontend/web/src/features/gallery/components/BoardsListPanelContent.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/BoardsListPanelContent.tsx
@@ -5,7 +5,6 @@ import { useDisclosure } from 'common/hooks/useBoolean';
import { BoardsListWrapper } from 'features/gallery/components/Boards/BoardsList/BoardsListWrapper';
import { BoardsSearch } from 'features/gallery/components/Boards/BoardsList/BoardsSearch';
import { BoardsSettingsPopover } from 'features/gallery/components/Boards/BoardsSettingsPopover';
-import { GalleryHeader } from 'features/gallery/components/GalleryHeader';
import { selectBoardSearchText } from 'features/gallery/store/gallerySelectors';
import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
import { useAutoLayoutContext } from 'features/ui/layouts/auto-layout-context';
@@ -62,9 +61,6 @@ export const BoardsPanel = memo(() => {
{t('boards.boards')}
-
-
-
{
const { t } = useTranslation();
const dispatch = useAppDispatch();
- const itemDTO = useItemDTOContext();
+ const imageDTO = useImageDTOContext();
const onClick = useCallback(() => {
- if (isImageDTO(itemDTO)) {
- dispatch(imagesToChangeSelected([itemDTO.image_name]));
- } else {
- dispatch(videosToChangeSelected([itemDTO.video_id]));
- }
+ dispatch(imagesToChangeSelected([imageDTO.image_name]));
dispatch(isModalOpenChanged(true));
- }, [dispatch, itemDTO]);
+ }, [dispatch, imageDTO]);
return (
} onClickCapture={onClick}>
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemCopy.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemCopy.tsx
index 35608d6ddee..1df70a4a429 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemCopy.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemCopy.tsx
@@ -1,23 +1,18 @@
import { IconMenuItem } from 'common/components/IconMenuItem';
import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
-import { useItemDTOContext } from 'features/gallery/contexts/ItemDTOContext';
+import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiCopyBold } from 'react-icons/pi';
-import { isImageDTO } from 'services/api/types';
export const ContextMenuItemCopy = memo(() => {
const { t } = useTranslation();
- const itemDTO = useItemDTOContext();
+ const imageDTO = useImageDTOContext();
const copyImageToClipboard = useCopyImageToClipboard();
const onClick = useCallback(() => {
- if (isImageDTO(itemDTO)) {
- copyImageToClipboard(itemDTO.image_url);
- } else {
- // copyVideoToClipboard(itemDTO.video_url);
- }
- }, [copyImageToClipboard, itemDTO]);
+ copyImageToClipboard(imageDTO.image_url);
+ }, [copyImageToClipboard, imageDTO]);
return (
{
const { t } = useTranslation();
const deleteImageModal = useDeleteImageModalApi();
- const itemDTO = useItemDTOContext();
+ const imageDTO = useImageDTOContext();
const onClick = useCallback(async () => {
try {
- if (isImageDTO(itemDTO)) {
- await deleteImageModal.delete([itemDTO.image_name]);
- }
+ await deleteImageModal.delete([imageDTO.image_name]);
} catch {
// noop;
}
- }, [deleteImageModal, itemDTO]);
+ }, [deleteImageModal, imageDTO]);
return (
{
- const { t } = useTranslation();
- const deleteVideoModal = useDeleteVideoModalApi();
- const itemDTO = useItemDTOContext();
-
- const onClick = useCallback(async () => {
- try {
- if (isVideoDTO(itemDTO)) {
- await deleteVideoModal.delete([itemDTO.video_id]);
- }
- } catch {
- // noop;
- }
- }, [deleteVideoModal, itemDTO]);
-
- return (
- }
- onClickCapture={onClick}
- aria-label={t('gallery.deleteVideo', { count: 1 })}
- tooltip={t('gallery.deleteVideo', { count: 1 })}
- isDestructive
- />
- );
-});
-
-ContextMenuItemDeleteVideo.displayName = 'ContextMenuItemDeleteVideo';
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemDownload.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemDownload.tsx
index b53ba238910..723d806f484 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemDownload.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemDownload.tsx
@@ -1,23 +1,18 @@
import { IconMenuItem } from 'common/components/IconMenuItem';
import { useDownloadItem } from 'common/hooks/useDownloadImage';
-import { useItemDTOContext } from 'features/gallery/contexts/ItemDTOContext';
+import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiDownloadSimpleBold } from 'react-icons/pi';
-import { isImageDTO } from 'services/api/types';
export const ContextMenuItemDownload = memo(() => {
const { t } = useTranslation();
- const itemDTO = useItemDTOContext();
+ const imageDTO = useImageDTOContext();
const { downloadItem } = useDownloadItem();
const onClick = useCallback(() => {
- if (isImageDTO(itemDTO)) {
- downloadItem(itemDTO.image_url, itemDTO.image_name);
- } else {
- downloadItem(itemDTO.video_url, itemDTO.video_id);
- }
- }, [downloadItem, itemDTO]);
+ downloadItem(imageDTO.image_url, imageDTO.image_name);
+ }, [downloadItem, imageDTO]);
return (
{
const { t } = useTranslation();
- const itemDTO = useItemDTOContext();
+ const imageDTO = useImageDTOContext();
const loadWorkflowWithDialog = useLoadWorkflowWithDialog();
const hasTemplates = useStore($hasTemplates);
const onClick = useCallback(() => {
- if (isImageDTO(itemDTO)) {
- loadWorkflowWithDialog({ type: 'image', data: itemDTO.image_name });
- } else {
- // loadWorkflowWithDialog({ type: 'video', data: itemDTO.video_id });
- }
- }, [loadWorkflowWithDialog, itemDTO]);
+ loadWorkflowWithDialog({ type: 'image', data: imageDTO.image_name });
+ }, [loadWorkflowWithDialog, imageDTO]);
const isDisabled = useMemo(() => {
- if (isImageDTO(itemDTO)) {
- return !itemDTO.has_workflow || !hasTemplates;
- }
- return false;
- }, [itemDTO, hasTemplates]);
+ return !imageDTO.has_workflow || !hasTemplates;
+ }, [imageDTO, hasTemplates]);
return (
} onClickCapture={onClick} isDisabled={isDisabled}>
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemLocateInGalery.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemLocateInGalery.tsx
index 8055e592cfe..b5342d50f3b 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemLocateInGalery.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemLocateInGalery.tsx
@@ -1,6 +1,6 @@
import { MenuItem } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { useItemDTOContext } from 'features/gallery/contexts/ItemDTOContext';
+import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
import { IMAGE_CATEGORIES } from 'features/gallery/store/types';
import { navigationApi } from 'features/ui/layouts/navigation-api';
@@ -10,48 +10,33 @@ import { memo, useCallback, useMemo } from 'react';
import { flushSync } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { PiCrosshairBold } from 'react-icons/pi';
-import { isImageDTO } from 'services/api/types';
export const ContextMenuItemLocateInGalery = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
- const itemDTO = useItemDTOContext();
+ const imageDTO = useImageDTOContext();
const activeTab = useAppSelector(selectActiveTab);
const galleryPanel = useGalleryPanel(activeTab);
const isGalleryImage = useMemo(() => {
- return !itemDTO.is_intermediate;
- }, [itemDTO]);
+ return !imageDTO.is_intermediate;
+ }, [imageDTO]);
const onClick = useCallback(() => {
navigationApi.expandRightPanel();
galleryPanel.expand();
- if (isImageDTO(itemDTO)) {
flushSync(() => {
dispatch(
boardIdSelected({
- boardId: itemDTO.board_id ?? 'none',
+ boardId: imageDTO.board_id ?? 'none',
select: {
- selection: [{ type: 'image', id: itemDTO.image_name }],
- galleryView: IMAGE_CATEGORIES.includes(itemDTO.image_category) ? 'images' : 'assets',
+ selection: [{ type: 'image', id: imageDTO.image_name }],
+ galleryView: IMAGE_CATEGORIES.includes(imageDTO.image_category) ? 'images' : 'assets',
},
})
);
});
- } else {
- flushSync(() => {
- dispatch(
- boardIdSelected({
- boardId: itemDTO.board_id ?? 'none',
- select: {
- selection: [{ type: 'video', id: itemDTO.video_id }],
- galleryView: 'videos',
- },
- })
- );
- });
- }
- }, [dispatch, galleryPanel, itemDTO]);
+ }, [dispatch, galleryPanel, imageDTO]);
return (
} onClickCapture={onClick} isDisabled={!isGalleryImage}>
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemMetadataRecallActionsCanvasGenerateTabs.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemMetadataRecallActionsCanvasGenerateTabs.tsx
index b4c30ce3dc7..1965b2d698f 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemMetadataRecallActionsCanvasGenerateTabs.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemMetadataRecallActionsCanvasGenerateTabs.tsx
@@ -1,6 +1,6 @@
import { Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library';
import { SubMenuButtonContent, useSubMenu } from 'common/hooks/useSubMenu';
-import { useItemDTOContext } from 'features/gallery/contexts/ItemDTOContext';
+import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { useRecallAll } from 'features/gallery/hooks/useRecallAllImageMetadata';
import { useRecallCLIPSkip } from 'features/gallery/hooks/useRecallCLIPSkip';
import { useRecallDimensions } from 'features/gallery/hooks/useRecallDimensions';
@@ -17,21 +17,19 @@ import {
PiQuotesBold,
PiRulerBold,
} from 'react-icons/pi';
-import type { ImageDTO } from 'services/api/types';
export const ContextMenuItemMetadataRecallActionsCanvasGenerateTabs = memo(() => {
const { t } = useTranslation();
const subMenu = useSubMenu();
- const itemDTO = useItemDTOContext();
+ const imageDTO = useImageDTOContext();
- // TODO: Implement video recall metadata actions
- const recallAll = useRecallAll(itemDTO as ImageDTO);
- const recallRemix = useRecallRemix(itemDTO as ImageDTO);
- const recallPrompts = useRecallPrompts(itemDTO as ImageDTO);
- const recallSeed = useRecallSeed(itemDTO as ImageDTO);
- const recallDimensions = useRecallDimensions(itemDTO as ImageDTO);
- const recallCLIPSkip = useRecallCLIPSkip(itemDTO as ImageDTO);
+ const recallAll = useRecallAll(imageDTO );
+ const recallRemix = useRecallRemix(imageDTO );
+ const recallPrompts = useRecallPrompts(imageDTO );
+ const recallSeed = useRecallSeed(imageDTO );
+ const recallDimensions = useRecallDimensions(imageDTO );
+ const recallCLIPSkip = useRecallCLIPSkip(imageDTO );
return (
}>
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemMetadataRecallActionsUpscaleTab.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemMetadataRecallActionsUpscaleTab.tsx
index d3511e29e0b..d4aa0a4296b 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemMetadataRecallActionsUpscaleTab.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemMetadataRecallActionsUpscaleTab.tsx
@@ -1,22 +1,20 @@
import { Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library';
import { SubMenuButtonContent, useSubMenu } from 'common/hooks/useSubMenu';
-import { useItemDTOContext } from 'features/gallery/contexts/ItemDTOContext';
+import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { useRecallPrompts } from 'features/gallery/hooks/useRecallPrompts';
import { useRecallSeed } from 'features/gallery/hooks/useRecallSeed';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArrowBendUpLeftBold, PiPlantBold, PiQuotesBold } from 'react-icons/pi';
-import type { ImageDTO } from 'services/api/types';
export const ContextMenuItemMetadataRecallActionsUpscaleTab = memo(() => {
const { t } = useTranslation();
const subMenu = useSubMenu();
- const itemDTO = useItemDTOContext();
+ const imageDTO = useImageDTOContext();
- // TODO: Implement video recall metadata actions
- const recallPrompts = useRecallPrompts(itemDTO as ImageDTO);
- const recallSeed = useRecallSeed(itemDTO as ImageDTO);
+ const recallPrompts = useRecallPrompts(imageDTO);
+ const recallSeed = useRecallSeed(imageDTO);
return (
}>
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemNewCanvasFromImageSubMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemNewCanvasFromImageSubMenu.tsx
index b40525ae474..4aa8f9bb3e4 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemNewCanvasFromImageSubMenu.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemNewCanvasFromImageSubMenu.tsx
@@ -3,7 +3,7 @@ import { useAppStore } from 'app/store/storeHooks';
import { SubMenuButtonContent, useSubMenu } from 'common/hooks/useSubMenu';
import { useCanvasIsBusySafe } from 'features/controlLayers/hooks/useCanvasIsBusy';
import { useCanvasIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
-import { useItemDTOContextImageOnly } from 'features/gallery/contexts/ItemDTOContext';
+import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { newCanvasFromImage } from 'features/imageActions/actions';
import { toast } from 'features/toast/toast';
import { navigationApi } from 'features/ui/layouts/navigation-api';
@@ -16,7 +16,7 @@ export const ContextMenuItemNewCanvasFromImageSubMenu = memo(() => {
const { t } = useTranslation();
const subMenu = useSubMenu();
const store = useAppStore();
- const imageDTO = useItemDTOContextImageOnly();
+ const imageDTO = useImageDTOContext();
const isBusy = useCanvasIsBusySafe();
const isStaging = useCanvasIsStaging();
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemNewLayerFromImageSubMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemNewLayerFromImageSubMenu.tsx
index 710a381d937..0bc680c7fee 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemNewLayerFromImageSubMenu.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemNewLayerFromImageSubMenu.tsx
@@ -3,8 +3,7 @@ import { useAppStore } from 'app/store/storeHooks';
import { SubMenuButtonContent, useSubMenu } from 'common/hooks/useSubMenu';
import { NewLayerIcon } from 'features/controlLayers/components/common/icons';
import { useCanvasIsBusySafe } from 'features/controlLayers/hooks/useCanvasIsBusy';
-import { useItemDTOContextImageOnly } from 'features/gallery/contexts/ItemDTOContext';
-import { sentImageToCanvas } from 'features/gallery/store/actions';
+import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { createNewCanvasEntityFromImage } from 'features/imageActions/actions';
import { toast } from 'features/toast/toast';
import { navigationApi } from 'features/ui/layouts/navigation-api';
@@ -17,14 +16,13 @@ export const ContextMenuItemNewLayerFromImageSubMenu = memo(() => {
const { t } = useTranslation();
const subMenu = useSubMenu();
const store = useAppStore();
- const imageDTO = useItemDTOContextImageOnly();
+ const imageDTO = useImageDTOContext();
const isBusy = useCanvasIsBusySafe();
const onClickNewRasterLayerFromImage = useCallback(async () => {
const { dispatch, getState } = store;
await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID);
createNewCanvasEntityFromImage({ imageDTO, type: 'raster_layer', dispatch, getState });
- dispatch(sentImageToCanvas());
toast({
id: 'SENT_TO_CANVAS',
title: t('toast.sentToCanvas'),
@@ -36,7 +34,6 @@ export const ContextMenuItemNewLayerFromImageSubMenu = memo(() => {
const { dispatch, getState } = store;
await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID);
createNewCanvasEntityFromImage({ imageDTO, type: 'control_layer', dispatch, getState });
- dispatch(sentImageToCanvas());
toast({
id: 'SENT_TO_CANVAS',
title: t('toast.sentToCanvas'),
@@ -48,7 +45,6 @@ export const ContextMenuItemNewLayerFromImageSubMenu = memo(() => {
const { dispatch, getState } = store;
await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID);
createNewCanvasEntityFromImage({ imageDTO, type: 'inpaint_mask', dispatch, getState });
- dispatch(sentImageToCanvas());
toast({
id: 'SENT_TO_CANVAS',
title: t('toast.sentToCanvas'),
@@ -60,7 +56,6 @@ export const ContextMenuItemNewLayerFromImageSubMenu = memo(() => {
const { dispatch, getState } = store;
await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID);
createNewCanvasEntityFromImage({ imageDTO, type: 'regional_guidance', dispatch, getState });
- dispatch(sentImageToCanvas());
toast({
id: 'SENT_TO_CANVAS',
title: t('toast.sentToCanvas'),
@@ -72,7 +67,6 @@ export const ContextMenuItemNewLayerFromImageSubMenu = memo(() => {
const { dispatch, getState } = store;
await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID);
createNewCanvasEntityFromImage({ imageDTO, type: 'regional_guidance_with_reference_image', dispatch, getState });
- dispatch(sentImageToCanvas());
toast({
id: 'SENT_TO_CANVAS',
title: t('toast.sentToCanvas'),
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemOpenInNewTab.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemOpenInNewTab.tsx
index e2b9f42363b..80e99fbfb7a 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemOpenInNewTab.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemOpenInNewTab.tsx
@@ -1,24 +1,15 @@
-import { useAppDispatch } from 'app/store/storeHooks';
import { IconMenuItem } from 'common/components/IconMenuItem';
-import { useItemDTOContext } from 'features/gallery/contexts/ItemDTOContext';
-import { imageOpenedInNewTab } from 'features/gallery/store/actions';
+import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArrowSquareOutBold } from 'react-icons/pi';
-import { isImageDTO } from 'services/api/types';
export const ContextMenuItemOpenInNewTab = memo(() => {
const { t } = useTranslation();
- const itemDTO = useItemDTOContext();
- const dispatch = useAppDispatch();
+ const imageDTO = useImageDTOContext();
const onClick = useCallback(() => {
- if (isImageDTO(itemDTO)) {
- window.open(itemDTO.image_url, '_blank');
- dispatch(imageOpenedInNewTab());
- } else {
- window.open(itemDTO.video_url, '_blank');
- }
- }, [itemDTO, dispatch]);
+ window.open(imageDTO.image_url, '_blank');
+ }, [imageDTO]);
return (
{
const dispatch = useAppDispatch();
const { t } = useTranslation();
- const itemDTO = useItemDTOContext();
+ const imageDTO = useImageDTOContext();
const onClick = useCallback(() => {
- if (isImageDTO(itemDTO)) {
- dispatch(imageToCompareChanged(null));
- dispatch(itemSelected({ type: 'image', id: itemDTO.image_name }));
- navigationApi.focusPanelInActiveTab(VIEWER_PANEL_ID);
- } else {
- // TODO: Implement video open in viewer
- }
- }, [dispatch, itemDTO]);
+ dispatch(imageToCompareChanged(null));
+ dispatch(itemSelected({ type: 'image', id: imageDTO.image_name }));
+ navigationApi.focusPanelInActiveTab(VIEWER_PANEL_ID);
+ }, [dispatch, imageDTO]);
return (
{
const { t } = useTranslation();
const dispatch = useAppDispatch();
- const itemDTO = useItemDTOContext();
+ const imageDTO = useImageDTOContext();
const selectMaySelectForCompare = useMemo(
() =>
createSelector(selectGallerySlice, (gallery) => {
- if (isImageDTO(itemDTO)) {
- return gallery.imageToCompare !== itemDTO.image_name;
- }
- return false;
+ return gallery.imageToCompare !== imageDTO.image_name;
}),
- [itemDTO]
+ [imageDTO]
);
const maySelectForCompare = useAppSelector(selectMaySelectForCompare);
const onClick = useCallback(() => {
- if (isImageDTO(itemDTO)) {
- dispatch(imageToCompareChanged(itemDTO.image_name));
- } else {
- // TODO: Implement video select for compare
- }
- }, [dispatch, itemDTO]);
+ dispatch(imageToCompareChanged(imageDTO.image_name));
+ }, [dispatch, imageDTO]);
return (
{
const { t } = useTranslation();
const dispatch = useAppDispatch();
- const imageDTO = useItemDTOContextImageOnly();
+ const imageDTO = useImageDTOContext();
const handleSendToCanvas = useCallback(() => {
dispatch(upscaleInitialImageChanged(imageDTOToImageWithDims(imageDTO)));
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemSendToVideo.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemSendToVideo.tsx
deleted file mode 100644
index b59f0addd7d..00000000000
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemSendToVideo.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { MenuItem } from '@invoke-ai/ui-library';
-import { imageDTOToCroppableImage } from 'features/controlLayers/store/util';
-import { useItemDTOContextImageOnly } from 'features/gallery/contexts/ItemDTOContext';
-import { startingFrameImageChanged } from 'features/parameters/store/videoSlice';
-import { navigationApi } from 'features/ui/layouts/navigation-api';
-import { memo, useCallback } from 'react';
-import { useTranslation } from 'react-i18next';
-import { PiVideoBold } from 'react-icons/pi';
-import { useDispatch } from 'react-redux';
-
-export const ContextMenuItemSendToVideo = memo(() => {
- const { t } = useTranslation();
- const imageDTO = useItemDTOContextImageOnly();
- const dispatch = useDispatch();
-
- const onClick = useCallback(() => {
- dispatch(startingFrameImageChanged(imageDTOToCroppableImage(imageDTO)));
- navigationApi.switchToTab('video');
- }, [imageDTO, dispatch]);
-
- return (
- } onClickCapture={onClick} aria-label={t('parameters.sendToVideo')}>
- {t('parameters.sendToVideo')}
-
- );
-});
-
-ContextMenuItemSendToVideo.displayName = 'ContextMenuItemSendToVideo';
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemStarUnstar.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemStarUnstar.tsx
index 4828fb5e9ce..b6e465db463 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemStarUnstar.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemStarUnstar.tsx
@@ -1,50 +1,35 @@
import { MenuItem } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { $customStarUI } from 'app/store/nanostores/customStarUI';
-import { useItemDTOContext } from 'features/gallery/contexts/ItemDTOContext';
+import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiStarBold, PiStarFill } from 'react-icons/pi';
import { useStarImagesMutation, useUnstarImagesMutation } from 'services/api/endpoints/images';
-import { useStarVideosMutation, useUnstarVideosMutation } from 'services/api/endpoints/videos';
-import { isImageDTO, isVideoDTO } from 'services/api/types';
export const ContextMenuItemStarUnstar = memo(() => {
const { t } = useTranslation();
- const itemDTO = useItemDTOContext();
- const customStarUi = useStore($customStarUI);
+ const imageDTO = useImageDTOContext();
const [starImages] = useStarImagesMutation();
const [unstarImages] = useUnstarImagesMutation();
- const [starVideos] = useStarVideosMutation();
- const [unstarVideos] = useUnstarVideosMutation();
const starImage = useCallback(() => {
- if (isImageDTO(itemDTO)) {
- starImages({ image_names: [itemDTO.image_name] });
- } else if (isVideoDTO(itemDTO)) {
- starVideos({ video_ids: [itemDTO.video_id] });
- }
- }, [starImages, itemDTO, starVideos]);
+ starImages({ image_names: [imageDTO.image_name] });
+ }, [starImages, imageDTO]);
const unstarImage = useCallback(() => {
- if (isImageDTO(itemDTO)) {
- unstarImages({ image_names: [itemDTO.image_name] });
- } else if (isVideoDTO(itemDTO)) {
- unstarVideos({ video_ids: [itemDTO.video_id] });
- }
- }, [unstarImages, itemDTO, unstarVideos]);
+ unstarImages({ image_names: [imageDTO.image_name] });
+ }, [unstarImages, imageDTO]);
- if (itemDTO.starred) {
+ if (imageDTO.starred) {
return (
- } onClickCapture={unstarImage}>
- {customStarUi ? customStarUi.off.text : t('gallery.unstarImage')}
+ } onClickCapture={unstarImage}>
+ {t('gallery.unstarImage')}
);
}
return (
- } onClickCapture={starImage}>
- {customStarUi ? customStarUi.on.text : t('gallery.starImage')}
+ } onClickCapture={starImage}>
+ {t('gallery.starImage')}
);
});
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemUseAsPromptTemplate.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemUseAsPromptTemplate.tsx
index ae526243fe5..188be4c307d 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemUseAsPromptTemplate.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemUseAsPromptTemplate.tsx
@@ -1,5 +1,5 @@
import { MenuItem } from '@invoke-ai/ui-library';
-import { useItemDTOContextImageOnly } from 'features/gallery/contexts/ItemDTOContext';
+import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { useCreateStylePresetFromMetadata } from 'features/gallery/hooks/useCreateStylePresetFromMetadata';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -7,7 +7,7 @@ import { PiPaintBrushBold } from 'react-icons/pi';
export const ContextMenuItemUseAsPromptTemplate = memo(() => {
const { t } = useTranslation();
- const imageDTO = useItemDTOContextImageOnly();
+ const imageDTO = useImageDTOContext();
const stylePreset = useCreateStylePresetFromMetadata(imageDTO);
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemUseAsRefImage.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemUseAsRefImage.tsx
index 41505ae81d5..918d2850342 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemUseAsRefImage.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemUseAsRefImage.tsx
@@ -3,7 +3,7 @@ import { useAppStore } from 'app/store/storeHooks';
import { getDefaultRefImageConfig } from 'features/controlLayers/hooks/addLayerHooks';
import { refImageAdded } from 'features/controlLayers/store/refImagesSlice';
import { imageDTOToCroppableImage } from 'features/controlLayers/store/util';
-import { useItemDTOContextImageOnly } from 'features/gallery/contexts/ItemDTOContext';
+import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { toast } from 'features/toast/toast';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@@ -12,7 +12,7 @@ import { PiImageBold } from 'react-icons/pi';
export const ContextMenuItemUseAsRefImage = memo(() => {
const { t } = useTranslation();
const store = useAppStore();
- const imageDTO = useItemDTOContextImageOnly();
+ const imageDTO = useImageDTOContext();
const onClickNewGlobalReferenceImageFromImage = useCallback(() => {
const { dispatch, getState } = store;
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemUseForPromptGeneration.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemUseForPromptGeneration.tsx
deleted file mode 100644
index 12cbb22f9c9..00000000000
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemUseForPromptGeneration.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { MenuItem } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { useAppSelector, useAppStore } from 'app/store/storeHooks';
-import { useItemDTOContextImageOnly } from 'features/gallery/contexts/ItemDTOContext';
-import { expandPrompt } from 'features/prompt/PromptExpansion/expand';
-import { promptExpansionApi } from 'features/prompt/PromptExpansion/state';
-import { selectAllowPromptExpansion } from 'features/system/store/configSlice';
-import { toast } from 'features/toast/toast';
-import { memo, useCallback } from 'react';
-import { useTranslation } from 'react-i18next';
-import { PiTextTBold } from 'react-icons/pi';
-
-export const ContextMenuItemUseForPromptGeneration = memo(() => {
- const { t } = useTranslation();
- const { dispatch, getState } = useAppStore();
- const imageDTO = useItemDTOContextImageOnly();
- const { isPending } = useStore(promptExpansionApi.$state);
- const isPromptExpansionEnabled = useAppSelector(selectAllowPromptExpansion);
-
- const handleUseForPromptGeneration = useCallback(() => {
- promptExpansionApi.setPending(imageDTO);
- expandPrompt({ dispatch, getState, imageDTO });
- toast({
- id: 'PROMPT_GENERATION_STARTED',
- title: t('toast.promptGenerationStarted'),
- status: 'info',
- });
- }, [dispatch, getState, imageDTO, t]);
-
- if (!isPromptExpansionEnabled) {
- return null;
- }
-
- return (
- }
- onClickCapture={handleUseForPromptGeneration}
- id="use-for-prompt-generation"
- isDisabled={isPending}
- >
- {t('gallery.useForPromptGeneration')}
-
- );
-});
-
-ContextMenuItemUseForPromptGeneration.displayName = 'ContextMenuItemUseForPromptGeneration';
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MultipleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MultipleSelectionMenuItems.tsx
index ee21261cb31..0e086ad5e4e 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MultipleSelectionMenuItems.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MultipleSelectionMenuItems.tsx
@@ -1,10 +1,7 @@
import { MenuDivider, MenuItem } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { $customStarUI } from 'app/store/nanostores/customStarUI';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { imagesToChangeSelected, isModalOpenChanged } from 'features/changeBoardModal/store/slice';
import { useDeleteImageModalApi } from 'features/deleteImageModal/store/state';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiDownloadSimpleBold, PiFoldersBold, PiStarBold, PiStarFill, PiTrashSimpleBold } from 'react-icons/pi';
@@ -18,11 +15,8 @@ const MultipleSelectionMenuItems = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const selection = useAppSelector((s) => s.gallery.selection);
- const customStarUi = useStore($customStarUI);
const deleteImageModal = useDeleteImageModalApi();
- const isBulkDownloadEnabled = useFeatureStatus('bulkDownload');
-
const [starImages] = useStarImagesMutation();
const [unstarImages] = useUnstarImagesMutation();
const [bulkDownload] = useBulkDownloadImagesMutation();
@@ -50,17 +44,15 @@ const MultipleSelectionMenuItems = () => {
return (
<>
- } onClickCapture={handleUnstarSelection}>
- {customStarUi ? customStarUi.off.text : `Unstar All`}
+ } onClickCapture={handleUnstarSelection}>
+ Unstar All
+
+ } onClickCapture={handleStarSelection}>
+ Star All
- } onClickCapture={handleStarSelection}>
- {customStarUi ? customStarUi.on.text : `Star All`}
+ } onClickCapture={handleBulkDownload}>
+ {t('gallery.downloadSelection')}
- {isBulkDownloadEnabled && (
- } onClickCapture={handleBulkDownload}>
- {t('gallery.downloadSelection')}
-
- )}
} onClickCapture={handleChangeBoard}>
{t('boards.changeBoard')}
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MultipleSelectionVideoMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MultipleSelectionVideoMenuItems.tsx
deleted file mode 100644
index 47edf37d3ff..00000000000
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MultipleSelectionVideoMenuItems.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { MenuDivider, MenuItem } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { $customStarUI } from 'app/store/nanostores/customStarUI';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { isModalOpenChanged, videosToChangeSelected } from 'features/changeBoardModal/store/slice';
-import { memo, useCallback } from 'react';
-import { useTranslation } from 'react-i18next';
-import { PiFoldersBold, PiStarBold, PiStarFill, PiTrashSimpleBold } from 'react-icons/pi';
-import { useDeleteVideosMutation, useStarVideosMutation, useUnstarVideosMutation } from 'services/api/endpoints/videos';
-
-const MultipleSelectionMenuItems = () => {
- const { t } = useTranslation();
- const dispatch = useAppDispatch();
- const selection = useAppSelector((s) => s.gallery.selection);
- const customStarUi = useStore($customStarUI);
-
- const [starVideos] = useStarVideosMutation();
- const [unstarVideos] = useUnstarVideosMutation();
- const [deleteVideos] = useDeleteVideosMutation();
-
- const handleChangeBoard = useCallback(() => {
- dispatch(videosToChangeSelected(selection.map((s) => s.id)));
- dispatch(isModalOpenChanged(true));
- }, [dispatch, selection]);
-
- const handleDeleteSelection = useCallback(() => {
- // TODO: Add confirm on delete and video usage functionality
- deleteVideos({ video_ids: selection.map((s) => s.id) });
- }, [deleteVideos, selection]);
-
- const handleStarSelection = useCallback(() => {
- starVideos({ video_ids: selection.map((s) => s.id) });
- }, [starVideos, selection]);
-
- const handleUnstarSelection = useCallback(() => {
- unstarVideos({ video_ids: selection.map((s) => s.id) });
- }, [unstarVideos, selection]);
-
- return (
- <>
- } onClickCapture={handleUnstarSelection}>
- {customStarUi ? customStarUi.off.text : `Unstar All`}
-
- } onClickCapture={handleStarSelection}>
- {customStarUi ? customStarUi.on.text : `Star All`}
-
- } onClickCapture={handleChangeBoard}>
- {t('boards.changeBoard')}
-
-
- } onClickCapture={handleDeleteSelection}>
- {t('gallery.deleteSelection')}
-
- >
- );
-};
-
-export default memo(MultipleSelectionMenuItems);
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/SingleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/SingleSelectionMenuItems.tsx
index 3556e1a9d48..9d64395a8c5 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/SingleSelectionMenuItems.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/SingleSelectionMenuItems.tsx
@@ -13,13 +13,10 @@ import { ContextMenuItemOpenInNewTab } from 'features/gallery/components/Context
import { ContextMenuItemOpenInViewer } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemOpenInViewer';
import { ContextMenuItemSelectForCompare } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemSelectForCompare';
import { ContextMenuItemSendToUpscale } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemSendToUpscale';
-import { ContextMenuItemSendToVideo } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemSendToVideo';
import { ContextMenuItemStarUnstar } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemStarUnstar';
import { ContextMenuItemUseAsPromptTemplate } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemUseAsPromptTemplate';
import { ContextMenuItemUseAsRefImage } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemUseAsRefImage';
-import { ContextMenuItemUseForPromptGeneration } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemUseForPromptGeneration';
-import { ItemDTOContextProvider } from 'features/gallery/contexts/ItemDTOContext';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
+import { ImageDTOContextProvider } from 'features/gallery/contexts/ImageDTOContext';
import { selectActiveTab } from 'features/ui/store/uiSelectors';
import type { ImageDTO } from 'services/api/types';
@@ -32,10 +29,9 @@ type SingleSelectionMenuItemsProps = {
const SingleSelectionMenuItems = ({ imageDTO }: SingleSelectionMenuItemsProps) => {
const tab = useAppSelector(selectActiveTab);
- const isVideoEnabled = useFeatureStatus('video');
return (
-
+
@@ -50,8 +46,6 @@ const SingleSelectionMenuItems = ({ imageDTO }: SingleSelectionMenuItemsProps) =
{tab === 'upscaling' && }
- {isVideoEnabled && }
-
{(tab === 'canvas' || tab === 'generate') && }
@@ -64,7 +58,7 @@ const SingleSelectionMenuItems = ({ imageDTO }: SingleSelectionMenuItemsProps) =
// Only render this button on tabs with a gallery.
)}
-
+
);
};
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/SingleSelectionVideoMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/SingleSelectionVideoMenuItems.tsx
deleted file mode 100644
index d91fe886560..00000000000
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/SingleSelectionVideoMenuItems.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { MenuDivider } from '@invoke-ai/ui-library';
-import { IconMenuItemGroup } from 'common/components/IconMenuItem';
-import { ContextMenuItemChangeBoard } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemChangeBoard';
-import { ContextMenuItemDownload } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemDownload';
-import { ContextMenuItemOpenInNewTab } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemOpenInNewTab';
-import { ContextMenuItemOpenInViewer } from 'features/gallery/components/ContextMenu/MenuItems/ContextMenuItemOpenInViewer';
-import { ItemDTOContextProvider } from 'features/gallery/contexts/ItemDTOContext';
-import type { VideoDTO } from 'services/api/types';
-
-import { ContextMenuItemDeleteVideo } from './MenuItems/ContextMenuItemDeleteVideo';
-import { ContextMenuItemStarUnstar } from './MenuItems/ContextMenuItemStarUnstar';
-
-type SingleSelectionVideoMenuItemsProps = {
- videoDTO: VideoDTO;
-};
-
-const SingleSelectionVideoMenuItems = ({ videoDTO }: SingleSelectionVideoMenuItemsProps) => {
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default SingleSelectionVideoMenuItems;
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/VideoContextMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/VideoContextMenu.tsx
deleted file mode 100644
index 533a3d38b9a..00000000000
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/VideoContextMenu.tsx
+++ /dev/null
@@ -1,279 +0,0 @@
-import type { ChakraProps } from '@invoke-ai/ui-library';
-import { Menu, MenuButton, MenuList, Portal, useGlobalMenuClose } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { useAppSelector } from 'app/store/storeHooks';
-import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
-import MultipleSelectionVideoMenuItems from 'features/gallery/components/ContextMenu/MultipleSelectionVideoMenuItems';
-import SingleSelectionVideoMenuItems from 'features/gallery/components/ContextMenu/SingleSelectionVideoMenuItems';
-import { selectSelectionCount } from 'features/gallery/store/gallerySelectors';
-import { map } from 'nanostores';
-import type { RefObject } from 'react';
-import { memo, useCallback, useEffect, useRef } from 'react';
-import type { VideoDTO } from 'services/api/types';
-
-/**
- * The delay in milliseconds before the context menu opens on long press.
- */
-const LONGPRESS_DELAY_MS = 500;
-/**
- * The threshold in pixels that the pointer must move before the long press is cancelled.
- */
-const LONGPRESS_MOVE_THRESHOLD_PX = 10;
-
-/**
- * The singleton state of the context menu.
- */
-const $videoContextMenuState = map<{
- isOpen: boolean;
- videoDTO: VideoDTO | null;
- position: { x: number; y: number };
-}>({
- isOpen: false,
- videoDTO: null,
- position: { x: -1, y: -1 },
-});
-
-/**
- * Convenience function to close the context menu.
- */
-const onClose = () => {
- $videoContextMenuState.setKey('isOpen', false);
-};
-
-/**
- * Map of elements to image DTOs. This is used to determine which image DTO to show the context menu for, depending on
- * the target of the context menu or long press event.
- */
-const elToVideoMap = new Map();
-
-/**
- * Given a target node, find the first registered parent element that contains the target node and return the imageDTO
- * associated with it.
- */
-const getVideoDTOFromMap = (target: Node): VideoDTO | undefined => {
- const entry = Array.from(elToVideoMap.entries()).find((entry) => entry[0].contains(target));
- return entry?.[1];
-};
-
-/**
- * Register a context menu for an image DTO on a target element.
- * @param imageDTO The image DTO to register the context menu for.
- * @param targetRef The ref of the target element that should trigger the context menu.
- */
-export const useVideoContextMenu = (videoDTO: VideoDTO, ref: RefObject | (HTMLElement | null)) => {
- useEffect(() => {
- if (ref === null) {
- return;
- }
- const el = ref instanceof HTMLElement ? ref : ref.current;
- if (!el) {
- return;
- }
- elToVideoMap.set(el, videoDTO);
- return () => {
- elToVideoMap.delete(el);
- };
- }, [videoDTO, ref]);
-};
-
-/**
- * Singleton component that renders the context menu for images.
- */
-export const VideoContextMenu = memo(() => {
- useAssertSingleton('VideoContextMenu');
- const state = useStore($videoContextMenuState);
- useGlobalMenuClose(onClose);
-
- return (
-
-
-
-
- );
-});
-
-VideoContextMenu.displayName = 'VideoContextMenu';
-
-const _hover: ChakraProps['_hover'] = { bg: 'transparent' };
-
-/**
- * A logical component that listens for context menu events and opens the context menu. It's separate from
- * ImageContextMenu component to avoid re-rendering the whole context menu on every context menu event.
- */
-const VideoContextMenuEventLogical = memo(() => {
- const lastPositionRef = useRef<{ x: number; y: number }>({ x: -1, y: -1 });
- const longPressTimeoutRef = useRef(0);
- const animationTimeoutRef = useRef(0);
-
- const onContextMenu = useCallback((e: MouseEvent | PointerEvent) => {
- if (e.shiftKey) {
- // This is a shift + right click event, which should open the native context menu
- onClose();
- return;
- }
-
- const videoDTO = getVideoDTOFromMap(e.target as Node);
-
- if (!videoDTO) {
- // Can't find the image DTO, close the context menu
- onClose();
- return;
- }
-
- // clear pending delayed open
- window.clearTimeout(animationTimeoutRef.current);
- e.preventDefault();
-
- if (lastPositionRef.current.x !== e.pageX || lastPositionRef.current.y !== e.pageY) {
- // if the mouse moved, we need to close, wait for animation and reopen the menu at the new position
- if ($videoContextMenuState.get().isOpen) {
- onClose();
- }
- animationTimeoutRef.current = window.setTimeout(() => {
- // Open the menu after the animation with the new state
- $videoContextMenuState.set({
- isOpen: true,
- position: { x: e.pageX, y: e.pageY },
- videoDTO,
- });
- }, 100);
- } else {
- // else we can just open the menu at the current position w/ new state
- $videoContextMenuState.set({
- isOpen: true,
- position: { x: e.pageX, y: e.pageY },
- videoDTO,
- });
- }
-
- // Always sync the last position
- lastPositionRef.current = { x: e.pageX, y: e.pageY };
- }, []);
-
- // Use a long press to open the context menu on touch devices
- const onPointerDown = useCallback(
- (e: PointerEvent) => {
- if (e.pointerType === 'mouse') {
- // Bail out if it's a mouse event - this is for touch/pen only
- return;
- }
-
- longPressTimeoutRef.current = window.setTimeout(() => {
- onContextMenu(e);
- }, LONGPRESS_DELAY_MS);
-
- lastPositionRef.current = { x: e.pageX, y: e.pageY };
- },
- [onContextMenu]
- );
-
- const onPointerMove = useCallback((e: PointerEvent) => {
- if (e.pointerType === 'mouse') {
- // Bail out if it's a mouse event - this is for touch/pen only
- return;
- }
- if (longPressTimeoutRef.current === null) {
- return;
- }
-
- // If the pointer has moved more than the threshold, cancel the long press
- const lastPosition = lastPositionRef.current;
-
- const distanceFromLastPosition = Math.hypot(e.pageX - lastPosition.x, e.pageY - lastPosition.y);
-
- if (distanceFromLastPosition > LONGPRESS_MOVE_THRESHOLD_PX) {
- clearTimeout(longPressTimeoutRef.current);
- }
- }, []);
-
- const onPointerUp = useCallback((e: PointerEvent) => {
- if (e.pointerType === 'mouse') {
- // Bail out if it's a mouse event - this is for touch/pen only
- return;
- }
- if (longPressTimeoutRef.current) {
- clearTimeout(longPressTimeoutRef.current);
- }
- }, []);
-
- const onPointerCancel = useCallback((e: PointerEvent) => {
- if (e.pointerType === 'mouse') {
- // Bail out if it's a mouse event - this is for touch/pen only
- return;
- }
- if (longPressTimeoutRef.current) {
- clearTimeout(longPressTimeoutRef.current);
- }
- }, []);
-
- useEffect(() => {
- const controller = new AbortController();
-
- // Context menu events
- window.addEventListener('contextmenu', onContextMenu, { signal: controller.signal });
-
- // Long press events
- window.addEventListener('pointerdown', onPointerDown, { signal: controller.signal });
- window.addEventListener('pointerup', onPointerUp, { signal: controller.signal });
- window.addEventListener('pointercancel', onPointerCancel, { signal: controller.signal });
- window.addEventListener('pointermove', onPointerMove, { signal: controller.signal });
-
- return () => {
- controller.abort();
- };
- }, [onContextMenu, onPointerCancel, onPointerDown, onPointerMove, onPointerUp]);
-
- useEffect(
- () => () => {
- // Clean up any timeouts when we unmount
- window.clearTimeout(animationTimeoutRef.current);
- window.clearTimeout(longPressTimeoutRef.current);
- },
- []
- );
-
- return null;
-});
-
-VideoContextMenuEventLogical.displayName = 'VideoContextMenuEventLogical';
-
-// The content of the context menu, which changes based on the selection count. Split out and memoized to avoid
-// re-rendering the whole context menu too often.
-const MenuContent = memo(() => {
- const selectionCount = useAppSelector(selectSelectionCount);
- const state = useStore($videoContextMenuState);
-
- if (!state.videoDTO) {
- return null;
- }
-
- if (selectionCount > 1) {
- return (
-
-
-
- );
- }
-
- return (
-
-
-
- );
-});
-
-MenuContent.displayName = 'MenuContent';
diff --git a/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx b/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx
index 11291bd5c72..3864094604d 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx
@@ -6,7 +6,6 @@ import { useDisclosure } from 'common/hooks/useBoolean';
import { useGallerySearchTerm } from 'features/gallery/components/ImageGrid/useGallerySearchTerm';
import { selectSelectedBoardId } from 'features/gallery/store/gallerySelectors';
import { galleryViewChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useAutoLayoutContext } from 'features/ui/layouts/auto-layout-context';
import { useGalleryPanel } from 'features/ui/layouts/use-gallery-panel';
import type { CSSProperties } from 'react';
@@ -19,7 +18,6 @@ import { GallerySettingsPopover } from './GallerySettingsPopover/GallerySettings
import { GalleryUploadButton } from './GalleryUploadButton';
import { GallerySearch } from './ImageGrid/GallerySearch';
import { ImageGallery } from './NewGallery';
-import { VideoGallery } from './VideoGallery';
const COLLAPSE_STYLES: CSSProperties = { flexShrink: 0, minHeight: 0, width: '100%' };
@@ -44,10 +42,6 @@ export const GalleryPanel = memo(() => {
dispatch(galleryViewChanged('assets'));
}, [dispatch]);
- const handleClickVideos = useCallback(() => {
- dispatch(galleryViewChanged('videos'));
- }, [dispatch]);
-
const handleClickSearch = useCallback(() => {
onResetSearchTerm();
if (!searchDisclosure.isOpen && galleryPanel.$isCollapsed.get()) {
@@ -58,7 +52,6 @@ export const GalleryPanel = memo(() => {
const selectedBoardId = useAppSelector(selectSelectedBoardId);
const boardName = useBoardName(selectedBoardId);
- const isVideoEnabled = useFeatureStatus('video');
return (
@@ -83,16 +76,6 @@ export const GalleryPanel = memo(() => {
{t('parameters.images')}
- {isVideoEnabled && (
-
- )}
- {galleryView === 'videos' ? : }
+
);
diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryHeader.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryHeader.tsx
deleted file mode 100644
index 1bcf7eb68ce..00000000000
--- a/invokeai/frontend/web/src/features/gallery/components/GalleryHeader.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Link } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { $projectName, $projectUrl } from 'app/store/nanostores/projectId';
-import { memo } from 'react';
-
-export const GalleryHeader = memo(() => {
- const projectName = useStore($projectName);
- const projectUrl = useStore($projectUrl);
-
- if (projectName && projectUrl) {
- return (
-
- {projectName}
-
- );
- }
-
- return null;
-});
-
-GalleryHeader.displayName = 'GalleryHeader';
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx
index 5a794530b0a..764201222b5 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx
@@ -217,7 +217,7 @@ export const GalleryImage = memo(({ imageDTO }: Props) => {
maxH="full"
borderRadius="base"
/>
-
+
{dragPreviewState?.type === 'multiple-image' ? createMultipleImageDragPreview(dragPreviewState) : null}
{dragPreviewState?.type === 'single-image' ? createSingleImageDragPreview(dragPreviewState) : null}
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemDeleteIconButton.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemDeleteIconButton.tsx
index 2b5185b11f6..0a97bf819de 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemDeleteIconButton.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemDeleteIconButton.tsx
@@ -5,33 +5,23 @@ import type { MouseEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiTrashSimpleFill } from 'react-icons/pi';
-import { useDeleteVideosMutation } from 'services/api/endpoints/videos';
-import { type ImageDTO, isImageDTO, type VideoDTO } from 'services/api/types';
+import type { ImageDTO } from 'services/api/types';
type Props = {
- itemDTO: ImageDTO | VideoDTO;
+ imageDTO: ImageDTO;
};
-export const GalleryItemDeleteIconButton = memo(({ itemDTO }: Props) => {
+export const GalleryItemDeleteIconButton = memo(({ imageDTO }: Props) => {
const shift = useShiftModifier();
const { t } = useTranslation();
const deleteImageModal = useDeleteImageModalApi();
- const [deleteVideos] = useDeleteVideosMutation();
const onClick = useCallback(
(e: MouseEvent) => {
e.stopPropagation();
- if (!itemDTO) {
- return;
- }
- if (isImageDTO(itemDTO)) {
- deleteImageModal.delete([itemDTO.image_name]);
- } else {
- // TODO: Add confirm on delete and video usage functionality
- deleteVideos({ video_ids: [itemDTO.video_id] });
- }
+ deleteImageModal.delete([imageDTO.image_name]);
},
- [deleteImageModal, deleteVideos, itemDTO]
+ [deleteImageModal, imageDTO]
);
if (!shift) {
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemHoverIcons.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemHoverIcons.tsx
index 03e69780dc4..e453b112e14 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemHoverIcons.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemHoverIcons.tsx
@@ -5,22 +5,22 @@ import { GalleryItemSizeBadge } from 'features/gallery/components/ImageGrid/Gall
import { GalleryItemStarIconButton } from 'features/gallery/components/ImageGrid/GalleryItemStarIconButton';
import { selectAlwaysShouldImageSizeBadge } from 'features/gallery/store/gallerySelectors';
import { memo } from 'react';
-import type { ImageDTO, VideoDTO } from 'services/api/types';
+import type { ImageDTO } from 'services/api/types';
type Props = {
- itemDTO: ImageDTO | VideoDTO;
+ imageDTO: ImageDTO;
isHovered: boolean;
};
-export const GalleryItemHoverIcons = memo(({ itemDTO, isHovered }: Props) => {
+export const GalleryItemHoverIcons = memo(({ imageDTO, isHovered }: Props) => {
const alwaysShowImageSizeBadge = useAppSelector(selectAlwaysShouldImageSizeBadge);
return (
<>
- {(isHovered || alwaysShowImageSizeBadge) && }
- {(isHovered || itemDTO.starred) && }
- {isHovered && }
- {isHovered && }
+ {(isHovered || alwaysShowImageSizeBadge) && }
+ {(isHovered || imageDTO.starred) && }
+ {isHovered && }
+ {isHovered && }
>
);
});
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemOpenInViewerIconButton.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemOpenInViewerIconButton.tsx
index 14a2f6f0874..ae7821a3f1d 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemOpenInViewerIconButton.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemOpenInViewerIconButton.tsx
@@ -6,26 +6,21 @@ import { VIEWER_PANEL_ID } from 'features/ui/layouts/shared';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArrowsOutBold } from 'react-icons/pi';
-import { type ImageDTO, isImageDTO, type VideoDTO } from 'services/api/types';
+import type { ImageDTO } from 'services/api/types';
type Props = {
- itemDTO: ImageDTO | VideoDTO;
+ imageDTO: ImageDTO;
};
-export const GalleryItemOpenInViewerIconButton = memo(({ itemDTO }: Props) => {
+export const GalleryItemOpenInViewerIconButton = memo(({ imageDTO }: Props) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const onClick = useCallback(() => {
- if (isImageDTO(itemDTO)) {
- dispatch(imageToCompareChanged(null));
- dispatch(itemSelected({ type: 'image', id: itemDTO.image_name }));
- } else {
- // dispatch(videoToCompareChanged(null));
- // dispatch(videoSelected(itemDTO.video_id));
- }
+ dispatch(imageToCompareChanged(null));
+ dispatch(itemSelected({ type: 'image', id: imageDTO.image_name }));
navigationApi.focusPanelInActiveTab(VIEWER_PANEL_ID);
- }, [dispatch, itemDTO]);
+ }, [dispatch, imageDTO]);
return (
{
+export const GalleryItemSizeBadge = memo(({ imageDTO }: Props) => {
return (
{
lineHeight={1.25}
borderTopEndRadius="base"
pointerEvents="none"
- >{`${itemDTO.width}x${itemDTO.height}`}
+ >{`${imageDTO.width}x${imageDTO.height}`}
);
});
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemStarIconButton.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemStarIconButton.tsx
index 798eba6834e..71607ff3ecb 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemStarIconButton.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemStarIconButton.tsx
@@ -1,57 +1,30 @@
-import { useStore } from '@nanostores/react';
-import { $customStarUI } from 'app/store/nanostores/customStarUI';
import { DndImageIcon } from 'features/dnd/DndImageIcon';
import { memo, useCallback } from 'react';
import { PiStarBold, PiStarFill } from 'react-icons/pi';
import { useStarImagesMutation, useUnstarImagesMutation } from 'services/api/endpoints/images';
-import { useStarVideosMutation, useUnstarVideosMutation } from 'services/api/endpoints/videos';
-import { type ImageDTO, isImageDTO, type VideoDTO } from 'services/api/types';
+import type { ImageDTO } from 'services/api/types';
type Props = {
- itemDTO: ImageDTO | VideoDTO;
+ imageDTO: ImageDTO;
};
-export const GalleryItemStarIconButton = memo(({ itemDTO }: Props) => {
- const customStarUi = useStore($customStarUI);
+export const GalleryItemStarIconButton = memo(({ imageDTO }: Props) => {
const [starImages] = useStarImagesMutation();
const [unstarImages] = useUnstarImagesMutation();
- const [starVideos] = useStarVideosMutation();
- const [unstarVideos] = useUnstarVideosMutation();
const toggleStarredState = useCallback(() => {
- if (itemDTO.starred) {
- if (isImageDTO(itemDTO)) {
- unstarImages({ image_names: [itemDTO.image_name] });
- } else {
- unstarVideos({ video_ids: [itemDTO.video_id] });
- }
+ if (imageDTO.starred) {
+ unstarImages({ image_names: [imageDTO.image_name] });
} else {
- if (isImageDTO(itemDTO)) {
- starImages({ image_names: [itemDTO.image_name] });
- } else {
- starVideos({ video_ids: [itemDTO.video_id] });
- }
+ starImages({ image_names: [imageDTO.image_name] });
}
- }, [starImages, unstarImages, starVideos, unstarVideos, itemDTO]);
-
- if (customStarUi) {
- return (
-
- );
- }
+ }, [starImages, unstarImages, imageDTO]);
return (
: }
- tooltip={itemDTO.starred ? 'Unstar' : 'Star'}
+ icon={imageDTO.starred ? : }
+ tooltip={imageDTO.starred ? 'Unstar' : 'Star'}
position="absolute"
top={2}
insetInlineEnd={2}
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryVideo.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryVideo.tsx
deleted file mode 100644
index fe249b6019e..00000000000
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryVideo.tsx
+++ /dev/null
@@ -1,218 +0,0 @@
-import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
-import { draggable, monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
-import { Flex, Image } from '@invoke-ai/ui-library';
-import { createSelector } from '@reduxjs/toolkit';
-import type { AppDispatch, AppGetState } from 'app/store/store';
-import { useAppSelector, useAppStore } from 'app/store/storeHooks';
-import { uniq } from 'es-toolkit';
-import { multipleVideoDndSource, singleVideoDndSource } from 'features/dnd/dnd';
-import type { DndDragPreviewMultipleVideoState } from 'features/dnd/DndDragPreviewMultipleVideo';
-import { createMultipleVideoDragPreview, setMultipleVideoDragPreview } from 'features/dnd/DndDragPreviewMultipleVideo';
-import type { DndDragPreviewSingleVideoState } from 'features/dnd/DndDragPreviewSingleVideo';
-import { createSingleVideoDragPreview, setSingleVideoDragPreview } from 'features/dnd/DndDragPreviewSingleVideo';
-import { firefoxDndFix } from 'features/dnd/util';
-import { useVideoContextMenu } from 'features/gallery/components/ContextMenu/VideoContextMenu';
-import {
- selectGetVideoIdsQueryArgs,
- selectSelectedBoardId,
- selectSelection,
-} from 'features/gallery/store/gallerySelectors';
-import { imageToCompareChanged, selectGallerySlice, selectionChanged } from 'features/gallery/store/gallerySlice';
-import { navigationApi } from 'features/ui/layouts/navigation-api';
-import { VIEWER_PANEL_ID } from 'features/ui/layouts/shared';
-import type { MouseEvent, MouseEventHandler } from 'react';
-import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import { videosApi } from 'services/api/endpoints/videos';
-import type { VideoDTO } from 'services/api/types';
-
-import { galleryItemContainerSX } from './galleryItemContainerSX';
-import { GalleryItemHoverIcons } from './GalleryItemHoverIcons';
-import { GalleryVideoPlaceholder } from './GalleryVideoPlaceholder';
-
-interface Props {
- videoDTO: VideoDTO;
-}
-
-const buildOnClick =
- (videoId: string, dispatch: AppDispatch, getState: AppGetState) => (e: MouseEvent) => {
- const { shiftKey, ctrlKey, metaKey, altKey } = e;
- const state = getState();
- const queryArgs = selectGetVideoIdsQueryArgs(state);
- const videoIds = videosApi.endpoints.getVideoIds.select(queryArgs)(state).data?.video_ids ?? [];
-
- // If we don't have the video ids cached, we can't perform selection operations
- // This can happen if the user clicks on a video before the ids are loaded
- if (videoIds.length === 0) {
- // For basic click without modifiers, we can still set selection
- if (!shiftKey && !ctrlKey && !metaKey && !altKey) {
- dispatch(selectionChanged([{ type: 'video', id: videoId }]));
- }
- return;
- }
-
- const selection = state.gallery.selection;
-
- if (altKey) {
- if (state.gallery.imageToCompare === videoId) {
- dispatch(imageToCompareChanged(null));
- } else {
- dispatch(imageToCompareChanged(videoId));
- }
- } else if (shiftKey) {
- const rangeEndVideoId = videoId;
- const lastSelectedVideo = selection.at(-1)?.id;
- const lastClickedIndex = videoIds.findIndex((id) => id === lastSelectedVideo);
- const currentClickedIndex = videoIds.findIndex((id) => id === rangeEndVideoId);
- if (lastClickedIndex > -1 && currentClickedIndex > -1) {
- // We have a valid range!
- const start = Math.min(lastClickedIndex, currentClickedIndex);
- const end = Math.max(lastClickedIndex, currentClickedIndex);
- const videosToSelect = videoIds.slice(start, end + 1);
- dispatch(selectionChanged(uniq(selection.concat(videosToSelect.map((id) => ({ type: 'video', id }))))));
- }
- } else if (ctrlKey || metaKey) {
- if (selection.some((n) => n.id === videoId) && selection.length > 1) {
- dispatch(selectionChanged(uniq(selection.filter((n) => n.id !== videoId))));
- } else {
- dispatch(selectionChanged(uniq(selection.concat({ type: 'video', id: videoId }))));
- }
- } else {
- dispatch(selectionChanged([{ type: 'video', id: videoId }]));
- }
- };
-
-export const GalleryVideo = memo(({ videoDTO }: Props) => {
- const store = useAppStore();
- const [isDragging, setIsDragging] = useState(false);
- const [dragPreviewState, setDragPreviewState] = useState<
- DndDragPreviewSingleVideoState | DndDragPreviewMultipleVideoState | null
- >(null);
- const ref = useRef(null);
- const selectIsSelected = useMemo(
- () => createSelector(selectGallerySlice, (gallery) => gallery.selection.some((s) => s.id === videoDTO.video_id)),
- [videoDTO.video_id]
- );
- const isSelected = useAppSelector(selectIsSelected);
-
- useEffect(() => {
- const element = ref.current;
- if (!element) {
- return;
- }
- return combine(
- firefoxDndFix(element),
- draggable({
- element,
- getInitialData: () => {
- const selection = selectSelection(store.getState());
- const boardId = selectSelectedBoardId(store.getState());
-
- // When we have multiple images selected, and the dragged image is part of the selection, initiate a
- // multi-image drag.
- if (selection.length > 1 && selection.some((s) => s.id === videoDTO.video_id)) {
- return multipleVideoDndSource.getData({
- video_ids: selection.map((s) => s.id),
- board_id: boardId,
- });
- } // Otherwise, initiate a single-image drag
-
- return singleVideoDndSource.getData({ videoDTO }, videoDTO.video_id);
- },
- // This is a "local" drag start event, meaning that it is only called when this specific image is dragged.
- onDragStart: ({ source }) => {
- // When we start dragging a single image, set the dragging state to true. This is only called when this
- // specific image is dragged.
- if (singleVideoDndSource.typeGuard(source.data)) {
- setIsDragging(true);
- return;
- }
- },
- onGenerateDragPreview: (args) => {
- if (multipleVideoDndSource.typeGuard(args.source.data)) {
- setMultipleVideoDragPreview({
- multipleVideoDndData: args.source.data,
- onGenerateDragPreviewArgs: args,
- setDragPreviewState,
- });
- } else if (singleVideoDndSource.typeGuard(args.source.data)) {
- setSingleVideoDragPreview({
- singleVideoDndData: args.source.data,
- onGenerateDragPreviewArgs: args,
- setDragPreviewState,
- });
- }
- },
- }),
- monitorForElements({
- // This is a "global" drag start event, meaning that it is called for all drag events.
- onDragStart: ({ source }) => {
- // When we start dragging multiple images, set the dragging state to true if the dragged image is part of the
- // selection. This is called for all drag events.
- if (
- multipleVideoDndSource.typeGuard(source.data) &&
- source.data.payload.video_ids.includes(videoDTO.video_id)
- ) {
- setIsDragging(true);
- }
- },
- onDrop: () => {
- // Always set the dragging state to false when a drop event occurs.
- setIsDragging(false);
- },
- })
- );
- }, [videoDTO, store]);
-
- const [isHovered, setIsHovered] = useState(false);
-
- const onMouseOver = useCallback(() => {
- setIsHovered(true);
- }, []);
-
- const onMouseOut = useCallback(() => {
- setIsHovered(false);
- }, []);
-
- const onClick = useMemo(() => buildOnClick(videoDTO.video_id, store.dispatch, store.getState), [videoDTO, store]);
-
- const onDoubleClick = useCallback>(() => {
- store.dispatch(imageToCompareChanged(null));
- navigationApi.focusPanelInActiveTab(VIEWER_PANEL_ID);
- }, [store]);
-
- useVideoContextMenu(videoDTO, ref);
-
- return (
- <>
-
- }
- objectFit="contain"
- maxW="full"
- maxH="full"
- borderRadius="base"
- />
-
-
- {dragPreviewState?.type === 'multiple-video' ? createMultipleVideoDragPreview(dragPreviewState) : null}
- {dragPreviewState?.type === 'single-video' ? createSingleVideoDragPreview(dragPreviewState) : null}
- >
- );
-});
-
-GalleryVideo.displayName = 'GalleryVideo';
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryVideoPlaceholder.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryVideoPlaceholder.tsx
deleted file mode 100644
index cca0f0aa51a..00000000000
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryVideoPlaceholder.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Flex, type FlexProps, Icon } from '@invoke-ai/ui-library';
-import { memo } from 'react';
-import { PiVideoBold } from 'react-icons/pi';
-
-export const GalleryVideoPlaceholder = memo((props: FlexProps) => (
-
-
-
-));
-
-GalleryVideoPlaceholder.displayName = 'GalleryVideoPlaceholder';
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx
index 482fadf111c..d66576aedb4 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx
@@ -13,7 +13,6 @@ import {
useCollectionMetadataDatum,
useSingleMetadataDatum,
useUnrecallableMetadataDatum,
- VideoMetadataHandlers,
} from 'features/metadata/parsing';
import { memo, useCallback } from 'react';
import { PiArrowBendUpLeftBold } from 'react-icons/pi';
@@ -64,28 +63,6 @@ export const ImageMetadataActions = memo((props: Props) => {
ImageMetadataActions.displayName = 'ImageMetadataActions';
-export const VideoMetadataActions = memo((props: Props) => {
- const { metadata } = props;
-
- if (!metadata || Object.keys(metadata).length === 0) {
- return null;
- }
-
- return (
-
-
-
-
-
-
-
-
-
- );
-});
-
-VideoMetadataActions.displayName = 'VideoMetadataActions';
-
export const UnrecallableMetadataDatum = typedMemo(
({ metadata, handler }: { metadata: unknown; handler: UnrecallableMetadataHandler }) => {
const { data } = useUnrecallableMetadataDatum(metadata, handler);
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/VideoMetadataViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/VideoMetadataViewer.tsx
deleted file mode 100644
index f46ec8d470d..00000000000
--- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/VideoMetadataViewer.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { ExternalLink, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
-import { IAINoContentFallback, IAINoContentFallbackWithSpinner } from 'common/components/IAIImageFallback';
-import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
-import { ImageMetadataHandlers } from 'features/metadata/parsing';
-import { memo } from 'react';
-import { useTranslation } from 'react-i18next';
-import { useDebouncedVideoMetadata } from 'services/api/hooks/useDebouncedMetadata';
-import type { VideoDTO } from 'services/api/types';
-
-import DataViewer from './DataViewer';
-import { UnrecallableMetadataDatum, VideoMetadataActions } from './ImageMetadataActions';
-
-type VideoMetadataViewerProps = {
- video: VideoDTO;
-};
-
-const VideoMetadataViewer = ({ video }: VideoMetadataViewerProps) => {
- const { t } = useTranslation();
-
- const { metadata, isLoading } = useDebouncedVideoMetadata(video.video_id);
-
- return (
-
-
-
-
-
-
- {t('metadata.recallParameters')}
- {t('metadata.metadata')}
- {t('metadata.videoDetails')}
-
-
-
-
- {isLoading && }
- {metadata && !isLoading && (
-
-
-
- )}
- {!metadata && !isLoading && }
-
-
- {metadata ? (
-
- ) : (
-
- )}
-
-
- {video ? (
-
- ) : (
-
- )}
-
- {/*
-
-
-
-
- */}
-
-
-
- );
-};
-
-export default memo(VideoMetadataViewer);
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 7994b231d69..603768e693a 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx
@@ -13,7 +13,6 @@ import { useRecallSeed } from 'features/gallery/hooks/useRecallSeed';
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
import { IMAGE_CATEGORIES } from 'features/gallery/store/types';
import { PostProcessingPopover } from 'features/parameters/components/PostProcessing/PostProcessingPopover';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { navigationApi } from 'features/ui/layouts/navigation-api';
import { useGalleryPanel } from 'features/ui/layouts/use-gallery-panel';
import { selectActiveTab } from 'features/ui/store/uiSelectors';
@@ -64,8 +63,6 @@ export const CurrentImageButtons = memo(({ imageDTO }: { imageDTO: ImageDTO }) =
const isCanvasOrGenerateOrUpscalingTab = tab === 'canvas' || tab === 'generate' || tab === 'upscaling';
const doesTabHaveGallery = tab === 'canvas' || tab === 'generate' || tab === 'workflows' || tab === 'upscaling';
- const isUpscalingEnabled = useFeatureStatus('upscaling');
-
const recallAll = useRecallAll(imageDTO);
const recallRemix = useRecallRemix(imageDTO);
const recallPrompts = useRecallPrompts(imageDTO);
@@ -182,7 +179,7 @@ export const CurrentImageButtons = memo(({ imageDTO }: { imageDTO: ImageDTO }) =
/>
)}
- {isUpscalingEnabled && }
+
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentVideoButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentVideoButtons.tsx
deleted file mode 100644
index 28e91a1e5f0..00000000000
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentVideoButtons.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import { Button, Divider, IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ui-library';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { useDeleteVideo } from 'features/deleteImageModal/hooks/use-delete-video';
-import { DeleteVideoButton } from 'features/deleteVideoModal/components/DeleteVideoButton';
-import SingleSelectionVideoMenuItems from 'features/gallery/components/ContextMenu/SingleSelectionVideoMenuItems';
-import { boardIdSelected } from 'features/gallery/store/gallerySlice';
-import { navigationApi } from 'features/ui/layouts/navigation-api';
-import { useGalleryPanel } from 'features/ui/layouts/use-gallery-panel';
-import { selectActiveTab } from 'features/ui/store/uiSelectors';
-import { useVideoViewerContext } from 'features/video/context/VideoViewerContext';
-import { useCaptureVideoFrame } from 'features/video/hooks/useCaptureVideoFrame';
-import { memo, useCallback, useState } from 'react';
-import { flushSync } from 'react-dom';
-import { useTranslation } from 'react-i18next';
-import { PiCameraBold, PiCrosshairBold, PiDotsThreeOutlineFill, PiSpinnerBold } from 'react-icons/pi';
-import type { VideoDTO } from 'services/api/types';
-
-export const CurrentVideoButtons = memo(({ videoDTO }: { videoDTO: VideoDTO }) => {
- const { t } = useTranslation();
- const tab = useAppSelector(selectActiveTab);
- const dispatch = useAppDispatch();
- const activeTab = useAppSelector(selectActiveTab);
- const galleryPanel = useGalleryPanel(activeTab);
- const deleteVideo = useDeleteVideo(videoDTO);
-
- const captureVideoFrame = useCaptureVideoFrame();
- const { videoRef } = useVideoViewerContext();
- const [capturing, setCapturing] = useState(false);
-
- const locateInGallery = useCallback(() => {
- navigationApi.expandRightPanel();
- galleryPanel.expand();
- flushSync(() => {
- dispatch(
- boardIdSelected({
- boardId: videoDTO.board_id ?? 'none',
- select: {
- selection: [{ type: 'video', id: videoDTO.video_id }],
- galleryView: 'videos',
- },
- })
- );
- });
- }, [dispatch, galleryPanel, videoDTO]);
-
- const onClickSaveFrame = useCallback(async () => {
- setCapturing(true);
- await captureVideoFrame(videoRef.current);
- setCapturing(false);
- }, [captureVideoFrame, videoRef]);
-
- const doesTabHaveGallery = tab === 'canvas' || tab === 'generate' || tab === 'workflows' || tab === 'upscaling';
-
- // const recallAll = useRecallAll(imageDTO);
- // const recallRemix = useRecallRemix(imageDTO);
- // const recallPrompts = useRecallPrompts(imageDTO);
- // const recallSeed = useRecallSeed(imageDTO);
- // const recallDimensions = useRecallDimensions(imageDTO);
- // const loadWorkflow = useLoadWorkflow(imageDTO);
- // const editImage = useEditImage(imageDTO);
- // const deleteImage = useDeleteImage(imageDTO);
-
- return (
- <>
-
-
-
-
- : }
- onClick={onClickSaveFrame}
- isDisabled={capturing || !videoRef}
- variant="link"
- size="sm"
- alignSelf="stretch"
- px={2}
- isLoading={capturing}
- loadingText="Capturing..."
- >
- {capturing ? 'Capturing...' : 'Save Current Frame'}
-
-
-
-
- {doesTabHaveGallery && (
- <>
- }
- aria-label={t('boards.locateInGalery')}
- tooltip={t('boards.locateInGalery')}
- onClick={locateInGallery}
- variant="link"
- size="sm"
- alignSelf="stretch"
- />
-
- >
- )}
-
-
- >
- );
-});
-
-CurrentVideoButtons.displayName = 'CurrentVideoButtons';
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentVideoPreview.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentVideoPreview.tsx
deleted file mode 100644
index 25c9806ab72..00000000000
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentVideoPreview.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import { Box, Flex } from '@invoke-ai/ui-library';
-import { useAppSelector } from 'app/store/storeHooks';
-import VideoMetadataViewer from 'features/gallery/components/ImageMetadataViewer/VideoMetadataViewer';
-import NextPrevItemButtons from 'features/gallery/components/NextPrevItemButtons';
-import { selectShouldShowItemDetails } from 'features/ui/store/uiSelectors';
-import { VideoView } from 'features/video/components/VideoView';
-import type { AnimationProps } from 'framer-motion';
-import { AnimatePresence, motion } from 'framer-motion';
-import { memo, useCallback, useRef, useState } from 'react';
-import type { VideoDTO } from 'services/api/types';
-
-import { NoContentForViewer } from './NoContentForViewer';
-
-export const CurrentVideoPreview = memo(({ videoDTO }: { videoDTO: VideoDTO | null }) => {
- const shouldShowItemDetails = useAppSelector(selectShouldShowItemDetails);
-
- // Show and hide the next/prev buttons on mouse move
- const [shouldShowNextPrevButtons, setShouldShowNextPrevButtons] = useState(false);
- const timeoutId = useRef(0);
- const onMouseOver = useCallback(() => {
- setShouldShowNextPrevButtons(true);
- window.clearTimeout(timeoutId.current);
- }, []);
- const onMouseOut = useCallback(() => {
- timeoutId.current = window.setTimeout(() => {
- setShouldShowNextPrevButtons(false);
- }, 500);
- }, []);
-
- return (
-
- {videoDTO && videoDTO.video_url && (
-
-
-
- )}
- {!videoDTO && }
- {shouldShowItemDetails && videoDTO && (
-
-
-
- )}
-
- {shouldShowNextPrevButtons && videoDTO && (
-
-
-
- )}
-
-
- );
-});
-CurrentVideoPreview.displayName = 'CurrentVideoPreview';
-
-const initial: AnimationProps['initial'] = {
- opacity: 0,
-};
-const animateArrows: AnimationProps['animate'] = {
- opacity: 1,
- transition: { duration: 0.07 },
-};
-const exit: AnimationProps['exit'] = {
- opacity: 0,
- transition: { duration: 0.07 },
-};
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonHover.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonHover.tsx
index 7123a8fbf37..edfada8bc2d 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonHover.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonHover.tsx
@@ -1,6 +1,4 @@
import { Box, Flex, Image } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { $crossOrigin } from 'app/store/nanostores/authToken';
import { useAppSelector } from 'app/store/storeHooks';
import { useBoolean } from 'common/hooks/useBoolean';
import { preventDefault } from 'common/util/stopPropagation';
@@ -14,8 +12,6 @@ import type { ComparisonProps } from './common';
import { fitDimsToContainer, getSecondImageDims } from './common';
export const ImageComparisonHover = memo(({ firstImage, secondImage, rect }: ComparisonProps) => {
- const crossOrigin = useStore($crossOrigin);
-
const comparisonFit = useAppSelector(selectComparisonFit);
const imageContainerRef = useRef(null);
const mouseOver = useBoolean(false);
@@ -57,7 +53,6 @@ export const ImageComparisonHover = memo(({ firstImage, secondImage, rect }: Com
id="image-comparison-hover-first-image"
src={firstImage.image_url}
fallbackSrc={firstImage.thumbnail_url}
- crossOrigin={crossOrigin}
w={fittedDims.width}
h={fittedDims.height}
maxW="full"
@@ -94,7 +89,6 @@ export const ImageComparisonHover = memo(({ firstImage, secondImage, rect }: Com
id="image-comparison-hover-second-image"
src={secondImage.image_url}
fallbackSrc={secondImage.thumbnail_url}
- crossOrigin={crossOrigin}
w={compareImageDims.width}
h={compareImageDims.height}
maxW={fittedDims.width}
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSideBySide.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSideBySide.tsx
index 45c4201c19d..a84e842dccc 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSideBySide.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSideBySide.tsx
@@ -1,6 +1,4 @@
import { Flex, Image } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { $crossOrigin } from 'app/store/nanostores/authToken';
import type { ComparisonProps } from 'features/gallery/components/ImageViewer/common';
import { ImageComparisonLabel } from 'features/gallery/components/ImageViewer/ImageComparisonLabel';
import { VerticalResizeHandle } from 'features/ui/components/tabs/ResizeHandle';
@@ -43,8 +41,6 @@ export const ImageComparisonSideBySide = memo(({ firstImage, secondImage }: Comp
ImageComparisonSideBySide.displayName = 'ImageComparisonSideBySide';
const SideBySideImage = memo(({ imageDTO, type }: { imageDTO: ImageDTO; type: 'first' | 'second' }) => {
- const crossOrigin = useStore($crossOrigin);
-
return (
@@ -56,7 +52,6 @@ const SideBySideImage = memo(({ imageDTO, type }: { imageDTO: ImageDTO; type: 'f
maxH="full"
src={imageDTO.image_url}
fallbackSrc={imageDTO.thumbnail_url}
- crossOrigin={crossOrigin}
objectFit="contain"
borderRadius="base"
/>
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSlider.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSlider.tsx
index ce4bc5f083d..1f0d64aafeb 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSlider.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSlider.tsx
@@ -1,6 +1,4 @@
import { Box, Flex, Icon, Image } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { $crossOrigin } from 'app/store/nanostores/authToken';
import { useAppSelector } from 'app/store/storeHooks';
import { preventDefault } from 'common/util/stopPropagation';
import { TRANSPARENCY_CHECKERBOARD_PATTERN_DARK_DATAURL } from 'features/controlLayers/konva/patterns/transparency-checkerboard-pattern';
@@ -23,7 +21,6 @@ const HANDLE_LEFT_INITIAL_PX = `calc(${INITIAL_POS} - ${HANDLE_HITBOX / 2}px)`;
export const ImageComparisonSlider = memo(({ firstImage, secondImage, rect }: ComparisonProps) => {
const comparisonFit = useAppSelector(selectComparisonFit);
- const crossOrigin = useStore($crossOrigin);
// How far the handle is from the left - this will be a CSS calculation that takes into account the handle width
const [left, setLeft] = useState(HANDLE_LEFT_INITIAL_PX);
@@ -135,7 +132,6 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage, rect }: Co
id="image-comparison-second-image"
src={secondImage.image_url}
fallbackSrc={secondImage.thumbnail_url}
- crossOrigin={crossOrigin}
w={compareImageDims.width}
h={compareImageDims.height}
maxW={fittedDims.width}
@@ -158,7 +154,6 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage, rect }: Co
id="image-comparison-first-image"
src={firstImage.image_url}
fallbackSrc={firstImage.thumbnail_url}
- crossOrigin={crossOrigin}
w={fittedDims.width}
h={fittedDims.height}
objectFit="cover"
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewerPanel.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewerPanel.tsx
index cea53931924..9e829ea8dcf 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewerPanel.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewerPanel.tsx
@@ -6,7 +6,6 @@ import { memo } from 'react';
import { ImageViewerContextProvider } from './context';
import { ImageComparison } from './ImageComparison';
import { ImageViewer } from './ImageViewer';
-import { VideoViewer } from './VideoViewer';
const selectIsComparing = createSelector(
[selectLastSelectedItem, selectImageToCompare],
@@ -23,8 +22,7 @@ export const ImageViewerPanel = memo(() => {
// The image viewer renders progress images - if no image is selected, show the image viewer anyway
!isComparing && !lastSelectedItem &&
}
- {!isComparing && lastSelectedItem?.type === 'image' && }
- {!isComparing && lastSelectedItem?.type === 'video' && }
+ {!isComparing && }
{isComparing && }
);
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx
index 62248a1883c..1649a14c511 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx
@@ -1,14 +1,10 @@
import type { ButtonProps } from '@invoke-ai/ui-library';
import { Alert, AlertDescription, AlertIcon, Button, Divider, Flex, Link, Spinner, Text } from '@invoke-ai/ui-library';
-import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { InvokeLogoIcon } from 'common/components/InvokeLogoIcon';
import { LOADING_SYMBOL, useHasImages } from 'features/gallery/hooks/useHasImages';
import { setInstallModelsTabByName } from 'features/modelManagerV2/store/installModelsStore';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
-import { selectIsLocal } from 'features/system/store/configSlice';
import { navigationApi } from 'features/ui/layouts/navigation-api';
-import { selectActiveTab } from 'features/ui/store/uiSelectors';
import type { PropsWithChildren } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { Trans, useTranslation } from 'react-i18next';
@@ -18,14 +14,11 @@ import { useMainModels } from 'services/api/hooks/modelsByType';
export const NoContentForViewer = memo(() => {
const hasImages = useHasImages();
const [mainModels, { data }] = useMainModels();
- const isLocal = useAppSelector(selectIsLocal);
- const isEnabled = useFeatureStatus('starterModels');
- const activeTab = useAppSelector(selectActiveTab);
const { t } = useTranslation();
const showStarterBundles = useMemo(() => {
- return isEnabled && data && mainModels.length === 0;
- }, [mainModels.length, data, isEnabled]);
+ return data && mainModels.length === 0;
+ }, [mainModels.length, data]);
if (hasImages === LOADING_SYMBOL) {
// Blank bg w/ a spinner. The new user experience components below have an invoke logo, but it's not centered.
@@ -43,11 +36,10 @@ export const NoContentForViewer = memo(() => {
- {isLocal ? : activeTab === 'workflows' ? : }
+
{showStarterBundles && }
-
- {isLocal && }
+
);
@@ -97,37 +89,6 @@ const GetStartedLocal = () => {
);
};
-const GetStartedCommercial = () => {
- return (
-
-
-
- );
-};
-
-const GetStartedWorkflows = () => {
- return (
-
-
-
- );
-};
-
-const GettingStartedVideosCallout = () => {
- return (
-
-
- ),
- }}
- />
-
- );
-};
-
const StarterBundlesCallout = () => {
const handleClickDownloadStarterModels = useCallback(() => {
navigationApi.switchToTab('models');
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/VideoViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/VideoViewer.tsx
deleted file mode 100644
index b06ac30c172..00000000000
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/VideoViewer.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { Divider, Flex } from '@invoke-ai/ui-library';
-import { useAppSelector } from 'app/store/storeHooks';
-import { selectLastSelectedItem } from 'features/gallery/store/gallerySelectors';
-import { VideoViewerContextProvider } from 'features/video/context/VideoViewerContext';
-import { memo } from 'react';
-import { useVideoDTO } from 'services/api/endpoints/videos';
-
-import { CurrentVideoPreview } from './CurrentVideoPreview';
-import { VideoViewerToolbar } from './VideoViewerToolbar';
-
-export const VideoViewer = memo(() => {
- const lastSelectedItem = useAppSelector(selectLastSelectedItem);
- const videoDTO = useVideoDTO(lastSelectedItem?.type === 'video' ? lastSelectedItem.id : null);
-
- return (
-
-
-
-
-
-
-
-
-
- );
-});
-
-VideoViewer.displayName = 'VideoViewer';
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/VideoViewerToolbar.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/VideoViewerToolbar.tsx
deleted file mode 100644
index 994da11ae14..00000000000
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/VideoViewerToolbar.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Flex } from '@invoke-ai/ui-library';
-import { useAppSelector } from 'app/store/storeHooks';
-import { ToggleMetadataViewerButton } from 'features/gallery/components/ImageViewer/ToggleMetadataViewerButton';
-import { selectLastSelectedItem } from 'features/gallery/store/gallerySelectors';
-import { memo } from 'react';
-import { useVideoDTO } from 'services/api/endpoints/videos';
-
-import { CurrentVideoButtons } from './CurrentVideoButtons';
-
-export const VideoViewerToolbar = memo(() => {
- const lastSelectedItem = useAppSelector(selectLastSelectedItem);
- const videoDTO = useVideoDTO(lastSelectedItem?.type === 'video' ? lastSelectedItem.id : null);
-
- return (
-
- {videoDTO && }
- {videoDTO && }
-
- );
-});
-
-VideoViewerToolbar.displayName = 'VideoViewerToolbar';
diff --git a/invokeai/frontend/web/src/features/gallery/components/NextPrevItemButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/NextPrevItemButtons.tsx
index c7a6823f4d5..b59965c28e3 100644
--- a/invokeai/frontend/web/src/features/gallery/components/NextPrevItemButtons.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/NextPrevItemButtons.tsx
@@ -9,14 +9,12 @@ import { useTranslation } from 'react-i18next';
import { PiCaretLeftBold, PiCaretRightBold } from 'react-icons/pi';
import { useGalleryImageNames } from './use-gallery-image-names';
-import { useGalleryVideoIds } from './use-gallery-video-ids';
const NextPrevItemButtons = ({ inset = 8 }: { inset?: ChakraProps['insetInlineStart' | 'insetInlineEnd'] }) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const lastSelectedItem = useAppSelector(selectLastSelectedItem);
const { imageNames, isFetching } = useGalleryImageNames();
- const { videoIds, isFetching: isFetchingVideos } = useGalleryVideoIds();
const isOnFirstItem = useMemo(
() => (lastSelectedItem ? imageNames.at(0) === lastSelectedItem.id : false),
@@ -28,26 +26,24 @@ const NextPrevItemButtons = ({ inset = 8 }: { inset?: ChakraProps['insetInlineSt
);
const onClickLeftArrow = useCallback(() => {
- const items = lastSelectedItem?.type === 'image' ? imageNames : videoIds;
- const targetIndex = lastSelectedItem ? items.findIndex((n) => n === lastSelectedItem.id) - 1 : 0;
- const clampedIndex = clamp(targetIndex, 0, items.length - 1);
- const n = items.at(clampedIndex);
+ const targetIndex = lastSelectedItem ? imageNames.findIndex((n) => n === lastSelectedItem.id) - 1 : 0;
+ const clampedIndex = clamp(targetIndex, 0, imageNames.length - 1);
+ const n = imageNames.at(clampedIndex);
if (!n) {
return;
}
dispatch(itemSelected({ type: lastSelectedItem?.type ?? 'image', id: n }));
- }, [dispatch, imageNames, lastSelectedItem, videoIds]);
+ }, [dispatch, imageNames, lastSelectedItem]);
const onClickRightArrow = useCallback(() => {
- const items = lastSelectedItem?.type === 'image' ? imageNames : videoIds;
- const targetIndex = lastSelectedItem ? items.findIndex((n) => n === lastSelectedItem.id) + 1 : 0;
- const clampedIndex = clamp(targetIndex, 0, items.length - 1);
- const n = items.at(clampedIndex);
+ const targetIndex = lastSelectedItem ? imageNames.findIndex((n) => n === lastSelectedItem.id) + 1 : 0;
+ const clampedIndex = clamp(targetIndex, 0, imageNames.length - 1);
+ const n = imageNames.at(clampedIndex);
if (!n) {
return;
}
dispatch(itemSelected({ type: lastSelectedItem?.type ?? 'image', id: n }));
- }, [dispatch, imageNames, lastSelectedItem, videoIds]);
+ }, [dispatch, imageNames, lastSelectedItem]);
return (
@@ -60,7 +56,7 @@ const NextPrevItemButtons = ({ inset = 8 }: { inset?: ChakraProps['insetInlineSt
icon={}
variant="unstyled"
onClick={onClickLeftArrow}
- isDisabled={isFetching || isFetchingVideos}
+ isDisabled={isFetching}
color="base.100"
pointerEvents="auto"
insetInlineStart={inset}
@@ -75,7 +71,7 @@ const NextPrevItemButtons = ({ inset = 8 }: { inset?: ChakraProps['insetInlineSt
icon={}
variant="unstyled"
onClick={onClickRightArrow}
- isDisabled={isFetching || isFetchingVideos}
+ isDisabled={isFetching}
color="base.100"
pointerEvents="auto"
insetInlineEnd={inset}
diff --git a/invokeai/frontend/web/src/features/gallery/components/VideoGallery.tsx b/invokeai/frontend/web/src/features/gallery/components/VideoGallery.tsx
deleted file mode 100644
index 766971e76ec..00000000000
--- a/invokeai/frontend/web/src/features/gallery/components/VideoGallery.tsx
+++ /dev/null
@@ -1,390 +0,0 @@
-import { Box, Flex, forwardRef, Grid, GridItem, Spinner, Text } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { createSelector } from '@reduxjs/toolkit';
-import { $accountTypeText } from 'app/store/nanostores/accountTypeText';
-import { useAppSelector, useAppStore } from 'app/store/storeHooks';
-import { getFocusedRegion } from 'common/hooks/focus';
-import { useRangeBasedVideoFetching } from 'features/gallery/hooks/useRangeBasedVideoFetching';
-import type { selectGetVideoIdsQueryArgs } from 'features/gallery/store/gallerySelectors';
-import { selectGalleryImageMinimumWidth, selectLastSelectedItem } from 'features/gallery/store/gallerySelectors';
-import { selectionChanged } from 'features/gallery/store/gallerySlice';
-import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
-import { selectAllowVideo } from 'features/system/store/configSlice';
-import type { MutableRefObject } from 'react';
-import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react';
-import type {
- GridComponents,
- GridComputeItemKey,
- GridItemContent,
- ListRange,
- ScrollSeekConfiguration,
- VirtuosoGridHandle,
-} from 'react-virtuoso';
-import { VirtuosoGrid } from 'react-virtuoso';
-import { videosApi } from 'services/api/endpoints/videos';
-import { useDebounce } from 'use-debounce';
-
-import { getItemIndex } from './getItemIndex';
-import { getItemsPerRow } from './getItemsPerRow';
-import { GallerySelectionCountTag } from './ImageGrid/GallerySelectionCountTag';
-import { GalleryVideo } from './ImageGrid/GalleryVideo';
-import { GalleryVideoPlaceholder } from './ImageGrid/GalleryVideoPlaceholder';
-import { scrollIntoView } from './scrollIntoView';
-import { useGalleryVideoIds } from './use-gallery-video-ids';
-import { useScrollableGallery } from './useScrollableGallery';
-
-type ListVideoIdsQueryArgs = ReturnType;
-
-type GridContext = {
- queryArgs: ListVideoIdsQueryArgs;
- videoIds: string[];
-};
-
-const VideoAtPosition = memo(({ videoId }: { index: number; videoId: string }) => {
- /*
- * We rely on the useRangeBasedImageFetching to fetch all image DTOs, caching them with RTK Query.
- *
- * In this component, we just want to consume that cache. Unforutnately, RTK Query does not provide a way to
- * subscribe to a query without triggering a new fetch.
- *
- * There is a hack, though:
- * - https://github.com/reduxjs/redux-toolkit/discussions/4213
- *
- * This essentially means "subscribe to the query once it has some data".
- * One issue with this approach. When an item DTO is already cached - for example, because it is selected and
- * rendered in the viewer - it will show up in the grid before the other items have loaded. This is most
- * noticeable when first loading a board. The first item in the board is selected and rendered immediately in
- * the viewer, caching the DTO. The gallery grid renders, and that first item displays as a thumbnail while the
- * others are still placeholders. After a moment, the rest of the items load up and display as thumbnails.
- */
-
- // Use `currentData` instead of `data` to prevent a flash of previous image rendered at this index
- const { currentData: videoDTO, isUninitialized } = videosApi.endpoints.getVideoDTO.useQueryState(videoId);
- videosApi.endpoints.getVideoDTO.useQuerySubscription(videoId, { skip: isUninitialized });
-
- if (!videoDTO) {
- return ;
- }
-
- return ;
-});
-VideoAtPosition.displayName = 'VideoAtPosition';
-
-const computeItemKey: GridComputeItemKey = (index, itemId, { queryArgs }) => {
- return `${JSON.stringify(queryArgs)}-${itemId ?? index}`;
-};
-
-/**
- * Handles keyboard navigation for the gallery.
- */
-const useKeyboardNavigation = (
- itemIds: string[],
- virtuosoRef: React.RefObject,
- rootRef: React.RefObject
-) => {
- const { dispatch, getState } = useAppStore();
-
- const handleKeyDown = useCallback(
- (event: KeyboardEvent) => {
- if (getFocusedRegion() !== 'gallery') {
- // Only handle keyboard navigation when the gallery is focused
- return;
- }
- // Only handle arrow keys
- if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
- return;
- }
- // Don't interfere if user is typing in an input
- if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {
- return;
- }
-
- const rootEl = rootRef.current;
- const virtuosoGridHandle = virtuosoRef.current;
-
- if (!rootEl || !virtuosoGridHandle) {
- return;
- }
-
- if (itemIds.length === 0) {
- return;
- }
-
- const itemsPerRow = getItemsPerRow(rootEl);
-
- if (itemsPerRow === 0) {
- // This can happen if the grid is not yet rendered or has no items
- return;
- }
-
- event.preventDefault();
-
- const state = getState();
- const itemId = selectLastSelectedItem(state)?.id;
-
- const currentIndex = getItemIndex(itemId, itemIds);
-
- let newIndex = currentIndex;
-
- switch (event.key) {
- case 'ArrowLeft':
- if (currentIndex > 0) {
- newIndex = currentIndex - 1;
- }
- break;
- case 'ArrowRight':
- if (currentIndex < itemIds.length - 1) {
- newIndex = currentIndex + 1;
- }
- break;
- case 'ArrowUp':
- // If on first row, stay on current item
- if (currentIndex < itemsPerRow) {
- newIndex = currentIndex;
- } else {
- newIndex = Math.max(0, currentIndex - itemsPerRow);
- }
- break;
- case 'ArrowDown':
- // If no items below, stay on current item
- if (currentIndex >= itemIds.length - itemsPerRow) {
- newIndex = currentIndex;
- } else {
- newIndex = Math.min(itemIds.length - 1, currentIndex + itemsPerRow);
- }
- break;
- }
-
- if (newIndex !== currentIndex && newIndex >= 0 && newIndex < itemIds.length) {
- const nextItemId = itemIds[newIndex];
- if (nextItemId) {
- dispatch(selectionChanged([{ type: 'video', id: nextItemId }]));
- }
- }
- },
- [rootRef, virtuosoRef, itemIds, getState, dispatch]
- );
-
- useRegisteredHotkeys({
- id: 'galleryNavLeft',
- category: 'gallery',
- callback: handleKeyDown,
- options: { preventDefault: true },
- dependencies: [handleKeyDown],
- });
-
- useRegisteredHotkeys({
- id: 'galleryNavRight',
- category: 'gallery',
- callback: handleKeyDown,
- options: { preventDefault: true },
- dependencies: [handleKeyDown],
- });
-
- useRegisteredHotkeys({
- id: 'galleryNavUp',
- category: 'gallery',
- callback: handleKeyDown,
- options: { preventDefault: true },
- dependencies: [handleKeyDown],
- });
-
- useRegisteredHotkeys({
- id: 'galleryNavDown',
- category: 'gallery',
- callback: handleKeyDown,
- options: { preventDefault: true },
- dependencies: [handleKeyDown],
- });
-
- useRegisteredHotkeys({
- id: 'galleryNavLeftAlt',
- category: 'gallery',
- callback: handleKeyDown,
- options: { preventDefault: true },
- dependencies: [handleKeyDown],
- });
-
- useRegisteredHotkeys({
- id: 'galleryNavRightAlt',
- category: 'gallery',
- callback: handleKeyDown,
- options: { preventDefault: true },
- dependencies: [handleKeyDown],
- });
-
- useRegisteredHotkeys({
- id: 'galleryNavUpAlt',
- category: 'gallery',
- callback: handleKeyDown,
- options: { preventDefault: true },
- dependencies: [handleKeyDown],
- });
-
- useRegisteredHotkeys({
- id: 'galleryNavDownAlt',
- category: 'gallery',
- callback: handleKeyDown,
- options: { preventDefault: true },
- dependencies: [handleKeyDown],
- });
-};
-
-/**
- * Keeps the last selected image in view when the gallery is scrolled.
- * This is useful for keyboard navigation and ensuring the user can see their selection.
- * It only tracks the last selected image, not the image to compare.
- */
-const useKeepSelectedVideoInView = (
- videoIds: string[],
- virtuosoRef: React.RefObject,
- rootRef: React.RefObject,
- rangeRef: MutableRefObject
-) => {
- const targetVideoId = useAppSelector(selectLastSelectedItem)?.id;
-
- useEffect(() => {
- const virtuosoGridHandle = virtuosoRef.current;
- const rootEl = rootRef.current;
- const range = rangeRef.current;
-
- if (!virtuosoGridHandle || !rootEl || !targetVideoId || !videoIds || videoIds.length === 0) {
- return;
- }
- scrollIntoView(targetVideoId, videoIds, rootEl, virtuosoGridHandle, range);
- }, [targetVideoId, videoIds, rangeRef, rootRef, virtuosoRef]);
-};
-
-export const VideoGallery = memo(() => {
- const virtuosoRef = useRef(null);
- const rangeRef = useRef({ startIndex: 0, endIndex: 0 });
- const rootRef = useRef(null);
-
- // Get the ordered list of image names - this is our primary data source for virtualization
- const { queryArgs, videoIds, isLoading } = useGalleryVideoIds();
-
- // Use range-based fetching for bulk loading image DTOs into cache based on the visible range
- const { onRangeChanged } = useRangeBasedVideoFetching({
- videoIds,
- enabled: !isLoading,
- });
-
- useKeepSelectedVideoInView(videoIds, virtuosoRef, rootRef, rangeRef);
- useKeyboardNavigation(videoIds, virtuosoRef, rootRef);
- const scrollerRef = useScrollableGallery(rootRef);
-
- /*
- * We have to keep track of the visible range for keep-selected-image-in-view functionality and push the range to
- * the range-based image fetching hook.
- */
- const handleRangeChanged = useCallback(
- (range: ListRange) => {
- rangeRef.current = range;
- onRangeChanged(range);
- },
- [onRangeChanged]
- );
-
- const context = useMemo(() => ({ videoIds, queryArgs }), [videoIds, queryArgs]);
-
- const isVideoEnabled = useAppSelector(selectAllowVideo);
- const accountTypeText = useStore($accountTypeText);
-
- if (!isVideoEnabled) {
- return (
-
-
- Video generation is not enabled for {accountTypeText} accounts
-
-
- );
- }
-
- if (isLoading) {
- return (
-
-
- Loading gallery...
-
- );
- }
-
- if (videoIds.length === 0) {
- return (
-
- No videos found
-
- );
- }
-
- return (
- // This wrapper component is necessary to initialize the overlay scrollbars!
-
-
- ref={virtuosoRef}
- context={context}
- data={videoIds}
- increaseViewportBy={4096}
- itemContent={itemContent}
- computeItemKey={computeItemKey}
- components={components}
- style={virtuosoGridStyle}
- scrollerRef={scrollerRef}
- scrollSeekConfiguration={scrollSeekConfiguration}
- rangeChanged={handleRangeChanged}
- />
-
-
- );
-});
-
-VideoGallery.displayName = 'VideoGallery';
-
-const scrollSeekConfiguration: ScrollSeekConfiguration = {
- enter: (velocity) => {
- return Math.abs(velocity) > 2048;
- },
- exit: (velocity) => {
- return velocity === 0;
- },
-};
-
-// Styles
-const virtuosoGridStyle = { height: '100%', width: '100%' };
-
-const selectGridTemplateColumns = createSelector(
- selectGalleryImageMinimumWidth,
- (galleryImageMinimumWidth) => `repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, 1fr))`
-);
-
-// Grid components
-const ListComponent: GridComponents['List'] = forwardRef(({ context: _, ...rest }, ref) => {
- const _gridTemplateColumns = useAppSelector(selectGridTemplateColumns);
- const [gridTemplateColumns] = useDebounce(_gridTemplateColumns, 300);
-
- return ;
-});
-ListComponent.displayName = 'ListComponent';
-
-const itemContent: GridItemContent = (index, videoId) => {
- return ;
-};
-
-const ItemComponent: GridComponents['Item'] = forwardRef(({ context: _, ...rest }, ref) => (
-
-));
-ItemComponent.displayName = 'ItemComponent';
-
-const ScrollSeekPlaceholderComponent: GridComponents['ScrollSeekPlaceholder'] = (props) => (
-
-
-
-);
-
-ScrollSeekPlaceholderComponent.displayName = 'ScrollSeekPlaceholderComponent';
-
-const components: GridComponents = {
- Item: ItemComponent,
- List: ListComponent,
- ScrollSeekPlaceholder: ScrollSeekPlaceholderComponent,
-};
diff --git a/invokeai/frontend/web/src/features/gallery/components/use-gallery-video-ids.ts b/invokeai/frontend/web/src/features/gallery/components/use-gallery-video-ids.ts
deleted file mode 100644
index 5bb9c5c0b72..00000000000
--- a/invokeai/frontend/web/src/features/gallery/components/use-gallery-video-ids.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { EMPTY_ARRAY } from 'app/store/constants';
-import { useAppSelector } from 'app/store/storeHooks';
-import { selectGetVideoIdsQueryArgs } from 'features/gallery/store/gallerySelectors';
-import { useGetVideoIdsQuery } from 'services/api/endpoints/videos';
-import { useDebounce } from 'use-debounce';
-
-const getVideoIdsQueryOptions = {
- refetchOnReconnect: true,
- selectFromResult: ({ currentData, isLoading, isFetching }) => ({
- videoIds: currentData?.video_ids ?? EMPTY_ARRAY,
- isLoading,
- isFetching,
- }),
-} satisfies Parameters[1];
-
-export const useGalleryVideoIds = () => {
- const _queryArgs = useAppSelector(selectGetVideoIdsQueryArgs);
- const [queryArgs] = useDebounce(_queryArgs, 300);
- const { videoIds, isLoading, isFetching } = useGetVideoIdsQuery(queryArgs, getVideoIdsQueryOptions);
- return { videoIds, isLoading, isFetching, queryArgs };
-};
diff --git a/invokeai/frontend/web/src/features/gallery/contexts/ImageDTOContext.ts b/invokeai/frontend/web/src/features/gallery/contexts/ImageDTOContext.ts
new file mode 100644
index 00000000000..e4ebbf3ec82
--- /dev/null
+++ b/invokeai/frontend/web/src/features/gallery/contexts/ImageDTOContext.ts
@@ -0,0 +1,13 @@
+import { createContext, useContext } from 'react';
+import type { ImageDTO } from 'services/api/types';
+import { assert } from 'tsafe';
+
+const ImageDTOCOntext = createContext(null);
+
+export const ImageDTOContextProvider = ImageDTOCOntext.Provider;
+
+export const useImageDTOContext = () => {
+ const dto = useContext(ImageDTOCOntext);
+ assert(dto !== null, 'useItemDTOContext must be used within ItemDTOContextProvider');
+ return dto;
+};
diff --git a/invokeai/frontend/web/src/features/gallery/contexts/ItemDTOContext.ts b/invokeai/frontend/web/src/features/gallery/contexts/ItemDTOContext.ts
deleted file mode 100644
index 847a1c5d07f..00000000000
--- a/invokeai/frontend/web/src/features/gallery/contexts/ItemDTOContext.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { createContext, useContext } from 'react';
-import { type ImageDTO, isImageDTO, type VideoDTO } from 'services/api/types';
-import { assert } from 'tsafe';
-
-const ItemDTOContext = createContext(null);
-
-export const ItemDTOContextProvider = ItemDTOContext.Provider;
-
-export const useItemDTOContext = () => {
- const itemDTO = useContext(ItemDTOContext);
- assert(itemDTO !== null, 'useItemDTOContext must be used within ItemDTOContextProvider');
- return itemDTO;
-};
-
-export const useItemDTOContextImageOnly = (): ImageDTO => {
- const itemDTO = useContext(ItemDTOContext);
- assert(itemDTO !== null, 'useItemDTOContext must be used within ItemDTOContextProvider');
- assert(isImageDTO(itemDTO), 'ItemDTO is not an image');
- return itemDTO as ImageDTO;
-};
diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useRangeBasedVideoFetching.ts b/invokeai/frontend/web/src/features/gallery/hooks/useRangeBasedVideoFetching.ts
deleted file mode 100644
index 4808ea6e623..00000000000
--- a/invokeai/frontend/web/src/features/gallery/hooks/useRangeBasedVideoFetching.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { useAppStore } from 'app/store/storeHooks';
-import { useCallback, useEffect, useState } from 'react';
-import type { ListRange } from 'react-virtuoso';
-import { useGetVideoDTOsByNamesMutation, videosApi } from 'services/api/endpoints/videos';
-import { useThrottledCallback } from 'use-debounce';
-
-interface UseRangeBasedVideoFetchingArgs {
- videoIds: string[];
- enabled: boolean;
-}
-
-interface UseRangeBasedVideoFetchingReturn {
- onRangeChanged: (range: ListRange) => void;
-}
-
-const getUncachedIds = (videoIds: string[], cachedVideoIds: string[], ranges: ListRange[]): string[] => {
- const uncachedIdsSet = new Set();
- const cachedVideoIdsSet = new Set(cachedVideoIds);
-
- for (const range of ranges) {
- for (let i = range.startIndex; i <= range.endIndex; i++) {
- const id = videoIds[i]!;
- if (id && !cachedVideoIdsSet.has(id)) {
- uncachedIdsSet.add(id);
- }
- }
- }
-
- return Array.from(uncachedIdsSet);
-};
-
-/**
- * Hook for bulk fetching image DTOs based on the visible range from virtuoso.
- * Individual image components should use `useGetImageDTOQuery(imageName)` to get their specific DTO.
- * This hook ensures DTOs are bulk fetched and cached efficiently.
- */
-export const useRangeBasedVideoFetching = ({
- videoIds,
- enabled,
-}: UseRangeBasedVideoFetchingArgs): UseRangeBasedVideoFetchingReturn => {
- const store = useAppStore();
- const [getVideoDTOsByNames] = useGetVideoDTOsByNamesMutation();
- const [lastRange, setLastRange] = useState(null);
- const [pendingRanges, setPendingRanges] = useState([]);
-
- const fetchVideos = useCallback(
- (ranges: ListRange[], videoIds: string[]) => {
- if (!enabled) {
- return;
- }
- const cachedVideoIds = videosApi.util.selectCachedArgsForQuery(store.getState(), 'getVideoDTO');
- const uncachedIds = getUncachedIds(videoIds, cachedVideoIds, ranges);
- // console.log('uncachedIds', uncachedIds);
- if (uncachedIds.length === 0) {
- return;
- }
- getVideoDTOsByNames({ video_ids: uncachedIds });
- setPendingRanges([]);
- },
- [enabled, getVideoDTOsByNames, store]
- );
-
- const throttledFetchVideos = useThrottledCallback(fetchVideos, 500);
-
- const onRangeChanged = useCallback((range: ListRange) => {
- setLastRange(range);
- setPendingRanges((prev) => [...prev, range]);
- }, []);
-
- useEffect(() => {
- const combinedRanges = lastRange ? [...pendingRanges, lastRange] : pendingRanges;
- throttledFetchVideos(combinedRanges, videoIds);
- }, [videoIds, lastRange, pendingRanges, throttledFetchVideos]);
-
- return {
- onRangeChanged,
- };
-};
diff --git a/invokeai/frontend/web/src/features/gallery/store/actions.ts b/invokeai/frontend/web/src/features/gallery/store/actions.ts
deleted file mode 100644
index 8d13c449369..00000000000
--- a/invokeai/frontend/web/src/features/gallery/store/actions.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { createAction } from '@reduxjs/toolkit';
-import type { ImageDTO } from 'services/api/types';
-
-export const sentImageToCanvas = createAction('gallery/sentImageToCanvas');
-
-export const imageDownloaded = createAction('gallery/imageDownloaded');
-
-export const imageCopiedToClipboard = createAction('gallery/imageCopiedToClipboard');
-
-export const imageOpenedInNewTab = createAction('gallery/imageOpenedInNewTab');
-
-export const imageUploadedClientSide = createAction<{
- imageDTO: ImageDTO;
- silent: boolean;
- isFirstUploadOfBatch: boolean;
-}>('gallery/imageUploadedClientSide');
diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
index 536fbd6d2a7..ab6b584a15e 100644
--- a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
@@ -2,7 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { selectGallerySlice } from 'features/gallery/store/gallerySlice';
import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types';
-import type { GetImageNamesArgs, GetVideoIdsArgs, ListBoardsArgs } from 'services/api/types';
+import type { GetImageNamesArgs, ListBoardsArgs } from 'services/api/types';
export const selectFirstSelectedItem = createSelector(selectGallerySlice, (gallery) => gallery.selection.at(0));
export const selectLastSelectedItem = createSelector(selectGallerySlice, (gallery) => gallery.selection.at(-1));
@@ -51,17 +51,6 @@ export const selectGetImageNamesQueryArgs = createMemoizedSelector(
})
);
-export const selectGetVideoIdsQueryArgs = createMemoizedSelector(
- [selectSelectedBoardId, selectGallerySearchTerm, selectGalleryOrderDir, selectGalleryStarredFirst],
- (board_id, search_term, order_dir, starred_first): GetVideoIdsArgs => ({
- board_id,
- search_term,
- order_dir,
- starred_first,
- is_intermediate: false,
- })
-);
-
export const selectAutoAssignBoardOnClick = createSelector(
selectGallerySlice,
(gallery) => gallery.autoAssignBoardOnClick
diff --git a/invokeai/frontend/web/src/features/imageActions/actions.ts b/invokeai/frontend/web/src/features/imageActions/actions.ts
index 14d27e900c1..2c9293127b4 100644
--- a/invokeai/frontend/web/src/features/imageActions/actions.ts
+++ b/invokeai/frontend/web/src/features/imageActions/actions.ts
@@ -42,7 +42,6 @@ import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
import { navigationApi } from 'features/ui/layouts/navigation-api';
import { WORKSPACE_PANEL_ID } from 'features/ui/layouts/shared';
import { imageDTOToFile, imagesApi, uploadImage } from 'services/api/endpoints/images';
-import { videosApi } from 'services/api/endpoints/videos';
import type { ImageDTO } from 'services/api/types';
import type { Equals } from 'tsafe';
import { assert } from 'tsafe';
@@ -324,15 +323,3 @@ export const removeImagesFromBoard = (arg: { image_names: string[]; dispatch: Ap
dispatch(imagesApi.endpoints.removeImagesFromBoard.initiate({ image_names }, { track: false }));
dispatch(selectionChanged([]));
};
-
-export const addVideosToBoard = (arg: { video_ids: string[]; boardId: BoardId; dispatch: AppDispatch }) => {
- const { video_ids, boardId, dispatch } = arg;
- dispatch(videosApi.endpoints.addVideosToBoard.initiate({ video_ids, board_id: boardId }, { track: false }));
- dispatch(selectionChanged([]));
-};
-
-export const removeVideosFromBoard = (arg: { video_ids: string[]; dispatch: AppDispatch }) => {
- const { video_ids, dispatch } = arg;
- dispatch(videosApi.endpoints.removeVideosFromBoard.initiate({ video_ids }, { track: false }));
- dispatch(selectionChanged([]));
-};
diff --git a/invokeai/frontend/web/src/features/lora/components/LoRASelect.tsx b/invokeai/frontend/web/src/features/lora/components/LoRASelect.tsx
index f0d1ffe878b..748aa9ca65c 100644
--- a/invokeai/frontend/web/src/features/lora/components/LoRASelect.tsx
+++ b/invokeai/frontend/web/src/features/lora/components/LoRASelect.tsx
@@ -6,7 +6,6 @@ import { InformationalPopover } from 'common/components/InformationalPopover/Inf
import type { GroupStatusMap } from 'common/components/Picker/Picker';
import { loraAdded, selectLoRAsSlice } from 'features/controlLayers/store/lorasSlice';
import { selectBase } from 'features/controlLayers/store/paramsSlice';
-import { API_BASE_MODELS } from 'features/modelManagerV2/models';
import { ModelPicker } from 'features/parameters/components/ModelPicker';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -69,11 +68,8 @@ const LoRASelect = () => {
return undefined;
}
- // Determine the group ID for the current base model
- const groupId = API_BASE_MODELS.includes(currentBaseModel) ? 'api' : currentBaseModel;
-
// Return a map with only the current base model group enabled
- return { [groupId]: true } satisfies GroupStatusMap;
+ return { [currentBaseModel]: true } satisfies GroupStatusMap;
}, [currentBaseModel]);
return (
diff --git a/invokeai/frontend/web/src/features/metadata/parsing.tsx b/invokeai/frontend/web/src/features/metadata/parsing.tsx
index a2363004ef3..d964279c2aa 100644
--- a/invokeai/frontend/web/src/features/metadata/parsing.tsx
+++ b/invokeai/frontend/web/src/features/metadata/parsing.tsx
@@ -33,32 +33,12 @@ import {
widthChanged,
} from 'features/controlLayers/store/paramsSlice';
import { refImagesRecalled } from 'features/controlLayers/store/refImagesSlice';
-import type {
- CanvasMetadata,
- LoRA,
- RefImageState,
- VideoAspectRatio as ParameterVideoAspectRatio,
- VideoDuration as ParameterVideoDuration,
- VideoResolution as ParameterVideoResolution,
-} from 'features/controlLayers/store/types';
-import {
- zCanvasMetadata,
- zCanvasReferenceImageState_OLD,
- zRefImageState,
- zVideoAspectRatio,
- zVideoDuration,
- zVideoResolution,
-} from 'features/controlLayers/store/types';
+import type { CanvasMetadata, LoRA, RefImageState } from 'features/controlLayers/store/types';
+import { zCanvasMetadata, zCanvasReferenceImageState_OLD, zRefImageState } from 'features/controlLayers/store/types';
import type { ModelIdentifierField, ModelType } from 'features/nodes/types/common';
import { zModelIdentifierField } from 'features/nodes/types/common';
import { zModelIdentifier } from 'features/nodes/types/v2/common';
import { modelSelected } from 'features/parameters/store/actions';
-import {
- videoAspectRatioChanged,
- videoDurationChanged,
- videoModelChanged,
- videoResolutionChanged,
-} from 'features/parameters/store/videoSlice';
import type {
ParameterCFGRescaleMultiplier,
ParameterCFGScale,
@@ -714,87 +694,6 @@ const VAEModel: SingleMetadataHandler = {
};
//#endregion VAEModel
-//#region VideoModel
-const VideoModel: SingleMetadataHandler = {
- [SingleMetadataKey]: true,
- type: 'VideoModel',
- parse: async (metadata, store) => {
- const raw = getProperty(metadata, 'model');
- const parsed = await parseModelIdentifier(raw, store, 'video');
- assert(parsed.type === 'video');
- return Promise.resolve(parsed);
- },
- recall: (value, store) => {
- store.dispatch(videoModelChanged({ videoModel: value }));
- },
- i18nKey: 'metadata.videoModel',
- LabelComponent: MetadataLabel,
- ValueComponent: ({ value }: SingleMetadataValueProps) => (
-
- ),
-};
-//#endregion VideoModel
-
-//#region VideoDuration
-const VideoDuration: SingleMetadataHandler = {
- [SingleMetadataKey]: true,
- type: 'VideoDuration',
- parse: (metadata) => {
- const raw = getProperty(metadata, 'duration');
- const parsed = zVideoDuration.parse(raw);
- return Promise.resolve(parsed);
- },
- recall: (value, store) => {
- store.dispatch(videoDurationChanged(value));
- },
- i18nKey: 'metadata.videoDuration',
- LabelComponent: MetadataLabel,
- ValueComponent: ({ value }: SingleMetadataValueProps) => (
-
- ),
-};
-//#endregion VideoDuration
-
-//#region VideoResolution
-const VideoResolution: SingleMetadataHandler = {
- [SingleMetadataKey]: true,
- type: 'VideoResolution',
- parse: (metadata) => {
- const raw = getProperty(metadata, 'resolution');
- const parsed = zVideoResolution.parse(raw);
- return Promise.resolve(parsed);
- },
- recall: (value, store) => {
- store.dispatch(videoResolutionChanged(value));
- },
- i18nKey: 'metadata.videoResolution',
- LabelComponent: MetadataLabel,
- ValueComponent: ({ value }: SingleMetadataValueProps) => (
-
- ),
-};
-//#endregion VideoResolution
-
-//#region VideoAspectRatio
-const VideoAspectRatio: SingleMetadataHandler = {
- [SingleMetadataKey]: true,
- type: 'VideoAspectRatio',
- parse: (metadata) => {
- const raw = getProperty(metadata, 'aspect_ratio');
- const parsed = zVideoAspectRatio.parse(raw);
- return Promise.resolve(parsed);
- },
- recall: (value, store) => {
- store.dispatch(videoAspectRatioChanged(value));
- },
- i18nKey: 'metadata.videoAspectRatio',
- LabelComponent: MetadataLabel,
- ValueComponent: ({ value }: SingleMetadataValueProps) => (
-
- ),
-};
-//#endregion VideoAspectRatio
-
//#region LoRAs
const LoRAs: CollectionMetadataHandler = {
[CollectionMetadataKey]: true,
@@ -1044,17 +943,6 @@ export const ImageMetadataHandlers = {
// ipAdapterToIPAdapterLayer: parseIPAdapterToIPAdapterLayer,
} as const;
-export const VideoMetadataHandlers = {
- CreatedBy,
- GenerationMode,
- PositivePrompt,
- VideoModel,
- Seed,
- VideoAspectRatio,
- VideoDuration,
- VideoResolution,
-};
-
const successToast = (parameter: string) => {
toast({
id: 'PARAMETER_SET',
diff --git a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useMainModelDefaultSettings.ts b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useMainModelDefaultSettings.ts
index f3a1d0c6434..dfab2d251f9 100644
--- a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useMainModelDefaultSettings.ts
+++ b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useMainModelDefaultSettings.ts
@@ -1,88 +1,48 @@
-import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
-import { useAppSelector } from 'app/store/storeHooks';
import { isNil } from 'es-toolkit/compat';
-import { selectConfigSlice } from 'features/system/store/configSlice';
import { useMemo } from 'react';
import type { MainModelConfig } from 'services/api/types';
-const initialStatesSelector = createMemoizedSelector(selectConfigSlice, (config) => {
- const { steps, guidance, scheduler, cfgRescaleMultiplier, vaePrecision, width, height } = config.sd;
- const { guidance: fluxGuidance } = config.flux;
-
- return {
- initialSteps: steps.initial,
- initialCfg: guidance.initial,
- initialScheduler: scheduler,
- initialCfgRescaleMultiplier: cfgRescaleMultiplier.initial,
- initialVaePrecision: vaePrecision,
- initialWidth: width.initial,
- initialHeight: height.initial,
- initialGuidance: fluxGuidance.initial,
- };
-});
-
export const useMainModelDefaultSettings = (modelConfig: MainModelConfig) => {
- const {
- initialSteps,
- initialCfg,
- initialScheduler,
- initialCfgRescaleMultiplier,
- initialVaePrecision,
- initialWidth,
- initialHeight,
- initialGuidance,
- } = useAppSelector(initialStatesSelector);
-
const defaultSettingsDefaults = useMemo(() => {
return {
vae: {
isEnabled: !isNil(modelConfig?.default_settings?.vae),
- value: modelConfig?.default_settings?.vae || 'default',
+ value: modelConfig?.default_settings?.vae ?? 'default',
},
vaePrecision: {
isEnabled: !isNil(modelConfig?.default_settings?.vae_precision),
- value: modelConfig?.default_settings?.vae_precision || initialVaePrecision || 'fp32',
+ value: modelConfig?.default_settings?.vae_precision ?? 'fp32',
},
scheduler: {
isEnabled: !isNil(modelConfig?.default_settings?.scheduler),
- value: modelConfig?.default_settings?.scheduler || initialScheduler || 'dpmpp_3m_k',
+ value: modelConfig?.default_settings?.scheduler ?? 'dpmpp_3m_k',
},
steps: {
isEnabled: !isNil(modelConfig?.default_settings?.steps),
- value: modelConfig?.default_settings?.steps || initialSteps,
+ value: modelConfig?.default_settings?.steps ?? 30,
},
cfgScale: {
isEnabled: !isNil(modelConfig?.default_settings?.cfg_scale),
- value: modelConfig?.default_settings?.cfg_scale || initialCfg,
+ value: modelConfig?.default_settings?.cfg_scale ?? 7,
},
cfgRescaleMultiplier: {
isEnabled: !isNil(modelConfig?.default_settings?.cfg_rescale_multiplier),
- value: modelConfig?.default_settings?.cfg_rescale_multiplier || initialCfgRescaleMultiplier,
+ value: modelConfig?.default_settings?.cfg_rescale_multiplier ?? 0,
},
width: {
isEnabled: !isNil(modelConfig?.default_settings?.width),
- value: modelConfig?.default_settings?.width || initialWidth,
+ value: modelConfig?.default_settings?.width ?? 512,
},
height: {
isEnabled: !isNil(modelConfig?.default_settings?.height),
- value: modelConfig?.default_settings?.height || initialHeight,
+ value: modelConfig?.default_settings?.height ?? 512,
},
guidance: {
isEnabled: !isNil(modelConfig?.default_settings?.guidance),
- value: modelConfig?.default_settings?.guidance || initialGuidance,
+ value: modelConfig?.default_settings?.guidance ?? 4,
},
};
- }, [
- modelConfig,
- initialVaePrecision,
- initialScheduler,
- initialSteps,
- initialCfg,
- initialCfgRescaleMultiplier,
- initialWidth,
- initialHeight,
- initialGuidance,
- ]);
+ }, [modelConfig]);
return defaultSettingsDefaults;
};
diff --git a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useStarterModelsToast.tsx b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useStarterModelsToast.tsx
index c3de0dd3fb5..f7e91af62fd 100644
--- a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useStarterModelsToast.tsx
+++ b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useStarterModelsToast.tsx
@@ -1,6 +1,5 @@
import { Button, Text, useToast } from '@invoke-ai/ui-library';
import { setInstallModelsTabByName } from 'features/modelManagerV2/store/installModelsStore';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { navigationApi } from 'features/ui/layouts/navigation-api';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -10,7 +9,6 @@ const TOAST_ID = 'starterModels';
export const useStarterModelsToast = () => {
const { t } = useTranslation();
- const isEnabled = useFeatureStatus('starterModels');
const [didToast, setDidToast] = useState(false);
const [mainModels, { data }] = useMainModels();
const toast = useToast();
@@ -23,7 +21,7 @@ export const useStarterModelsToast = () => {
toast.close(TOAST_ID);
}
}
- if (data && mainModels.length === 0 && !didToast && isEnabled) {
+ if (data && mainModels.length === 0 && !didToast) {
toast({
id: TOAST_ID,
title: t('modelManager.noModelsInstalled'),
@@ -34,7 +32,7 @@ export const useStarterModelsToast = () => {
onCloseComplete: () => setDidToast(true),
});
}
- }, [data, didToast, isEnabled, mainModels.length, t, toast]);
+ }, [data, didToast, mainModels.length, t, toast]);
};
const ToastDescription = () => {
diff --git a/invokeai/frontend/web/src/features/modelManagerV2/models.ts b/invokeai/frontend/web/src/features/modelManagerV2/models.ts
index 0b4096e010b..11b19b3937f 100644
--- a/invokeai/frontend/web/src/features/modelManagerV2/models.ts
+++ b/invokeai/frontend/web/src/features/modelManagerV2/models.ts
@@ -18,7 +18,6 @@ import {
isTIModelConfig,
isUnknownModelConfig,
isVAEModelConfig,
- isVideoModelConfig,
} from 'services/api/types';
import { objectEntries } from 'tsafe';
@@ -116,11 +115,6 @@ export const MODEL_CATEGORIES: Record =
i18nKey: 'modelManager.llavaOnevision',
filter: isLLaVAModelConfig,
},
- video: {
- category: 'video',
- i18nKey: 'Video',
- filter: isVideoModelConfig,
- },
};
export const MODEL_CATEGORIES_AS_LIST = objectEntries(MODEL_CATEGORIES).map(([category, { i18nKey, filter }]) => ({
@@ -141,13 +135,6 @@ export const MODEL_BASE_TO_COLOR: Record = {
'sdxl-refiner': 'invokeBlue',
flux: 'gold',
cogview4: 'red',
- imagen3: 'pink',
- imagen4: 'pink',
- 'chatgpt-4o': 'pink',
- 'flux-kontext': 'pink',
- 'gemini-2.5': 'pink',
- veo3: 'purple',
- runway: 'green',
unknown: 'red',
};
@@ -171,7 +158,6 @@ export const MODEL_TYPE_TO_LONG_NAME: Record = {
clip_embed: 'CLIP Embed',
siglip: 'SigLIP',
flux_redux: 'FLUX Redux',
- video: 'Video',
unknown: 'Unknown',
};
@@ -187,13 +173,6 @@ export const MODEL_BASE_TO_LONG_NAME: Record = {
'sdxl-refiner': 'Stable Diffusion XL Refiner',
flux: 'FLUX',
cogview4: 'CogView4',
- imagen3: 'Imagen3',
- imagen4: 'Imagen4',
- 'chatgpt-4o': 'ChatGPT 4o',
- 'flux-kontext': 'Flux Kontext',
- 'gemini-2.5': 'Gemini 2.5',
- veo3: 'Veo3',
- runway: 'Runway',
unknown: 'Unknown',
};
@@ -209,13 +188,6 @@ export const MODEL_BASE_TO_SHORT_NAME: Record = {
'sdxl-refiner': 'SDXLR',
flux: 'FLUX',
cogview4: 'CogView4',
- imagen3: 'Imagen3',
- imagen4: 'Imagen4',
- 'chatgpt-4o': 'ChatGPT 4o',
- 'flux-kontext': 'Flux Kontext',
- 'gemini-2.5': 'Gemini 2.5',
- veo3: 'Veo3',
- runway: 'Runway',
unknown: 'Unknown',
};
@@ -244,60 +216,11 @@ export const MODEL_FORMAT_TO_LONG_NAME: Record = {
bnb_quantized_int8b: 'BNB Quantized (int8b)',
bnb_quantized_nf4b: 'BNB Quantized (nf4b)',
gguf_quantized: 'GGUF Quantized',
- api: 'API',
unknown: 'Unknown',
};
-/**
- * List of base models that make API requests
- */
-export const API_BASE_MODELS: BaseModelType[] = ['imagen3', 'imagen4', 'chatgpt-4o', 'flux-kontext', 'gemini-2.5'];
-
-export const SUPPORTS_SEED_BASE_MODELS: BaseModelType[] = ['sd-1', 'sd-2', 'sd-3', 'sdxl', 'flux', 'cogview4'];
-
export const SUPPORTS_OPTIMIZED_DENOISING_BASE_MODELS: BaseModelType[] = ['flux', 'sd-3'];
-export const SUPPORTS_REF_IMAGES_BASE_MODELS: BaseModelType[] = [
- 'sd-1',
- 'sdxl',
- 'flux',
- 'flux-kontext',
- 'chatgpt-4o',
- 'gemini-2.5',
-];
-
-export const SUPPORTS_NEGATIVE_PROMPT_BASE_MODELS: BaseModelType[] = [
- 'sd-1',
- 'sd-2',
- 'sdxl',
- 'cogview4',
- 'sd-3',
- 'imagen3',
- 'imagen4',
-];
-
-export const SUPPORTS_PIXEL_DIMENSIONS_BASE_MODELS: BaseModelType[] = [
- 'sd-1',
- 'sd-2',
- 'sd-3',
- 'sdxl',
- 'flux',
- 'cogview4',
-];
-
-export const SUPPORTS_ASPECT_RATIO_BASE_MODELS: BaseModelType[] = [
- 'sd-1',
- 'sd-2',
- 'sd-3',
- 'sdxl',
- 'flux',
- 'cogview4',
- 'imagen3',
- 'imagen4',
- 'flux-kontext',
- 'chatgpt-4o',
-];
-
-export const VIDEO_BASE_MODELS = ['veo3', 'runway'];
+export const SUPPORTS_REF_IMAGES_BASE_MODELS: BaseModelType[] = ['sd-1', 'sdxl', 'flux'];
-export const REQUIRES_STARTING_FRAME_BASE_MODELS = ['runway'];
+export const SUPPORTS_NEGATIVE_PROMPT_BASE_MODELS: BaseModelType[] = ['sd-1', 'sd-2', 'sdxl', 'cogview4', 'sd-3'];
diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HFToken.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HFToken.tsx
index cdf8bceaf97..5d68f3fdc5b 100644
--- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HFToken.tsx
+++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HFToken.tsx
@@ -8,8 +8,6 @@ import {
FormLabel,
Input,
} from '@invoke-ai/ui-library';
-import { skipToken } from '@reduxjs/toolkit/query';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { toast } from 'features/toast/toast';
import type { ChangeEvent } from 'react';
import { memo, useCallback, useMemo, useState } from 'react';
@@ -24,8 +22,7 @@ import { assert } from 'tsafe';
export const HFToken = () => {
const { t } = useTranslation();
- const isHFTokenEnabled = useFeatureStatus('hfToken');
- const { currentData } = useGetHFTokenStatusQuery(isHFTokenEnabled ? undefined : skipToken);
+ const { currentData } = useGetHFTokenStatusQuery();
const error = useMemo(() => {
switch (currentData) {
diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceForm.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceForm.tsx
index 0875256d463..ba1d80aa007 100644
--- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceForm.tsx
+++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/HuggingFaceFolder/HuggingFaceForm.tsx
@@ -1,6 +1,5 @@
import { Button, Flex, FormControl, FormErrorMessage, FormHelperText, FormLabel, Input } from '@invoke-ai/ui-library';
import { useInstallModel } from 'features/modelManagerV2/hooks/useInstallModel';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import type { ChangeEventHandler } from 'react';
import { memo, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -14,7 +13,6 @@ export const HuggingFaceForm = memo(() => {
const [displayResults, setDisplayResults] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const { t } = useTranslation();
- const isHFTokenEnabled = useFeatureStatus('hfToken');
const [_getHuggingFaceModels, { isLoading, data }] = useLazyGetHuggingFaceModelsQuery();
const [installModel] = useInstallModel();
@@ -66,7 +64,7 @@ export const HuggingFaceForm = memo(() => {
{t('modelManager.huggingFaceHelper')}
{!!errorMessage.length && {errorMessage}}
- {isHFTokenEnabled && }
+
{data && data.urls && displayResults && }
);
diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelFormatBadge.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelFormatBadge.tsx
index e139639f1f0..2d0192425dc 100644
--- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelFormatBadge.tsx
+++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelFormatBadge.tsx
@@ -17,7 +17,6 @@ const FORMAT_NAME_MAP: Record = {
bnb_quantized_int8b: 'bnb_quantized_int8b',
bnb_quantized_nf4b: 'quantized',
gguf_quantized: 'gguf',
- api: 'api',
omi: 'omi',
unknown: 'unknown',
olive: 'olive',
@@ -36,7 +35,6 @@ const FORMAT_COLOR_MAP: Record = {
bnb_quantized_int8b: 'base',
bnb_quantized_nf4b: 'base',
gguf_quantized: 'base',
- api: 'base',
unknown: 'red',
olive: 'base',
onnx: 'base',
diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgRescaleMultiplier.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgRescaleMultiplier.tsx
index 2fa9580e421..ca7684ae859 100644
--- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgRescaleMultiplier.tsx
+++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgRescaleMultiplier.tsx
@@ -1,8 +1,7 @@
import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
-import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle';
-import { selectCFGRescaleMultiplierConfig } from 'features/system/store/configSlice';
+import { CONSTRAINTS } from 'features/parameters/components/Advanced/ParamCFGRescaleMultiplier';
import { memo, useCallback, useMemo } from 'react';
import type { UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
@@ -15,12 +14,7 @@ type DefaultCfgRescaleMultiplierType = MainModelDefaultSettingsFormData['cfgResc
export const DefaultCfgRescaleMultiplier = memo((props: UseControllerProps) => {
const { field } = useController(props);
- const config = useAppSelector(selectCFGRescaleMultiplierConfig);
const { t } = useTranslation();
- const marks = useMemo(
- () => [config.sliderMin, Math.floor(config.sliderMax / 2), config.sliderMax],
- [config.sliderMax, config.sliderMin]
- );
const onChange = useCallback(
(v: number) => {
@@ -53,20 +47,20 @@ export const DefaultCfgRescaleMultiplier = memo((props: UseControllerProps
diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgScale.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgScale.tsx
index 78558243ae6..76f698b8f7b 100644
--- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgScale.tsx
+++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultCfgScale.tsx
@@ -1,8 +1,7 @@
import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
-import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle';
-import { selectCFGScaleConfig } from 'features/system/store/configSlice';
+import { CONSTRAINTS, MARKS } from 'features/parameters/components/Core/ParamCFGScale';
import { memo, useCallback, useMemo } from 'react';
import type { UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
@@ -15,12 +14,7 @@ type DefaultCfgType = MainModelDefaultSettingsFormData['cfgScale'];
export const DefaultCfgScale = memo((props: UseControllerProps) => {
const { field } = useController(props);
- const config = useAppSelector(selectCFGScaleConfig);
const { t } = useTranslation();
- const marks = useMemo(
- () => [config.sliderMin, Math.floor(config.sliderMax / 2), config.sliderMax],
- [config.sliderMax, config.sliderMin]
- );
const onChange = useCallback(
(v: number) => {
@@ -53,20 +47,20 @@ export const DefaultCfgScale = memo((props: UseControllerProps
diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultGuidance.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultGuidance.tsx
index cb939a268cd..df8c62cbf8a 100644
--- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultGuidance.tsx
+++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultGuidance.tsx
@@ -1,8 +1,7 @@
import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
-import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle';
-import { selectGuidanceConfig } from 'features/system/store/configSlice';
+import { CONSTRAINTS, MARKS } from 'features/parameters/components/Core/ParamGuidance';
import { memo, useCallback, useMemo } from 'react';
import type { UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
@@ -15,16 +14,7 @@ type DefaultGuidanceType = MainModelDefaultSettingsFormData['guidance'];
export const DefaultGuidance = memo((props: UseControllerProps) => {
const { field } = useController(props);
- const config = useAppSelector(selectGuidanceConfig);
const { t } = useTranslation();
- const marks = useMemo(
- () => [
- config.sliderMin,
- Math.floor(config.sliderMax - (config.sliderMax - config.sliderMin) / 2),
- config.sliderMax,
- ],
- [config.sliderMax, config.sliderMin]
- );
const onChange = useCallback(
(v: number) => {
@@ -57,20 +47,20 @@ export const DefaultGuidance = memo((props: UseControllerProps
diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultHeight.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultHeight.tsx
index 78706b8223d..7603007d5a0 100644
--- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultHeight.tsx
+++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultHeight.tsx
@@ -1,8 +1,7 @@
import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
-import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle';
-import { selectHeightConfig } from 'features/system/store/configSlice';
+import { CONSTRAINTS } from 'features/parameters/components/Dimensions/DimensionsHeight';
import { memo, useCallback, useMemo } from 'react';
import type { UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
@@ -19,12 +18,8 @@ type Props = {
export const DefaultHeight = memo(({ control, optimalDimension }: Props) => {
const { field } = useController({ control, name: 'height' });
- const config = useAppSelector(selectHeightConfig);
const { t } = useTranslation();
- const marks = useMemo(
- () => [config.sliderMin, optimalDimension, config.sliderMax],
- [config.sliderMin, optimalDimension, config.sliderMax]
- );
+ const marks = useMemo(() => [CONSTRAINTS.sliderMin, optimalDimension, CONSTRAINTS.sliderMax], [optimalDimension]);
const onChange = useCallback(
(v: number) => {
@@ -57,20 +52,20 @@ export const DefaultHeight = memo(({ control, optimalDimension }: Props) => {
diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultSteps.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultSteps.tsx
index 42afa6a1074..0e052f569df 100644
--- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultSteps.tsx
+++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultSteps.tsx
@@ -1,8 +1,7 @@
import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
-import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle';
-import { selectStepsConfig } from 'features/system/store/configSlice';
+import { CONSTRAINTS, MARKS } from 'features/parameters/components/Core/ParamSteps';
import { memo, useCallback, useMemo } from 'react';
import type { UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
@@ -15,12 +14,7 @@ type DefaultSteps = MainModelDefaultSettingsFormData['steps'];
export const DefaultSteps = memo((props: UseControllerProps) => {
const { field } = useController(props);
- const config = useAppSelector(selectStepsConfig);
const { t } = useTranslation();
- const marks = useMemo(
- () => [config.sliderMin, Math.floor(config.sliderMax / 2), config.sliderMax],
- [config.sliderMax, config.sliderMin]
- );
const onChange = useCallback(
(v: number) => {
@@ -53,20 +47,20 @@ export const DefaultSteps = memo((props: UseControllerProps
diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultWidth.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultWidth.tsx
index 66467617d02..4ffc6e8f8fb 100644
--- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultWidth.tsx
+++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultWidth.tsx
@@ -1,8 +1,7 @@
import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
-import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { SettingToggle } from 'features/modelManagerV2/subpanels/ModelPanel/SettingToggle';
-import { selectWidthConfig } from 'features/system/store/configSlice';
+import { CONSTRAINTS } from 'features/parameters/components/Dimensions/DimensionsWidth';
import { memo, useCallback, useMemo } from 'react';
import type { UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
@@ -19,12 +18,8 @@ type Props = {
export const DefaultWidth = memo(({ control, optimalDimension }: Props) => {
const { field } = useController({ control, name: 'width' });
- const config = useAppSelector(selectWidthConfig);
const { t } = useTranslation();
- const marks = useMemo(
- () => [config.sliderMin, optimalDimension, config.sliderMax],
- [config.sliderMin, optimalDimension, config.sliderMax]
- );
+ const marks = useMemo(() => [CONSTRAINTS.sliderMin, optimalDimension, CONSTRAINTS.sliderMax], [optimalDimension]);
const onChange = useCallback(
(v: number) => {
@@ -57,20 +52,20 @@ export const DefaultWidth = memo(({ control, optimalDimension }: Props) => {
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/AddNodeCmdk/AddNodeCmdk.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/AddNodeCmdk/AddNodeCmdk.tsx
index 55c683029fd..a9a502e7795 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/AddNodeCmdk/AddNodeCmdk.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/AddNodeCmdk/AddNodeCmdk.tsx
@@ -19,7 +19,6 @@ import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import { memoize } from 'es-toolkit/compat';
import { useBuildNode } from 'features/nodes/hooks/useBuildNode';
-import { useIsWorkflowEditorLocked } from 'features/nodes/hooks/useIsWorkflowEditorLocked';
import {
$addNodeCmdk,
$cursorPos,
@@ -147,7 +146,6 @@ export const AddNodeCmdk = memo(() => {
const [searchTerm, setSearchTerm] = useState('');
const addNode = useAddNode();
const tab = useAppSelector(selectActiveTab);
- const isLocked = useIsWorkflowEditorLocked();
// Filtering the list is expensive - debounce the search term to avoid stutters
const [debouncedSearchTerm] = useDebounce(searchTerm, 300);
const isOpen = useStore($addNodeCmdk);
@@ -162,8 +160,8 @@ export const AddNodeCmdk = memo(() => {
id: 'addNode',
category: 'workflows',
callback: open,
- options: { enabled: tab === 'workflows' && !isLocked, preventDefault: true },
- dependencies: [open, tab, isLocked],
+ options: { enabled: tab === 'workflows', preventDefault: true },
+ dependencies: [open, tab],
});
const onChange = useCallback((e: ChangeEvent) => {
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx
index c6f29095e44..f6474dec74b 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx
@@ -4,7 +4,6 @@ import type {
EdgeChange,
HandleType,
NodeChange,
- NodeMouseHandler,
OnEdgesChange,
OnInit,
OnMoveEnd,
@@ -23,10 +22,8 @@ import {
} from '@xyflow/react';
import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHooks';
import { useFocusRegion, useIsRegionFocused } from 'common/hooks/focus';
-import { $isSelectingOutputNode, $outputNodeId } from 'features/nodes/components/sidePanel/workflow/publish';
import { useConnection } from 'features/nodes/hooks/useConnection';
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
-import { useIsWorkflowEditorLocked } from 'features/nodes/hooks/useIsWorkflowEditorLocked';
import { useNodeCopyPaste } from 'features/nodes/hooks/useNodeCopyPaste';
import {
$addNodeCmdk,
@@ -52,7 +49,7 @@ import {
import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
import { selectSelectionMode, selectShouldSnapToGrid } from 'features/nodes/store/workflowSettingsSlice';
import { NO_DRAG_CLASS, NO_PAN_CLASS, NO_WHEEL_CLASS } from 'features/nodes/types/constants';
-import { type AnyEdge, type AnyNode, isInvocationNode } from 'features/nodes/types/invocation';
+import type { AnyEdge, AnyNode } from 'features/nodes/types/invocation';
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
import type { CSSProperties, MouseEvent } from 'react';
import { memo, useCallback, useMemo, useRef } from 'react';
@@ -94,7 +91,6 @@ export const Flow = memo(() => {
const flowWrapper = useRef(null);
const isValidConnection = useIsValidConnection();
const updateNodeInternals = useUpdateNodeInternals();
- const isLocked = useIsWorkflowEditorLocked();
useFocusRegion('workflows', flowWrapper);
@@ -212,18 +208,6 @@ export const Flow = memo(() => {
// #endregion
- const onNodeClick = useCallback>((e, node) => {
- if (!$isSelectingOutputNode.get()) {
- return;
- }
- if (!isInvocationNode(node)) {
- return;
- }
- const { id } = node.data;
- $outputNodeId.set(id);
- $isSelectingOutputNode.set(false);
- }, []);
-
return (
<>
@@ -235,7 +219,6 @@ export const Flow = memo(() => {
nodes={nodes}
edges={edges}
onInit={onInit}
- onNodeClick={onNodeClick}
onMouseMove={onMouseMove}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
@@ -248,12 +231,6 @@ export const Flow = memo(() => {
onMoveEnd={handleMoveEnd}
connectionLineComponent={CustomConnectionLine}
isValidConnection={isValidConnection}
- edgesFocusable={!isLocked}
- edgesReconnectable={!isLocked}
- nodesDraggable={!isLocked}
- nodesConnectable={!isLocked}
- nodesFocusable={!isLocked}
- elementsSelectable={!isLocked}
minZoom={0.1}
snapToGrid={shouldSnapToGrid}
snapGrid={snapGrid}
@@ -279,8 +256,6 @@ export const Flow = memo(() => {
Flow.displayName = 'Flow';
const HotkeyIsolator = memo(() => {
- const isLocked = useIsWorkflowEditorLocked();
-
const mayUndo = useAppSelector(selectMayUndo);
const mayRedo = useAppSelector(selectMayRedo);
@@ -295,7 +270,7 @@ const HotkeyIsolator = memo(() => {
id: 'copySelection',
category: 'workflows',
callback: copySelection,
- options: { enabled: isWorkflowsFocused && !isLocked, preventDefault: true },
+ options: { enabled: isWorkflowsFocused, preventDefault: true },
dependencies: [copySelection],
});
@@ -324,24 +299,24 @@ const HotkeyIsolator = memo(() => {
id: 'selectAll',
category: 'workflows',
callback: selectAll,
- options: { enabled: isWorkflowsFocused && !isLocked, preventDefault: true },
- dependencies: [selectAll, isWorkflowsFocused, isLocked],
+ options: { enabled: isWorkflowsFocused, preventDefault: true },
+ dependencies: [selectAll, isWorkflowsFocused],
});
useRegisteredHotkeys({
id: 'pasteSelection',
category: 'workflows',
callback: pasteSelection,
- options: { enabled: isWorkflowsFocused && !isLocked, preventDefault: true },
- dependencies: [pasteSelection, isLocked, isWorkflowsFocused],
+ options: { enabled: isWorkflowsFocused, preventDefault: true },
+ dependencies: [pasteSelection, isWorkflowsFocused],
});
useRegisteredHotkeys({
id: 'pasteSelectionWithEdges',
category: 'workflows',
callback: pasteSelectionWithEdges,
- options: { enabled: isWorkflowsFocused && !isLocked, preventDefault: true },
- dependencies: [pasteSelectionWithEdges, isLocked, isWorkflowsFocused],
+ options: { enabled: isWorkflowsFocused, preventDefault: true },
+ dependencies: [pasteSelectionWithEdges, isWorkflowsFocused],
});
useRegisteredHotkeys({
@@ -350,8 +325,8 @@ const HotkeyIsolator = memo(() => {
callback: () => {
store.dispatch(undo());
},
- options: { enabled: isWorkflowsFocused && !isLocked && mayUndo, preventDefault: true },
- dependencies: [store, mayUndo, isLocked, isWorkflowsFocused],
+ options: { enabled: isWorkflowsFocused && mayUndo, preventDefault: true },
+ dependencies: [store, mayUndo, isWorkflowsFocused],
});
useRegisteredHotkeys({
@@ -360,8 +335,8 @@ const HotkeyIsolator = memo(() => {
callback: () => {
store.dispatch(redo());
},
- options: { enabled: isWorkflowsFocused && !isLocked && mayRedo, preventDefault: true },
- dependencies: [store, mayRedo, isLocked, isWorkflowsFocused],
+ options: { enabled: isWorkflowsFocused && mayRedo, preventDefault: true },
+ dependencies: [store, mayRedo, isWorkflowsFocused],
});
const onEscapeHotkey = useCallback(() => {
@@ -398,8 +373,8 @@ const HotkeyIsolator = memo(() => {
id: 'deleteSelection',
category: 'workflows',
callback: deleteSelection,
- options: { preventDefault: true, enabled: isWorkflowsFocused && !isLocked },
- dependencies: [deleteSelection, isWorkflowsFocused, isLocked],
+ options: { preventDefault: true, enabled: isWorkflowsFocused },
+ dependencies: [deleteSelection, isWorkflowsFocused],
});
return null;
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeFooter.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeFooter.tsx
index 851b85880f2..890666b0c4e 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeFooter.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeFooter.tsx
@@ -3,7 +3,6 @@ import { Flex, FormControlGroup } from '@invoke-ai/ui-library';
import { useIsExecutableNode } from 'features/nodes/hooks/useIsBatchNode';
import { useNodeHasImageOutput } from 'features/nodes/hooks/useNodeHasImageOutput';
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { memo } from 'react';
import SaveToGalleryCheckbox from './SaveToGalleryCheckbox';
@@ -18,7 +17,6 @@ const props: ChakraProps = { w: 'unset' };
const InvocationNodeFooter = ({ nodeId }: Props) => {
const hasImageOutput = useNodeHasImageOutput();
const isExecutableNode = useIsExecutableNode();
- const isCacheEnabled = useFeatureStatus('invocationCache');
return (
{
justifyContent="space-between"
>
- {isExecutableNode && isCacheEnabled && }
+ {isExecutableNode && }
{isExecutableNode && hasImageOutput && }
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldHandle.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldHandle.tsx
index 93b2518ca03..ba3282459fd 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldHandle.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldHandle.tsx
@@ -8,7 +8,6 @@ import {
useIsConnectionStartField,
} from 'features/nodes/hooks/useFieldConnectionState';
import { useInputFieldTemplateOrThrow } from 'features/nodes/hooks/useInputFieldTemplateOrThrow';
-import { useIsWorkflowEditorLocked } from 'features/nodes/hooks/useIsWorkflowEditorLocked';
import { useFieldTypeName } from 'features/nodes/hooks/usePrettyFieldType';
import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
import type { FieldInputTemplate } from 'features/nodes/types/field';
@@ -106,16 +105,9 @@ type HandleCommonProps = {
};
const IdleHandle = memo(({ fieldTemplate, fieldTypeName, fieldColor, isModelField }: HandleCommonProps) => {
- const isLocked = useIsWorkflowEditorLocked();
return (
-
+
{
if (connectionError !== null) {
@@ -149,13 +140,7 @@ const ConnectionInProgressHandle = memo(
return (
-
+
{
- const isLocked = useIsWorkflowEditorLocked();
-
return (
-
+
{
if (connectionErrorTKey !== null) {
@@ -150,13 +140,7 @@ const ConnectionInProgressHandle = memo(
return (
-
+
{
const mouseOverNode = useMouseOverNode(nodeId);
const mouseOverFormField = useMouseOverFormField(nodeId);
const zoomToNode = useZoomToNode(nodeId);
- const isLocked = useIsWorkflowEditorLocked();
const isInvalid = useNodeHasErrors();
const hasError = isMissingTemplate || isInvalid;
@@ -74,7 +72,6 @@ const NodeWrapper = (props: NodeWrapperProps) => {
sx={containerSx}
width={width || NODE_WIDTH}
opacity={opacity}
- data-is-editor-locked={isLocked}
data-is-selected={selected}
data-is-mouse-over-form-field={mouseOverFormField.isMouseOverFormField}
data-status={hasError ? 'error' : needsUpdate ? 'warning' : undefined}
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NonInvocationNodeWrapper.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NonInvocationNodeWrapper.tsx
index 885c4e5f146..7e2cde7093f 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NonInvocationNodeWrapper.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NonInvocationNodeWrapper.tsx
@@ -1,7 +1,6 @@
import type { ChakraProps } from '@invoke-ai/ui-library';
import { Box, useGlobalMenuClose } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
-import { useIsWorkflowEditorLocked } from 'features/nodes/hooks/useIsWorkflowEditorLocked';
import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
import { useNodeExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
import { useZoomToNode } from 'features/nodes/hooks/useZoomToNode';
@@ -23,7 +22,6 @@ const NonInvocationNodeWrapper = (props: NonInvocationNodeWrapperProps) => {
const { nodeId, width, children, selected } = props;
const mouseOverNode = useMouseOverNode(nodeId);
const zoomToNode = useZoomToNode(nodeId);
- const isLocked = useIsWorkflowEditorLocked();
const executionState = useNodeExecutionState(nodeId);
const isInProgress = executionState?.status === zNodeStatus.enum.IN_PROGRESS;
@@ -66,7 +64,6 @@ const NonInvocationNodeWrapper = (props: NonInvocationNodeWrapperProps) => {
sx={containerSx}
width={width || NODE_WIDTH}
opacity={opacity}
- data-is-editor-locked={isLocked}
data-is-selected={selected}
>
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/shared.ts b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/shared.ts
index 721c816b198..70e56cb4db6 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/shared.ts
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/shared.ts
@@ -56,12 +56,6 @@ export const containerSx: SystemStyleObject = {
display: 'block',
shadow: '0 0 0 2px var(--border-color-selected)',
},
- '&[data-is-editor-locked="true"]': {
- '& *': {
- cursor: 'not-allowed',
- pointerEvents: 'none',
- },
- },
};
export const shadowsSx: SystemStyleObject = {
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/TopLeftPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/TopLeftPanel.tsx
index 7320c1fce77..2aaa79243c0 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/TopLeftPanel.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/TopLeftPanel.tsx
@@ -1,61 +1,15 @@
-import { Alert, AlertDescription, AlertIcon, AlertTitle, Box, Flex } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
+import { Flex } from '@invoke-ai/ui-library';
import AddNodeButton from 'features/nodes/components/flow/panels/TopPanel/AddNodeButton';
import UpdateNodesButton from 'features/nodes/components/flow/panels/TopPanel/UpdateNodesButton';
-import {
- $isInPublishFlow,
- $isSelectingOutputNode,
- useIsValidationRunInProgress,
- useIsWorkflowPublished,
-} from 'features/nodes/components/sidePanel/workflow/publish';
-import { useIsWorkflowEditorLocked } from 'features/nodes/hooks/useIsWorkflowEditorLocked';
import { memo } from 'react';
-import { useTranslation } from 'react-i18next';
export const TopLeftPanel = memo(() => {
- const isLocked = useIsWorkflowEditorLocked();
- const isInPublishFlow = useStore($isInPublishFlow);
- const isPublished = useIsWorkflowPublished();
- const isValidationRunInProgress = useIsValidationRunInProgress();
- const isSelectingOutputNode = useStore($isSelectingOutputNode);
-
- const { t } = useTranslation();
return (
- {!isLocked && (
-
-
-
-
- )}
- {isLocked && (
-
-
-
- {t('workflows.builder.workflowLocked')}
- {isValidationRunInProgress && (
-
- {t('workflows.builder.publishingValidationRunInProgress')}
-
- )}
- {isInPublishFlow && !isValidationRunInProgress && !isSelectingOutputNode && (
-
- {t('workflows.builder.workflowLockedDuringPublishing')}
-
- )}
- {isInPublishFlow && !isValidationRunInProgress && isSelectingOutputNode && (
-
- {t('workflows.builder.selectingOutputNodeDesc')}
-
- )}
- {isPublished && (
-
- {t('workflows.builder.workflowLockedPublished')}
-
- )}
-
-
- )}
+
+
+
+
);
});
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/TopRightPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/TopRightPanel.tsx
index af778d3a9fb..5d4977db8ef 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/TopRightPanel.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopPanel/TopRightPanel.tsx
@@ -2,21 +2,15 @@ import { Flex, IconButton } from '@invoke-ai/ui-library';
import ClearFlowButton from 'features/nodes/components/flow/panels/TopPanel/ClearFlowButton';
import SaveWorkflowButton from 'features/nodes/components/flow/panels/TopPanel/SaveWorkflowButton';
import { useWorkflowEditorSettingsModal } from 'features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings';
-import { useIsWorkflowEditorLocked } from 'features/nodes/hooks/useIsWorkflowEditorLocked';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiGearSixFill } from 'react-icons/pi';
export const TopRightPanel = memo(() => {
const modal = useWorkflowEditorSettingsModal();
- const isLocked = useIsWorkflowEditorLocked();
const { t } = useTranslation();
- if (isLocked) {
- return null;
- }
-
return (
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowListMenu/ActiveWorkflowNameAndActions.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowListMenu/ActiveWorkflowNameAndActions.tsx
index dc8af90b176..f947e3165a8 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowListMenu/ActiveWorkflowNameAndActions.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowListMenu/ActiveWorkflowNameAndActions.tsx
@@ -1,6 +1,5 @@
import { Flex, Spacer } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
-import { useIsWorkflowPublished } from 'features/nodes/components/sidePanel/workflow/publish';
import { WorkflowListMenuTrigger } from 'features/nodes/components/sidePanel/WorkflowListMenu/WorkflowListMenuTrigger';
import { WorkflowViewEditToggleButton } from 'features/nodes/components/sidePanel/WorkflowViewEditToggleButton';
import { selectWorkflowMode } from 'features/nodes/store/workflowLibrarySlice';
@@ -11,13 +10,12 @@ import SaveWorkflowButton from './SaveWorkflowButton';
export const ActiveWorkflowNameAndActions = memo(() => {
const mode = useAppSelector(selectWorkflowMode);
- const isPublished = useIsWorkflowPublished();
return (
- {mode === 'edit' && !isPublished && }
+ {mode === 'edit' && }
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowsTabLeftPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowsTabLeftPanel.tsx
index 9f60a1d7a59..a31b71a4d44 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowsTabLeftPanel.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowsTabLeftPanel.tsx
@@ -1,10 +1,6 @@
import { Flex } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks';
import { EditModeLeftPanelContent } from 'features/nodes/components/sidePanel/EditModeLeftPanelContent';
-import { PublishedWorkflowPanelContent } from 'features/nodes/components/sidePanel/PublishedWorkflowPanelContent';
-import { $isInPublishFlow, useIsWorkflowPublished } from 'features/nodes/components/sidePanel/workflow/publish';
-import { PublishWorkflowPanelContent } from 'features/nodes/components/sidePanel/workflow/PublishWorkflowPanelContent';
import { ActiveWorkflowDescription } from 'features/nodes/components/sidePanel/WorkflowListMenu/ActiveWorkflowDescription';
import { ActiveWorkflowNameAndActions } from 'features/nodes/components/sidePanel/WorkflowListMenu/ActiveWorkflowNameAndActions';
import { selectWorkflowMode } from 'features/nodes/store/workflowLibrarySlice';
@@ -15,19 +11,15 @@ import { ViewModeLeftPanelContent } from './viewMode/ViewModeLeftPanelContent';
const WorkflowsTabLeftPanel = () => {
const mode = useAppSelector(selectWorkflowMode);
- const isPublished = useIsWorkflowPublished();
- const isInPublishFlow = useStore($isInPublishFlow);
return (
- {isInPublishFlow && }
- {!isInPublishFlow && }
- {!isInPublishFlow && !isPublished && mode === 'view' && }
- {!isInPublishFlow && !isPublished && mode === 'view' && }
- {!isInPublishFlow && !isPublished && mode === 'edit' && }
- {isPublished && }
+
+ {mode === 'view' && }
+ {mode === 'view' && }
+ {mode === 'edit' && }
);
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/IsolatedWorkflowBuilderWatcher.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/IsolatedWorkflowBuilderWatcher.tsx
index 0b38dd014c7..60b1dc66331 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/IsolatedWorkflowBuilderWatcher.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/IsolatedWorkflowBuilderWatcher.tsx
@@ -44,9 +44,8 @@ const queryOptions = {
if (!currentData) {
return { serverWorkflowHash: null };
}
- const { is_published: _is_published, ...serverWorkflow } = currentData.workflow;
return {
- serverWorkflowHash: stableHash(serverWorkflow),
+ serverWorkflowHash: stableHash(currentData.workflow),
};
},
} satisfies Parameters[1];
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/PublishWorkflowPanelContent.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/PublishWorkflowPanelContent.tsx
deleted file mode 100644
index 1f90716819b..00000000000
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/PublishWorkflowPanelContent.tsx
+++ /dev/null
@@ -1,475 +0,0 @@
-import type { ButtonProps } from '@invoke-ai/ui-library';
-import {
- Button,
- ButtonGroup,
- Divider,
- Flex,
- ListItem,
- Spacer,
- Text,
- Tooltip,
- UnorderedList,
-} from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { logger } from 'app/logging/logger';
-import { $projectUrl } from 'app/store/nanostores/projectId';
-import { useAppSelector } from 'app/store/storeHooks';
-import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
-import { withResultAsync } from 'common/util/result';
-import { parseify } from 'common/util/serialize';
-import { ExternalLink } from 'features/gallery/components/ImageViewer/NoContentForViewer';
-import { InvocationNodeContextProvider } from 'features/nodes/components/flow/nodes/Invocation/context';
-import { NodeFieldElementOverlay } from 'features/nodes/components/sidePanel/builder/NodeFieldElementEditMode';
-import { useDoesWorkflowHaveUnsavedChanges } from 'features/nodes/components/sidePanel/workflow/IsolatedWorkflowBuilderWatcher';
-import {
- $isInPublishFlow,
- $isPublishing,
- $isReadyToDoValidationRun,
- $isSelectingOutputNode,
- $outputNodeId,
- $validationRunData,
- selectHasUnpublishableNodes,
- usePublishInputs,
-} from 'features/nodes/components/sidePanel/workflow/publish';
-import { useInputFieldTemplateTitleOrThrow } from 'features/nodes/hooks/useInputFieldTemplateTitleOrThrow';
-import { useInputFieldUserTitleOrThrow } from 'features/nodes/hooks/useInputFieldUserTitleOrThrow';
-import { useMouseOverFormField } from 'features/nodes/hooks/useMouseOverNode';
-import { useNodeTemplateTitleOrThrow } from 'features/nodes/hooks/useNodeTemplateTitleOrThrow';
-import { useNodeUserTitleOrThrow } from 'features/nodes/hooks/useNodeUserTitleOrThrow';
-import { useOutputFieldNames } from 'features/nodes/hooks/useOutputFieldNames';
-import { useOutputFieldTemplate } from 'features/nodes/hooks/useOutputFieldTemplate';
-import { useZoomToNode } from 'features/nodes/hooks/useZoomToNode';
-import { useEnqueueWorkflows } from 'features/queue/hooks/useEnqueueWorkflows';
-import { $isReadyToEnqueue } from 'features/queue/store/readiness';
-import { selectAllowPublishWorkflows } from 'features/system/store/configSlice';
-import { toast } from 'features/toast/toast';
-import type { PropsWithChildren } from 'react';
-import { memo, useCallback, useMemo } from 'react';
-import { Trans, useTranslation } from 'react-i18next';
-import { PiArrowLineRightBold, PiLightningFill, PiXBold } from 'react-icons/pi';
-import { serializeError } from 'serialize-error';
-import { assert } from 'tsafe';
-
-const log = logger('generation');
-
-export const PublishWorkflowPanelContent = memo(() => {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-});
-PublishWorkflowPanelContent.displayName = 'PublishWorkflowPanelContent';
-
-const OutputFields = memo(() => {
- const { t } = useTranslation();
- const outputNodeId = useStore($outputNodeId);
-
- return (
-
-
- {t('workflows.builder.publishedWorkflowOutputs')}
-
-
-
-
-
- {!outputNodeId && (
-
- {t('workflows.builder.noOutputNodeSelected')}
-
- )}
- {outputNodeId && (
-
-
-
- )}
-
- );
-});
-OutputFields.displayName = 'OutputFields';
-
-const OutputFieldsContent = memo(({ outputNodeId }: { outputNodeId: string }) => {
- const outputFieldNames = useOutputFieldNames();
-
- return (
- <>
- {outputFieldNames.map((fieldName) => (
-
- ))}
- >
- );
-});
-OutputFieldsContent.displayName = 'OutputFieldsContent';
-
-const PublishableInputFields = memo(() => {
- const { t } = useTranslation();
- const inputs = usePublishInputs();
-
- if (inputs.publishable.length === 0) {
- return (
-
-
- {t('workflows.builder.noPublishableInputs')}
-
-
- );
- }
-
- return (
-
- {t('workflows.builder.publishedWorkflowInputs')}
-
- {inputs.publishable.map(({ nodeId, fieldName }) => {
- return (
-
-
-
- );
- })}
-
- );
-});
-PublishableInputFields.displayName = 'PublishableInputFields';
-
-const UnpublishableInputFields = memo(() => {
- const { t } = useTranslation();
- const inputs = usePublishInputs();
-
- if (inputs.unpublishable.length === 0) {
- return null;
- }
-
- return (
-
-
- {t('workflows.builder.unpublishableInputs')}
-
-
- {inputs.unpublishable.map(({ nodeId, fieldName }) => {
- return (
-
-
-
- );
- })}
-
- );
-});
-UnpublishableInputFields.displayName = 'UnpublishableInputFields';
-
-const SelectOutputNodeButton = memo((props: ButtonProps) => {
- const { t } = useTranslation();
- const outputNodeId = useStore($outputNodeId);
- const isSelectingOutputNode = useStore($isSelectingOutputNode);
- const onClick = useCallback(() => {
- $outputNodeId.set(null);
- $isSelectingOutputNode.set(true);
- }, []);
- return (
- }
- isDisabled={isSelectingOutputNode}
- tooltip={isSelectingOutputNode ? t('workflows.builder.selectingOutputNodeDesc') : undefined}
- onClick={onClick}
- {...props}
- >
- {isSelectingOutputNode
- ? t('workflows.builder.selectingOutputNode')
- : outputNodeId
- ? t('workflows.builder.changeOutputNode')
- : t('workflows.builder.selectOutputNode')}
-
- );
-});
-SelectOutputNodeButton.displayName = 'SelectOutputNodeButton';
-
-const CancelPublishButton = memo(() => {
- const { t } = useTranslation();
- const isPublishing = useStore($isPublishing);
- const onClick = useCallback(() => {
- $isInPublishFlow.set(false);
- $isSelectingOutputNode.set(false);
- $outputNodeId.set(null);
- }, []);
- return (
- } onClick={onClick} isDisabled={isPublishing}>
- {t('common.cancel')}
-
- );
-});
-CancelPublishButton.displayName = 'CancelDeployButton';
-
-const PublishWorkflowButton = memo(() => {
- const { t } = useTranslation();
- const isPublishing = useStore($isPublishing);
- const isReadyToDoValidationRun = useStore($isReadyToDoValidationRun);
- const isReadyToEnqueue = useStore($isReadyToEnqueue);
- const doesWorkflowHaveUnsavedChanges = useDoesWorkflowHaveUnsavedChanges();
- const hasUnpublishableNodes = useAppSelector(selectHasUnpublishableNodes);
- const outputNodeId = useStore($outputNodeId);
- const isSelectingOutputNode = useStore($isSelectingOutputNode);
- const inputs = usePublishInputs();
- const allowPublishWorkflows = useAppSelector(selectAllowPublishWorkflows);
-
- const projectUrl = useStore($projectUrl);
-
- const enqueue = useEnqueueWorkflows();
- const onClick = useCallback(async () => {
- $isPublishing.set(true);
- const result = await withResultAsync(() => enqueue(true, true));
- if (result.isErr()) {
- toast({
- id: 'TOAST_PUBLISH_FAILED',
- status: 'error',
- title: t('workflows.builder.publishFailed'),
- description: t('workflows.builder.publishFailedDesc'),
- duration: null,
- });
- log.error({ error: serializeError(result.error) }, 'Failed to enqueue batch');
- } else {
- toast({
- id: 'TOAST_PUBLISH_SUCCESSFUL',
- status: 'success',
- title: t('workflows.builder.publishSuccess'),
- description: (
- ,
- }}
- />
- ),
- duration: null,
- });
- assert(result.value.enqueueResult.batch.batch_id);
- assert(result.value.batchConfig.validation_run_data);
- $validationRunData.set({
- batchId: result.value.enqueueResult.batch.batch_id,
- workflowId: result.value.batchConfig.validation_run_data.workflow_id,
- });
- log.debug(parseify(result.value), 'Enqueued batch');
- }
- $isPublishing.set(false);
- }, [enqueue, projectUrl, t]);
-
- const isDisabled = useMemo(() => {
- return (
- !allowPublishWorkflows ||
- !isReadyToEnqueue ||
- doesWorkflowHaveUnsavedChanges ||
- hasUnpublishableNodes ||
- !isReadyToDoValidationRun ||
- !(outputNodeId !== null && !isSelectingOutputNode) ||
- isPublishing
- );
- }, [
- allowPublishWorkflows,
- doesWorkflowHaveUnsavedChanges,
- hasUnpublishableNodes,
- isReadyToDoValidationRun,
- isReadyToEnqueue,
- isSelectingOutputNode,
- outputNodeId,
- isPublishing,
- ]);
-
- return (
- 0}
- hasUnpublishableInputs={inputs.unpublishable.length > 0}
- >
- } isDisabled={isDisabled} onClick={onClick}>
- {isPublishing ? t('workflows.builder.publishing') : t('workflows.builder.publish')}
-
-
- );
-});
-PublishWorkflowButton.displayName = 'DoValidationRunButton';
-
-const NodeInputFieldPreview = memo(({ nodeId, fieldName }: { nodeId: string; fieldName: string }) => {
- const mouseOverFormField = useMouseOverFormField(nodeId);
- const nodeUserTitle = useNodeUserTitleOrThrow();
- const nodeTemplateTitle = useNodeTemplateTitleOrThrow();
- const fieldUserTitle = useInputFieldUserTitleOrThrow(fieldName);
- const fieldTemplateTitle = useInputFieldTemplateTitleOrThrow(fieldName);
- const zoomToNode = useZoomToNode(nodeId);
-
- return (
-
- {`${nodeUserTitle || nodeTemplateTitle} -> ${fieldUserTitle || fieldTemplateTitle}`}
- {`${nodeId} -> ${fieldName}`}
-
-
- );
-});
-NodeInputFieldPreview.displayName = 'NodeInputFieldPreview';
-
-const NodeOutputFieldPreview = memo(({ nodeId, fieldName }: { nodeId: string; fieldName: string }) => {
- const mouseOverFormField = useMouseOverFormField(nodeId);
- const nodeUserTitle = useNodeUserTitleOrThrow();
- const nodeTemplateTitle = useNodeTemplateTitleOrThrow();
- const fieldTemplate = useOutputFieldTemplate(fieldName);
- const zoomToNode = useZoomToNode(nodeId);
-
- return (
-
- {`${nodeUserTitle || nodeTemplateTitle} -> ${fieldTemplate.title}`}
- {`${nodeId} -> ${fieldName}`}
-
-
- );
-});
-NodeOutputFieldPreview.displayName = 'NodeOutputFieldPreview';
-
-export const StartPublishFlowButton = memo(() => {
- const { t } = useTranslation();
- const allowPublishWorkflows = useAppSelector(selectAllowPublishWorkflows);
- const isReadyToEnqueue = useStore($isReadyToEnqueue);
- const doesWorkflowHaveUnsavedChanges = useDoesWorkflowHaveUnsavedChanges();
- const hasUnpublishableNodes = useAppSelector(selectHasUnpublishableNodes);
- const inputs = usePublishInputs();
-
- const onClick = useCallback(() => {
- $isInPublishFlow.set(true);
- }, []);
-
- const isDisabled = useMemo(() => {
- return !allowPublishWorkflows || !isReadyToEnqueue || doesWorkflowHaveUnsavedChanges || hasUnpublishableNodes;
- }, [allowPublishWorkflows, doesWorkflowHaveUnsavedChanges, hasUnpublishableNodes, isReadyToEnqueue]);
-
- return (
- 0}
- hasUnpublishableInputs={inputs.unpublishable.length > 0}
- >
- } variant="ghost" size="sm" isDisabled={isDisabled}>
- {t('workflows.builder.publish')}
-
-
- );
-});
-
-StartPublishFlowButton.displayName = 'StartPublishFlowButton';
-
-const PublishTooltip = memo(
- ({
- isWorkflowSaved,
- hasUnpublishableNodes,
- isReadyToEnqueue,
- hasOutputNode,
- hasPublishableInputs,
- hasUnpublishableInputs,
- children,
- }: PropsWithChildren<{
- isWorkflowSaved: boolean;
- hasUnpublishableNodes: boolean;
- isReadyToEnqueue: boolean;
- hasOutputNode: boolean;
- hasPublishableInputs: boolean;
- hasUnpublishableInputs: boolean;
- }>) => {
- const { t } = useTranslation();
- const warnings = useMemo(() => {
- const _warnings: string[] = [];
- if (!hasPublishableInputs) {
- _warnings.push(t('workflows.builder.warningWorkflowHasNoPublishableInputFields'));
- }
- if (hasUnpublishableInputs) {
- _warnings.push(t('workflows.builder.warningWorkflowHasUnpublishableInputFields'));
- }
- return _warnings;
- }, [hasPublishableInputs, hasUnpublishableInputs, t]);
- const errors = useMemo(() => {
- const _errors: string[] = [];
- if (!isWorkflowSaved) {
- _errors.push(t('workflows.builder.errorWorkflowHasUnsavedChanges'));
- }
- if (hasUnpublishableNodes) {
- _errors.push(t('workflows.builder.errorWorkflowHasUnpublishableNodes'));
- }
- if (!isReadyToEnqueue) {
- _errors.push(t('workflows.builder.errorWorkflowHasInvalidGraph'));
- }
- if (!hasOutputNode) {
- _errors.push(t('workflows.builder.errorWorkflowHasNoOutputNode'));
- }
- return _errors;
- }, [hasUnpublishableNodes, hasOutputNode, isReadyToEnqueue, isWorkflowSaved, t]);
-
- if (errors.length === 0 && warnings.length === 0) {
- return children;
- }
-
- return (
-
- {errors.length > 0 && (
- <>
-
- {t('workflows.builder.cannotPublish')}:
-
-
- {errors.map((problem, index) => (
- {problem}
- ))}
-
- >
- )}
- {warnings.length > 0 && (
- <>
-
- {t('workflows.builder.publishWarnings')}:
-
-
- {warnings.map((problem, index) => (
- {problem}
- ))}
-
- >
- )}
-
- }
- >
- {children}
-
- );
- }
-);
-PublishTooltip.displayName = 'PublishTooltip';
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/ShareWorkflowModal.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/ShareWorkflowModal.tsx
deleted file mode 100644
index b88a877e3dd..00000000000
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/ShareWorkflowModal.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import {
- Button,
- Flex,
- Heading,
- IconButton,
- Modal,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
- Text,
-} from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { $projectUrl } from 'app/store/nanostores/projectId';
-import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
-import { useClipboard } from 'common/hooks/useClipboard';
-import { toast } from 'features/toast/toast';
-import { atom } from 'nanostores';
-import { useCallback, useMemo } from 'react';
-import { useTranslation } from 'react-i18next';
-import { PiCopyBold } from 'react-icons/pi';
-import type { WorkflowRecordListItemWithThumbnailDTO } from 'services/api/types';
-
-const $workflowToShare = atom(null);
-const clearWorkflowToShare = () => $workflowToShare.set(null);
-
-export const useShareWorkflow = () => {
- const copyWorkflowLink = useCallback((workflow: WorkflowRecordListItemWithThumbnailDTO) => {
- $workflowToShare.set(workflow);
- }, []);
-
- return copyWorkflowLink;
-};
-
-export const ShareWorkflowModal = () => {
- useAssertSingleton('ShareWorkflowModal');
- const workflowToShare = useStore($workflowToShare);
- const projectUrl = useStore($projectUrl);
- const { t } = useTranslation();
- const clipboard = useClipboard();
- const workflowLink = useMemo(() => {
- if (!workflowToShare || !projectUrl) {
- return null;
- }
- return `${window.location.origin}${projectUrl}/studio?selectedWorkflowId=${workflowToShare.workflow_id}`;
- }, [projectUrl, workflowToShare]);
-
- const handleCopy = useCallback(() => {
- if (!workflowLink) {
- return;
- }
- clipboard.writeText(workflowLink, () => {
- toast({
- status: 'success',
- title: t('toast.linkCopied'),
- });
- });
- $workflowToShare.set(null);
- }, [workflowLink, clipboard, t]);
-
- return (
-
-
-
-
-
- {t('workflows.copyShareLinkForWorkflow')}
- {workflowToShare?.name}
-
-
-
-
-
- {workflowLink}
- }
- onClick={handleCopy}
- />
-
-
-
-
-
-
-
-
- );
-};
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryListItemActions/ShareWorkflow.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryListItemActions/ShareWorkflow.tsx
deleted file mode 100644
index 971e9eca78a..00000000000
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryListItemActions/ShareWorkflow.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { IconButton, Tooltip } from '@invoke-ai/ui-library';
-import { useShareWorkflow } from 'features/nodes/components/sidePanel/workflow/WorkflowLibrary/ShareWorkflowModal';
-import type { MouseEvent } from 'react';
-import { memo, useCallback } from 'react';
-import { useTranslation } from 'react-i18next';
-import { PiShareFatBold } from 'react-icons/pi';
-import type { WorkflowRecordListItemWithThumbnailDTO } from 'services/api/types';
-
-export const ShareWorkflowButton = memo(({ workflow }: { workflow: WorkflowRecordListItemWithThumbnailDTO }) => {
- const shareWorkflow = useShareWorkflow();
- const { t } = useTranslation();
-
- const handleClickShare = useCallback(
- (e: MouseEvent) => {
- e.stopPropagation();
- shareWorkflow(workflow);
- },
- [shareWorkflow, workflow]
- );
-
- return (
-
- }
- />
-
- );
-});
-
-ShareWorkflowButton.displayName = 'ShareWorkflowButton';
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx
index 604040d63a8..5000d7f564b 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx
@@ -26,7 +26,6 @@ import {
workflowLibraryTagToggled,
workflowLibraryViewChanged,
} from 'features/nodes/store/workflowLibrarySlice';
-import { selectAllowPublishWorkflows } from 'features/system/store/configSlice';
import { NewWorkflowButton } from 'features/workflowLibrary/components/NewWorkflowButton';
import { UploadWorkflowButton } from 'features/workflowLibrary/components/UploadWorkflowButton';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
@@ -40,7 +39,6 @@ export const WorkflowLibrarySideNav = () => {
const { t } = useTranslation();
const categoryOptions = useStore($workflowLibraryCategoriesOptions);
const view = useAppSelector(selectWorkflowLibraryView);
- const allowPublishWorkflows = useAppSelector(selectAllowPublishWorkflows);
return (
@@ -60,9 +58,6 @@ export const WorkflowLibrarySideNav = () => {
)}
- {allowPublishWorkflows && (
- {t('workflows.published')}
- )}
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowList.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowList.tsx
index 61802b37fb7..34b40e98473 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowList.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowList.tsx
@@ -68,7 +68,6 @@ const useInfiniteQueryAry = () => {
query: debouncedSearchTerm,
tags: view === 'defaults' ? selectedTags : [],
has_been_opened: getHasBeenOpened(view),
- is_published: view === 'published' ? true : undefined,
} satisfies Parameters[0];
}, [orderBy, direction, view, debouncedSearchTerm, selectedTags]);
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowListItem.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowListItem.tsx
index 93b8cc1c12f..34913434bc8 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowListItem.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowListItem.tsx
@@ -1,8 +1,6 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { Badge, Flex, Icon, Image, Spacer, Text } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { LockedWorkflowIcon } from 'features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryListItemActions/LockedWorkflowIcon';
-import { ShareWorkflowButton } from 'features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryListItemActions/ShareWorkflow';
import { selectWorkflowId } from 'features/nodes/store/selectors';
import { workflowModeChanged } from 'features/nodes/store/workflowLibrarySlice';
import { useLoadWorkflowWithDialog } from 'features/workflowLibrary/components/LoadWorkflowConfirmationAlertDialog';
@@ -82,7 +80,7 @@ export const WorkflowListItem = memo(({ workflow }: { workflow: WorkflowRecordLi
{workflow.name}
- {isActive && !workflow.is_published && (
+ {isActive && (
)}
- {workflow.is_published && (
-
- {t('workflows.builder.published')}
-
- )}
{workflow.category === 'project' && }
{workflow.category === 'default' && (
)}
- {workflow.category === 'default' && !workflow.is_published && (
-
- )}
- {workflow.category !== 'default' && !workflow.is_published && (
+ {workflow.category === 'default' && }
+ {workflow.category !== 'default' && (
<>
>
)}
- {workflow.category === 'project' && }
- {workflow.is_published && }
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowPanel.tsx
index 9b47484cc40..37bf9a2f195 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowPanel.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowPanel.tsx
@@ -1,8 +1,5 @@
import { Spacer, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
-import { useAppSelector } from 'app/store/storeHooks';
import { WorkflowBuilder } from 'features/nodes/components/sidePanel/builder/WorkflowBuilder';
-import { StartPublishFlowButton } from 'features/nodes/components/sidePanel/workflow/PublishWorkflowPanelContent';
-import { selectAllowPublishWorkflows } from 'features/system/store/configSlice';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -11,7 +8,6 @@ import WorkflowJSONTab from './WorkflowJSONTab';
const WorkflowFieldsLinearViewPanel = () => {
const { t } = useTranslation();
- const allowPublishWorkflows = useAppSelector(selectAllowPublishWorkflows);
return (
@@ -19,7 +15,6 @@ const WorkflowFieldsLinearViewPanel = () => {
{t('common.details')}
JSON
- {allowPublishWorkflows && }
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/publish.ts b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/publish.ts
deleted file mode 100644
index 397d127b2f0..00000000000
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/publish.ts
+++ /dev/null
@@ -1,157 +0,0 @@
-import { useStore } from '@nanostores/react';
-import { createSelector } from '@reduxjs/toolkit';
-import { skipToken } from '@reduxjs/toolkit/query';
-import { useAppSelector } from 'app/store/storeHooks';
-import { $templates } from 'features/nodes/store/nodesSlice';
-import {
- selectNodes,
- selectNodesSlice,
- selectWorkflowFormNodeFieldFieldIdentifiersDeduped,
- selectWorkflowId,
-} from 'features/nodes/store/selectors';
-import type { Templates } from 'features/nodes/store/types';
-import type { FieldIdentifier } from 'features/nodes/types/field';
-import { isBoardFieldType } from 'features/nodes/types/field';
-import { isBatchNode, isGeneratorNode, isInvocationNode } from 'features/nodes/types/invocation';
-import { atom, computed } from 'nanostores';
-import { useMemo } from 'react';
-import { useGetBatchStatusQuery } from 'services/api/endpoints/queue';
-import { useGetWorkflowQuery } from 'services/api/endpoints/workflows';
-import { assert } from 'tsafe';
-
-type FieldIdentiferWithLabel = FieldIdentifier & { label: string | null };
-type FieldIdentiferWithLabelAndType = FieldIdentiferWithLabel & { type: string };
-
-export const $isPublishing = atom(false);
-export const $isInPublishFlow = atom(false);
-export const $outputNodeId = atom(null);
-export const $isSelectingOutputNode = atom(false);
-export const $isReadyToDoValidationRun = computed(
- [$isInPublishFlow, $outputNodeId, $isSelectingOutputNode],
- (isInPublishFlow, outputNodeId, isSelectingOutputNode) => {
- return isInPublishFlow && outputNodeId !== null && !isSelectingOutputNode;
- }
-);
-export const $validationRunData = atom<{ batchId: string; workflowId: string } | null>(null);
-
-export const useIsValidationRunInProgress = () => {
- const validationRunData = useStore($validationRunData);
- const { isValidationRunInProgress } = useGetBatchStatusQuery(
- validationRunData?.batchId ? { batch_id: validationRunData.batchId } : skipToken,
- {
- selectFromResult: ({ currentData }) => {
- if (!currentData) {
- return { isValidationRunInProgress: false };
- }
- if (currentData && currentData.in_progress > 0) {
- return { isValidationRunInProgress: true };
- }
- return { isValidationRunInProgress: false };
- },
- }
- );
- return validationRunData !== null || isValidationRunInProgress;
-};
-
-export const selectFieldIdentifiersWithInvocationTypes = createSelector(
- selectWorkflowFormNodeFieldFieldIdentifiersDeduped,
- selectNodesSlice,
- (fieldIdentifiers, nodes) => {
- const result: FieldIdentiferWithLabelAndType[] = [];
- for (const fieldIdentifier of fieldIdentifiers) {
- const node = nodes.nodes.find((node) => node.id === fieldIdentifier.nodeId);
- assert(isInvocationNode(node), `Node ${fieldIdentifier.nodeId} not found`);
- result.push({
- nodeId: fieldIdentifier.nodeId,
- fieldName: fieldIdentifier.fieldName,
- type: node.data.type,
- label: node.data.inputs[fieldIdentifier.fieldName]?.label ?? null,
- });
- }
-
- return result;
- }
-);
-
-export const getPublishInputs = (fieldIdentifiers: FieldIdentiferWithLabelAndType[], templates: Templates) => {
- // Certain field types are not allowed to be input fields on a published workflow
- const publishable: FieldIdentiferWithLabel[] = [];
- const unpublishable: FieldIdentiferWithLabel[] = [];
- for (const fieldIdentifier of fieldIdentifiers) {
- const fieldTemplate = templates[fieldIdentifier.type]?.inputs[fieldIdentifier.fieldName];
- if (!fieldTemplate) {
- unpublishable.push(fieldIdentifier);
- continue;
- }
- if (isBoardFieldType(fieldTemplate.type)) {
- unpublishable.push(fieldIdentifier);
- continue;
- }
- publishable.push(fieldIdentifier);
- }
- return { publishable, unpublishable };
-};
-
-export const usePublishInputs = () => {
- const templates = useStore($templates);
- const fieldIdentifiersWithInvocationTypes = useAppSelector(selectFieldIdentifiersWithInvocationTypes);
- const fieldIdentifiers = useMemo(
- () => getPublishInputs(fieldIdentifiersWithInvocationTypes, templates),
- [fieldIdentifiersWithInvocationTypes, templates]
- );
-
- return fieldIdentifiers;
-};
-
-const queryOptions = {
- selectFromResult: ({ currentData }) => {
- if (!currentData) {
- return { isPublished: false };
- }
- return { isPublished: currentData.is_published };
- },
-} satisfies Parameters[1];
-
-export const useIsWorkflowPublished = () => {
- const workflowId = useAppSelector(selectWorkflowId);
- const { isPublished } = useGetWorkflowQuery(workflowId ?? skipToken, queryOptions);
-
- return isPublished;
-};
-
-// These nodes are not allowed to be in published workflows because they dynamically generate model identifiers
-const NODE_TYPE_PUBLISH_DENYLIST = [
- 'metadata_to_model',
- 'metadata_to_sdxl_model',
- 'metadata_to_vae',
- 'metadata_to_lora_collection',
- 'metadata_to_loras',
- 'metadata_to_sdlx_loras',
- 'metadata_to_controlnets',
- 'metadata_to_ip_adapters',
- 'metadata_to_t2i_adapters',
- 'google_imagen3_generate_image',
- 'google_imagen3_edit_image',
- 'google_imagen4_generate_image',
- 'chatgpt_4o_generate_image',
- 'chatgpt_4o_edit_image',
- 'flux_kontext_generate_image',
- 'flux_kontext_edit_image',
- 'claude_expand_prompt',
- 'claude_analyze_image',
-];
-
-export const selectHasUnpublishableNodes = createSelector(selectNodes, (nodes) => {
- for (const node of nodes) {
- if (!isInvocationNode(node)) {
- return true;
- }
- if (isBatchNode(node) || isGeneratorNode(node)) {
- return true;
- }
- if (NODE_TYPE_PUBLISH_DENYLIST.includes(node.data.type)) {
- return true;
- }
- }
- return false;
-});
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useIsWorkflowEditorLocked.ts b/invokeai/frontend/web/src/features/nodes/hooks/useIsWorkflowEditorLocked.ts
deleted file mode 100644
index 2738dad04b4..00000000000
--- a/invokeai/frontend/web/src/features/nodes/hooks/useIsWorkflowEditorLocked.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { useStore } from '@nanostores/react';
-import {
- $isInPublishFlow,
- useIsValidationRunInProgress,
- useIsWorkflowPublished,
-} from 'features/nodes/components/sidePanel/workflow/publish';
-
-export const useIsWorkflowEditorLocked = () => {
- const isInPublishFlow = useStore($isInPublishFlow);
- const isPublished = useIsWorkflowPublished();
- const isValidationRunInProgress = useIsValidationRunInProgress();
-
- const isLocked = isInPublishFlow || isPublished || isValidationRunInProgress;
- return isLocked;
-};
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useWithFooter.ts b/invokeai/frontend/web/src/features/nodes/hooks/useWithFooter.ts
index d3c63329ea2..b140295801b 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useWithFooter.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useWithFooter.ts
@@ -1,16 +1,9 @@
import { useIsExecutableNode } from 'features/nodes/hooks/useIsBatchNode';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
-import { useMemo } from 'react';
import { useNodeHasImageOutput } from './useNodeHasImageOutput';
export const useWithFooter = () => {
const hasImageOutput = useNodeHasImageOutput();
const isExecutableNode = useIsExecutableNode();
- const isCacheEnabled = useFeatureStatus('invocationCache');
- const withFooter = useMemo(
- () => isExecutableNode && (hasImageOutput || isCacheEnabled),
- [hasImageOutput, isCacheEnabled, isExecutableNode]
- );
- return withFooter;
+ return isExecutableNode && hasImageOutput;
};
diff --git a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts
index 20c27d2cd6e..98b41da3059 100644
--- a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts
+++ b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts
@@ -565,7 +565,7 @@ const slice = createSlice({
state.formFieldInitialValues = formFieldInitialValues;
},
workflowLoaded: (state, action: PayloadAction) => {
- const { nodes, edges, is_published: _is_published, ...workflowExtra } = action.payload;
+ const { nodes, edges, ...workflowExtra } = action.payload;
const formFieldInitialValues = getFormFieldInitialValues(workflowExtra.form, nodes);
diff --git a/invokeai/frontend/web/src/features/nodes/types/common.ts b/invokeai/frontend/web/src/features/nodes/types/common.ts
index c51defd79c5..97c7fff795d 100644
--- a/invokeai/frontend/web/src/features/nodes/types/common.ts
+++ b/invokeai/frontend/web/src/features/nodes/types/common.ts
@@ -1,6 +1,3 @@
-import type { S } from 'services/api/types';
-import type { Equals } from 'tsafe';
-import { assert } from 'tsafe';
import { z } from 'zod';
// #region Field data schemas
@@ -14,13 +11,6 @@ type ImageFieldCollection = z.infer;
export const isImageFieldCollection = (field: unknown): field is ImageFieldCollection =>
zImageFieldCollection.safeParse(field).success;
-const zVideoField = z.object({
- video_id: z.string().trim().min(1),
-});
-type VideoField = z.infer;
-export const isVideoField = (field: unknown): field is VideoField => zVideoField.safeParse(field).success;
-assert>();
-
export const zBoardField = z.object({
board_id: z.string().trim().min(1),
});
@@ -82,31 +72,10 @@ export const zBaseModelType = z.enum([
'sdxl-refiner',
'flux',
'cogview4',
- 'imagen3',
- 'imagen4',
- 'chatgpt-4o',
- 'flux-kontext',
- 'gemini-2.5',
- 'veo3',
- 'runway',
'unknown',
]);
export type BaseModelType = z.infer;
-export const zMainModelBase = z.enum([
- 'sd-1',
- 'sd-2',
- 'sd-3',
- 'sdxl',
- 'flux',
- 'cogview4',
- 'imagen3',
- 'imagen4',
- 'chatgpt-4o',
- 'flux-kontext',
- 'gemini-2.5',
- 'veo3',
- 'runway',
-]);
+export const zMainModelBase = z.enum(['sd-1', 'sd-2', 'sd-3', 'sdxl', 'flux', 'cogview4']);
type MainModelBase = z.infer;
export const isMainModelBase = (base: unknown): base is MainModelBase => zMainModelBase.safeParse(base).success;
export const zModelType = z.enum([
@@ -126,7 +95,6 @@ export const zModelType = z.enum([
'clip_embed',
'siglip',
'flux_redux',
- 'video',
'unknown',
]);
export type ModelType = z.infer;
@@ -165,7 +133,6 @@ export const zModelFormat = z.enum([
'bnb_quantized_int8b',
'bnb_quantized_nf4b',
'gguf_quantized',
- 'api',
'unknown',
]);
export type ModelFormat = z.infer;
diff --git a/invokeai/frontend/web/src/features/nodes/types/workflow.ts b/invokeai/frontend/web/src/features/nodes/types/workflow.ts
index a241a5e8fe8..d0ce39970a5 100644
--- a/invokeai/frontend/web/src/features/nodes/types/workflow.ts
+++ b/invokeai/frontend/web/src/features/nodes/types/workflow.ts
@@ -381,7 +381,6 @@ export const zWorkflowV3 = z.object({
}),
// Use the validated form schema!
form: zValidatedBuilderForm,
- is_published: z.boolean().nullish(),
});
export type WorkflowV3 = z.infer;
// #endregion
diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearBatchConfig.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearBatchConfig.ts
index 3792b22206d..900573065ff 100644
--- a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearBatchConfig.ts
+++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearBatchConfig.ts
@@ -2,7 +2,6 @@ import type { RootState } from 'app/store/store';
import { generateSeeds } from 'common/util/generateSeeds';
import { range } from 'es-toolkit/compat';
import type { SeedBehaviour } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
-import { API_BASE_MODELS, VIDEO_BASE_MODELS } from 'features/modelManagerV2/models';
import type { BaseModelType } from 'features/nodes/types/common';
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
import type { components } from 'services/api/schema';
@@ -14,11 +13,8 @@ const getExtendedPrompts = (arg: {
prompts: string[];
base: BaseModelType;
}): string[] => {
- const { seedBehaviour, iterations, prompts, base } = arg;
- // Normally, the seed behaviour implicity determines the batch size. But when we use models without seeds (like
- // ChatGPT 4o) in conjunction with the per-prompt seed behaviour, we lose out on that implicit batch size. To rectify
- // this, we need to create a batch of the right size by repeating the prompts.
- if (seedBehaviour === 'PER_PROMPT' || API_BASE_MODELS.includes(base) || VIDEO_BASE_MODELS.includes(base)) {
+ const { seedBehaviour, iterations, prompts } = arg;
+ if (seedBehaviour === 'PER_PROMPT') {
return range(iterations).flatMap(() => prompts);
}
return prompts;
diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/Graph.test.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/Graph.test.ts
index 42d66f0fc81..f2a6dc19885 100644
--- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/Graph.test.ts
+++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/Graph.test.ts
@@ -674,7 +674,6 @@ describe('Graph', () => {
variant: 'inpaint',
format: 'diffusers',
repo_variant: 'fp16',
- usage_info: null,
});
expect(field).toEqual({
key: 'b00ee8df-523d-40d2-9578-597283b07cb2',
diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildChatGPT4oGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildChatGPT4oGraph.ts
deleted file mode 100644
index c579fc05bc1..00000000000
--- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildChatGPT4oGraph.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-import { logger } from 'app/logging/logger';
-import { getPrefixedId } from 'features/controlLayers/konva/util';
-import { selectMainModelConfig } from 'features/controlLayers/store/paramsSlice';
-import { selectRefImagesSlice } from 'features/controlLayers/store/refImagesSlice';
-import { selectCanvasMetadata } from 'features/controlLayers/store/selectors';
-import { isChatGPT4oAspectRatioID, isChatGPT4oReferenceImageConfig } from 'features/controlLayers/store/types';
-import { getGlobalReferenceImageWarnings } from 'features/controlLayers/store/validators';
-import { type ImageField, zImageField, zModelIdentifierField } from 'features/nodes/types/common';
-import { Graph } from 'features/nodes/util/graph/generation/Graph';
-import {
- getOriginalAndScaledSizesForOtherModes,
- getOriginalAndScaledSizesForTextToImage,
- selectCanvasOutputFields,
-} from 'features/nodes/util/graph/graphBuilderUtils';
-import type { GraphBuilderArg, GraphBuilderReturn } from 'features/nodes/util/graph/types';
-import { UnsupportedGenerationModeError } from 'features/nodes/util/graph/types';
-import { selectActiveTab } from 'features/ui/store/uiSelectors';
-import { t } from 'i18next';
-import type { Equals } from 'tsafe';
-import { assert } from 'tsafe';
-
-const log = logger('system');
-
-export const buildChatGPT4oGraph = async (arg: GraphBuilderArg): Promise => {
- const { generationMode, state, manager } = arg;
-
- if (generationMode !== 'txt2img' && generationMode !== 'img2img') {
- throw new UnsupportedGenerationModeError(t('toast.chatGPT4oIncompatibleGenerationMode'));
- }
-
- log.debug({ generationMode, manager: manager?.id }, 'Building ChatGPT 4o graph');
-
- const model = selectMainModelConfig(state);
-
- const refImages = selectRefImagesSlice(state);
-
- assert(model, 'No model selected');
- assert(model.base === 'chatgpt-4o', 'Selected model is not a ChatGPT 4o API model');
-
- const validRefImages = refImages.entities
- .filter((entity) => entity.isEnabled)
- .filter((entity) => isChatGPT4oReferenceImageConfig(entity.config))
- .filter((entity) => getGlobalReferenceImageWarnings(entity, model).length === 0)
- .toReversed(); // sends them in order they are displayed in the list
-
- let reference_images: ImageField[] | undefined = undefined;
-
- if (validRefImages.length > 0) {
- reference_images = [];
- for (const entity of validRefImages) {
- assert(entity.config.image, 'Image is required for reference image');
- reference_images.push(zImageField.parse(entity.config.image.crop?.image ?? entity.config.image.original.image));
- }
- }
-
- if (generationMode === 'txt2img') {
- const { originalSize, aspectRatio } = getOriginalAndScaledSizesForTextToImage(state);
- assert(isChatGPT4oAspectRatioID(aspectRatio.id), 'ChatGPT 4o does not support this aspect ratio');
-
- const g = new Graph(getPrefixedId('chatgpt_4o_txt2img_graph'));
- const positivePrompt = g.addNode({
- id: getPrefixedId('positive_prompt'),
- type: 'string',
- });
- const gptImage = g.addNode({
- // @ts-expect-error: These nodes are not available in the OSS application
- type: 'chatgpt_4o_generate_image',
- model: zModelIdentifierField.parse(model),
- aspect_ratio: aspectRatio.id,
- reference_images,
- ...selectCanvasOutputFields(state),
- });
-
- g.addEdge(
- positivePrompt,
- 'value',
- gptImage,
- // @ts-expect-error: These nodes are not available in the OSS application
- 'positive_prompt'
- );
- g.addEdgeToMetadata(positivePrompt, 'value', 'positive_prompt');
- g.upsertMetadata({
- model: Graph.getModelMetadataField(model),
- width: originalSize.width,
- height: originalSize.height,
- });
- return {
- g,
- positivePrompt,
- };
- } else if (generationMode === 'img2img') {
- const { aspectRatio, rect } = getOriginalAndScaledSizesForOtherModes(state);
- assert(isChatGPT4oAspectRatioID(aspectRatio.id), 'ChatGPT 4o does not support this aspect ratio');
-
- assert(manager !== null);
- const adapters = manager.compositor.getVisibleAdaptersOfType('raster_layer');
- const { image_name } = await manager.compositor.getCompositeImageDTO(adapters, rect, {
- is_intermediate: true,
- silent: true,
- });
- const g = new Graph(getPrefixedId('chatgpt_4o_img2img_graph'));
- const positivePrompt = g.addNode({
- id: getPrefixedId('positive_prompt'),
- type: 'string',
- });
- const gptImage = g.addNode({
- // @ts-expect-error: These nodes are not available in the OSS application
- type: 'chatgpt_4o_edit_image',
- model: zModelIdentifierField.parse(model),
- aspect_ratio: aspectRatio.id,
- base_image: { image_name },
- reference_images,
- ...selectCanvasOutputFields(state),
- });
-
- g.addEdge(
- positivePrompt,
- 'value',
- gptImage,
- // @ts-expect-error: These nodes are not available in the OSS application
- 'positive_prompt'
- );
- g.addEdgeToMetadata(positivePrompt, 'value', 'positive_prompt');
- g.upsertMetadata({
- model: Graph.getModelMetadataField(model),
- width: rect.width,
- height: rect.height,
- });
-
- if (selectActiveTab(state) === 'canvas') {
- g.upsertMetadata(selectCanvasMetadata(state));
- }
-
- g.setMetadataReceivingNode(gptImage);
-
- return {
- g,
- positivePrompt,
- };
- }
-
- assert>(false, 'Invalid generation mode for ChatGPT ');
-};
diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFluxKontextGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFluxKontextGraph.ts
deleted file mode 100644
index 164664e63bb..00000000000
--- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFluxKontextGraph.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import { logger } from 'app/logging/logger';
-import { getPrefixedId } from 'features/controlLayers/konva/util';
-import { selectMainModelConfig } from 'features/controlLayers/store/paramsSlice';
-import { selectRefImagesSlice } from 'features/controlLayers/store/refImagesSlice';
-import { isFluxKontextAspectRatioID, isFluxKontextReferenceImageConfig } from 'features/controlLayers/store/types';
-import { getGlobalReferenceImageWarnings } from 'features/controlLayers/store/validators';
-import { zImageField, zModelIdentifierField } from 'features/nodes/types/common';
-import { Graph } from 'features/nodes/util/graph/generation/Graph';
-import {
- getOriginalAndScaledSizesForTextToImage,
- selectCanvasOutputFields,
-} from 'features/nodes/util/graph/graphBuilderUtils';
-import type { GraphBuilderArg, GraphBuilderReturn } from 'features/nodes/util/graph/types';
-import { UnsupportedGenerationModeError } from 'features/nodes/util/graph/types';
-import { t } from 'i18next';
-import { assert } from 'tsafe';
-
-const log = logger('system');
-
-export const buildFluxKontextGraph = (arg: GraphBuilderArg): GraphBuilderReturn => {
- const { generationMode, state, manager } = arg;
-
- const model = selectMainModelConfig(state);
- assert(model, 'No model selected');
- assert(model.base === 'flux-kontext', 'Selected model is not a FLUX Kontext API model');
-
- if (generationMode !== 'txt2img') {
- throw new UnsupportedGenerationModeError(t('toast.fluxKontextIncompatibleGenerationMode'));
- }
-
- log.debug({ generationMode, manager: manager?.id }, 'Building FLUX Kontext graph');
-
- const { originalSize, aspectRatio } = getOriginalAndScaledSizesForTextToImage(state);
- assert(isFluxKontextAspectRatioID(aspectRatio.id), 'FLUX Kontext does not support this aspect ratio');
-
- const refImages = selectRefImagesSlice(state);
-
- const validRefImages = refImages.entities
- .filter((entity) => entity.isEnabled)
- .filter((entity) => isFluxKontextReferenceImageConfig(entity.config))
- .filter((entity) => getGlobalReferenceImageWarnings(entity, model).length === 0);
-
- const g = new Graph(getPrefixedId('flux_kontext_txt2img_graph'));
- const positivePrompt = g.addNode({
- id: getPrefixedId('positive_prompt'),
- type: 'string',
- });
-
- let fluxKontextImage;
-
- if (validRefImages.length > 0) {
- if (validRefImages.length === 1) {
- // Single reference image - use it directly
- const firstImage = validRefImages[0]?.config.image;
- assert(firstImage, 'First image should exist when validRefImages.length > 0');
-
- fluxKontextImage = g.addNode({
- // @ts-expect-error: These nodes are not available in the OSS application
- type: 'flux_kontext_edit_image',
- model: zModelIdentifierField.parse(model),
- aspect_ratio: aspectRatio.id,
- prompt_upsampling: true,
- input_image: zImageField.parse(firstImage.crop?.image ?? firstImage.original.image),
- ...selectCanvasOutputFields(state),
- });
- } else {
- // Multiple reference images - use concatenation
- const kontextConcatenator = g.addNode({
- id: getPrefixedId('flux_kontext_image_prep'),
- type: 'flux_kontext_image_prep',
- images: validRefImages.map(({ config }) =>
- zImageField.parse(config.image?.crop?.image ?? config.image?.original.image)
- ),
- });
-
- fluxKontextImage = g.addNode({
- // @ts-expect-error: These nodes are not available in the OSS application
- type: 'flux_kontext_edit_image',
- model: zModelIdentifierField.parse(model),
- aspect_ratio: aspectRatio.id,
- prompt_upsampling: true,
-
- ...selectCanvasOutputFields(state),
- });
- // @ts-expect-error: These nodes are not available in the OSS application
- g.addEdge(kontextConcatenator, 'image', fluxKontextImage, 'input_image');
- }
- } else {
- fluxKontextImage = g.addNode({
- // @ts-expect-error: These nodes are not available in the OSS application
- type: 'flux_kontext_generate_image',
- model: zModelIdentifierField.parse(model),
- aspect_ratio: aspectRatio.id,
- prompt_upsampling: true,
- ...selectCanvasOutputFields(state),
- });
- }
-
- g.addEdge(
- positivePrompt,
- 'value',
- fluxKontextImage,
- // @ts-expect-error: These nodes are not available in the OSS application
- 'positive_prompt'
- );
- g.addEdgeToMetadata(positivePrompt, 'value', 'positive_prompt');
-
- g.upsertMetadata({
- model: Graph.getModelMetadataField(model),
- width: originalSize.width,
- height: originalSize.height,
- });
-
- if (validRefImages.length > 0) {
- g.upsertMetadata({ ref_images: [validRefImages] }, 'merge');
- }
-
- g.setMetadataReceivingNode(fluxKontextImage);
-
- return {
- g,
- positivePrompt,
- };
-};
diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildGemini2_5Graph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildGemini2_5Graph.ts
deleted file mode 100644
index d31e8bcf7bf..00000000000
--- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildGemini2_5Graph.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { logger } from 'app/logging/logger';
-import { getPrefixedId } from 'features/controlLayers/konva/util';
-import { selectMainModelConfig } from 'features/controlLayers/store/paramsSlice';
-import { selectRefImagesSlice } from 'features/controlLayers/store/refImagesSlice';
-import { isGemini2_5ReferenceImageConfig } from 'features/controlLayers/store/types';
-import { getGlobalReferenceImageWarnings } from 'features/controlLayers/store/validators';
-import { type ImageField, zImageField } from 'features/nodes/types/common';
-import { Graph } from 'features/nodes/util/graph/generation/Graph';
-import { selectCanvasOutputFields } from 'features/nodes/util/graph/graphBuilderUtils';
-import type { GraphBuilderArg, GraphBuilderReturn } from 'features/nodes/util/graph/types';
-import { UnsupportedGenerationModeError } from 'features/nodes/util/graph/types';
-import { t } from 'i18next';
-import { assert } from 'tsafe';
-
-const log = logger('system');
-
-export const buildGemini2_5Graph = (arg: GraphBuilderArg): GraphBuilderReturn => {
- const { generationMode, state, manager } = arg;
-
- if (generationMode !== 'txt2img') {
- throw new UnsupportedGenerationModeError(
- t('toast.imagenIncompatibleGenerationMode', { model: 'Gemini 2.5 Flash Preview' })
- );
- }
-
- log.debug({ generationMode, manager: manager?.id }, 'Building Gemini 2.5 graph');
-
- const model = selectMainModelConfig(state);
-
- const refImages = selectRefImagesSlice(state);
-
- assert(model, 'No model selected');
- assert(model.base === 'gemini-2.5', 'Selected model is not a Gemini 2.5 API model');
-
- const validRefImages = refImages.entities
- .filter((entity) => entity.isEnabled)
- .filter((entity) => isGemini2_5ReferenceImageConfig(entity.config))
- .filter((entity) => getGlobalReferenceImageWarnings(entity, model).length === 0)
- .toReversed(); // sends them in order they are displayed in the list
-
- let reference_images: ImageField[] | undefined = undefined;
-
- if (validRefImages.length > 0) {
- reference_images = [];
- for (const entity of validRefImages) {
- assert(entity.config.image, 'Image is required for reference image');
- reference_images.push(zImageField.parse(entity.config.image.crop?.image ?? entity.config.image.original.image));
- }
- }
-
- const g = new Graph(getPrefixedId('gemini_2_5_txt2img_graph'));
- const positivePrompt = g.addNode({
- id: getPrefixedId('positive_prompt'),
- type: 'string',
- });
- const geminiImage = g.addNode({
- // @ts-expect-error: These nodes are not available in the OSS application
- type: 'google_gemini_generate_image',
- reference_images,
- ...selectCanvasOutputFields(state),
- });
-
- g.addEdge(
- positivePrompt,
- 'value',
- geminiImage,
- // @ts-expect-error: These nodes are not available in the OSS application
- 'positive_prompt'
- );
- g.addEdgeToMetadata(positivePrompt, 'value', 'positive_prompt');
- g.upsertMetadata({
- model: Graph.getModelMetadataField(model),
- });
-
- g.setMetadataReceivingNode(geminiImage);
-
- return {
- g,
- positivePrompt,
- };
-};
diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildImagen3Graph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildImagen3Graph.ts
deleted file mode 100644
index 53ba5629e43..00000000000
--- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildImagen3Graph.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import { logger } from 'app/logging/logger';
-import { getPrefixedId } from 'features/controlLayers/konva/util';
-import { selectMainModelConfig } from 'features/controlLayers/store/paramsSlice';
-import { isImagenAspectRatioID } from 'features/controlLayers/store/types';
-import { zModelIdentifierField } from 'features/nodes/types/common';
-import { Graph } from 'features/nodes/util/graph/generation/Graph';
-import {
- getOriginalAndScaledSizesForTextToImage,
- selectCanvasOutputFields,
- selectPresetModifiedPrompts,
-} from 'features/nodes/util/graph/graphBuilderUtils';
-import type { GraphBuilderArg, GraphBuilderReturn } from 'features/nodes/util/graph/types';
-import { UnsupportedGenerationModeError } from 'features/nodes/util/graph/types';
-import { t } from 'i18next';
-import { assert } from 'tsafe';
-
-const log = logger('system');
-
-export const buildImagen3Graph = (arg: GraphBuilderArg): GraphBuilderReturn => {
- const { generationMode, state, manager } = arg;
- log.debug({ generationMode, manager: manager?.id }, 'Building Imagen3 graph');
-
- const model = selectMainModelConfig(state);
-
- assert(model, 'No model selected');
- assert(model.base === 'imagen3', 'Selected model is not an Imagen3 API model');
-
- if (generationMode !== 'txt2img') {
- throw new UnsupportedGenerationModeError(t('toast.imagenIncompatibleGenerationMode', { model: 'Imagen3' }));
- }
-
- const prompts = selectPresetModifiedPrompts(state);
- assert(prompts.positive.length > 0, 'Imagen3 requires positive prompt to have at least one character');
-
- const { originalSize, aspectRatio } = getOriginalAndScaledSizesForTextToImage(state);
- assert(isImagenAspectRatioID(aspectRatio.id), 'Imagen3 does not support this aspect ratio');
-
- const g = new Graph(getPrefixedId('imagen3_txt2img_graph'));
- const positivePrompt = g.addNode({
- id: getPrefixedId('positive_prompt'),
- type: 'string',
- });
- const imagen3 = g.addNode({
- // @ts-expect-error: These nodes are not available in the OSS application
- type: 'google_imagen3_generate_image',
- model: zModelIdentifierField.parse(model),
- negative_prompt: prompts.negative,
- aspect_ratio: aspectRatio.id,
- // When enhance_prompt is true, Imagen3 will return a new image every time, ignoring the seed.
- enhance_prompt: true,
- ...selectCanvasOutputFields(state),
- });
-
- g.addEdge(
- positivePrompt,
- 'value',
- imagen3,
- // @ts-expect-error: These nodes are not available in the OSS application
- 'positive_prompt'
- );
- g.addEdgeToMetadata(positivePrompt, 'value', 'positive_prompt');
-
- g.upsertMetadata({
- negative_prompt: prompts.negative,
- width: originalSize.width,
- height: originalSize.height,
- model: Graph.getModelMetadataField(model),
- });
-
- g.setMetadataReceivingNode(imagen3);
-
- return {
- g,
- positivePrompt,
- };
-};
diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildImagen4Graph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildImagen4Graph.ts
deleted file mode 100644
index 56ce02033c3..00000000000
--- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildImagen4Graph.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { logger } from 'app/logging/logger';
-import { getPrefixedId } from 'features/controlLayers/konva/util';
-import { selectMainModelConfig } from 'features/controlLayers/store/paramsSlice';
-import { isImagenAspectRatioID } from 'features/controlLayers/store/types';
-import { zModelIdentifierField } from 'features/nodes/types/common';
-import { Graph } from 'features/nodes/util/graph/generation/Graph';
-import {
- getOriginalAndScaledSizesForTextToImage,
- selectCanvasOutputFields,
- selectPresetModifiedPrompts,
-} from 'features/nodes/util/graph/graphBuilderUtils';
-import type { GraphBuilderArg, GraphBuilderReturn } from 'features/nodes/util/graph/types';
-import { UnsupportedGenerationModeError } from 'features/nodes/util/graph/types';
-import { t } from 'i18next';
-import { assert } from 'tsafe';
-
-const log = logger('system');
-
-export const buildImagen4Graph = (arg: GraphBuilderArg): GraphBuilderReturn => {
- const { generationMode, state, manager } = arg;
- log.debug({ generationMode, manager: manager?.id }, 'Building Imagen4 graph');
-
- const model = selectMainModelConfig(state);
- assert(model, 'No model selected');
- assert(model.base === 'imagen4', 'Selected model is not a Imagen4 API model');
-
- if (generationMode !== 'txt2img') {
- throw new UnsupportedGenerationModeError(t('toast.imagenIncompatibleGenerationMode', { model: 'Imagen4' }));
- }
-
- const prompts = selectPresetModifiedPrompts(state);
- assert(prompts.positive.length > 0, 'Imagen4 requires positive prompt to have at least one character');
-
- const { originalSize, aspectRatio } = getOriginalAndScaledSizesForTextToImage(state);
- assert(isImagenAspectRatioID(aspectRatio.id), 'Imagen4 does not support this aspect ratio');
-
- const g = new Graph(getPrefixedId('imagen4_txt2img_graph'));
- const positivePrompt = g.addNode({
- id: getPrefixedId('positive_prompt'),
- type: 'string',
- });
- const imagen4 = g.addNode({
- // @ts-expect-error: These nodes are not available in the OSS application
- type: 'google_imagen4_generate_image',
- model: zModelIdentifierField.parse(model),
- negative_prompt: prompts.negative,
- aspect_ratio: aspectRatio.id,
- // When enhance_prompt is true, Imagen4 will return a new image every time, ignoring the seed.
- enhance_prompt: true,
- ...selectCanvasOutputFields(state),
- });
-
- g.addEdge(
- positivePrompt,
- 'value',
- imagen4,
- // @ts-expect-error: These nodes are not available in the OSS application
- 'positive_prompt'
- );
- g.addEdgeToMetadata(positivePrompt, 'value', 'positive_prompt');
-
- g.upsertMetadata({
- negative_prompt: prompts.negative,
- width: originalSize.width,
- height: originalSize.height,
- model: Graph.getModelMetadataField(model),
- });
-
- g.setMetadataReceivingNode(imagen4);
-
- return {
- g,
- positivePrompt,
- };
-};
diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildRunwayVideoGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildRunwayVideoGraph.ts
deleted file mode 100644
index 29af7064005..00000000000
--- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildRunwayVideoGraph.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import { logger } from 'app/logging/logger';
-import { getPrefixedId } from 'features/controlLayers/konva/util';
-import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
-import { zImageField } from 'features/nodes/types/common';
-import { Graph } from 'features/nodes/util/graph/generation/Graph';
-import { selectPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
-import type { GraphBuilderArg, GraphBuilderReturn } from 'features/nodes/util/graph/types';
-import { UnsupportedGenerationModeError } from 'features/nodes/util/graph/types';
-import {
- selectStartingFrameImage,
- selectVideoModelConfig,
- selectVideoSlice,
-} from 'features/parameters/store/videoSlice';
-import { t } from 'i18next';
-import { assert } from 'tsafe';
-
-const log = logger('system');
-
-export const buildRunwayVideoGraph = (arg: GraphBuilderArg): GraphBuilderReturn => {
- const { generationMode, state, manager } = arg;
-
- log.debug({ generationMode, manager: manager?.id }, 'Building Runway video graph');
-
- const supportedModes = ['txt2img'];
- if (!supportedModes.includes(generationMode)) {
- throw new UnsupportedGenerationModeError(t('toast.runwayIncompatibleGenerationMode'));
- }
-
- const model = selectVideoModelConfig(state);
- assert(model, 'No model selected');
- assert(model.base === 'runway', 'Selected model is not a Runway model');
-
- const params = selectParamsSlice(state);
- const videoParams = selectVideoSlice(state);
- const prompts = selectPresetModifiedPrompts(state);
- assert(prompts.positive.length > 0, 'Runway video requires positive prompt to have at least one character');
-
- const startingFrameImage = selectStartingFrameImage(state);
-
- assert(startingFrameImage, 'Video starting frame is required for runway video generation');
- const firstFrameImageField = zImageField.parse(startingFrameImage.crop?.image ?? startingFrameImage.original);
-
- const { seed, shouldRandomizeSeed } = params;
- const { videoDuration, videoAspectRatio, videoResolution } = videoParams;
-
- const finalSeed = shouldRandomizeSeed ? undefined : seed;
-
- const g = new Graph(getPrefixedId('runway_video_graph'));
-
- const positivePrompt = g.addNode({
- id: getPrefixedId('positive_prompt'),
- type: 'string',
- value: prompts.positive,
- });
-
- // Create the runway video generation node
- const runwayVideoNode = g.addNode({
- id: getPrefixedId('runway_generate_video'),
- // @ts-expect-error: This node is not available in the OSS application
- type: 'runway_generate_video',
- duration: parseInt(videoDuration || '0', 10),
- aspect_ratio: videoAspectRatio,
- seed: finalSeed,
- first_frame_image: firstFrameImageField,
- });
-
- // @ts-expect-error: This node is not available in the OSS application
- g.addEdge(positivePrompt, 'value', runwayVideoNode, 'prompt');
-
- // Set up metadata
- g.upsertMetadata({
- model: Graph.getModelMetadataField(model),
- positive_prompt: prompts.positive,
- duration: videoDuration,
- aspect_ratio: videoAspectRatio,
- resolution: videoResolution,
- seed: finalSeed,
- first_frame_image: startingFrameImage,
- });
-
- g.setMetadataReceivingNode(runwayVideoNode);
-
- return {
- g,
- positivePrompt,
- };
-};
diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildVeo3VideoGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildVeo3VideoGraph.ts
deleted file mode 100644
index b33c9cdde5c..00000000000
--- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildVeo3VideoGraph.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import { logger } from 'app/logging/logger';
-import { getPrefixedId } from 'features/controlLayers/konva/util';
-import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
-import { zImageField } from 'features/nodes/types/common';
-import { Graph } from 'features/nodes/util/graph/generation/Graph';
-import { selectPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilderUtils';
-import type { GraphBuilderArg, GraphBuilderReturn } from 'features/nodes/util/graph/types';
-import { UnsupportedGenerationModeError } from 'features/nodes/util/graph/types';
-import {
- selectStartingFrameImage,
- selectVideoModelConfig,
- selectVideoSlice,
-} from 'features/parameters/store/videoSlice';
-import { t } from 'i18next';
-import { assert } from 'tsafe';
-
-const log = logger('system');
-
-export const buildVeo3VideoGraph = (arg: GraphBuilderArg): GraphBuilderReturn => {
- const { generationMode, state, manager } = arg;
-
- log.debug({ generationMode, manager: manager?.id }, 'Building Veo3 video graph');
-
- const supportedModes = ['txt2img'];
- if (!supportedModes.includes(generationMode)) {
- throw new UnsupportedGenerationModeError(t('toast.veo3IncompatibleGenerationMode'));
- }
-
- const model = selectVideoModelConfig(state);
- assert(model, 'No model selected');
- assert(model.base === 'veo3', 'Selected model is not a Veo3 model');
-
- const params = selectParamsSlice(state);
- const videoParams = selectVideoSlice(state);
- const prompts = selectPresetModifiedPrompts(state);
- assert(prompts.positive.length > 0, 'Veo3 video requires positive prompt to have at least one character');
-
- const { seed, shouldRandomizeSeed } = params;
- const { videoResolution, videoDuration, videoAspectRatio } = videoParams;
- const finalSeed = shouldRandomizeSeed ? undefined : seed;
-
- const g = new Graph(getPrefixedId('veo3_video_graph'));
-
- const positivePrompt = g.addNode({
- id: getPrefixedId('positive_prompt'),
- type: 'string',
- value: prompts.positive,
- });
-
- // Create the veo3 video generation node
- const veo3VideoNode = g.addNode({
- id: getPrefixedId('google_veo_3_generate_video'),
- // @ts-expect-error: This node is not available in the OSS application
- type: 'google_veo_3_generate_video',
- model: model,
- aspect_ratio: '16:9',
- resolution: videoResolution,
- seed: finalSeed,
- });
-
- const startingFrameImage = selectStartingFrameImage(state);
-
- if (startingFrameImage) {
- const startingFrameImageField = zImageField.parse(startingFrameImage.crop?.image ?? startingFrameImage.original);
- // @ts-expect-error: This node is not available in the OSS application
- veo3VideoNode.starting_image = startingFrameImageField;
- }
-
- // @ts-expect-error: This node is not available in the OSS application
- g.addEdge(positivePrompt, 'value', veo3VideoNode, 'prompt');
-
- // Set up metadata
- g.upsertMetadata({
- model: Graph.getModelMetadataField(model),
- positive_prompt: prompts.positive,
- duration: videoDuration,
- aspect_ratio: videoAspectRatio,
- resolution: videoResolution,
- seed: finalSeed,
- first_frame_image: startingFrameImage,
- });
-
- g.setMetadataReceivingNode(veo3VideoNode);
-
- return {
- g,
- positivePrompt,
- };
-};
diff --git a/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamCFGRescaleMultiplier.tsx b/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamCFGRescaleMultiplier.tsx
index c601e3b9b60..56024b60a32 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamCFGRescaleMultiplier.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamCFGRescaleMultiplier.tsx
@@ -2,13 +2,21 @@ import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { selectCFGRescaleMultiplier, setCfgRescaleMultiplier } from 'features/controlLayers/store/paramsSlice';
-import { selectCFGRescaleMultiplierConfig } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
+export const CONSTRAINTS = {
+ initial: 0,
+ sliderMin: 0,
+ sliderMax: 0.99,
+ numberInputMin: 0,
+ numberInputMax: 0.99,
+ fineStep: 0.05,
+ coarseStep: 0.1,
+};
+
const ParamCFGRescaleMultiplier = () => {
const cfgRescaleMultiplier = useAppSelector(selectCFGRescaleMultiplier);
- const config = useAppSelector(selectCFGRescaleMultiplierConfig);
const dispatch = useAppDispatch();
const { t } = useTranslation();
@@ -22,21 +30,21 @@ const ParamCFGRescaleMultiplier = () => {
diff --git a/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamClipSkip.tsx b/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamClipSkip.tsx
index 1d5eacc669c..10cc8b0d984 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamClipSkip.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Advanced/ParamClipSkip.tsx
@@ -3,13 +3,19 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { selectCLIPSkip, selectModel, setClipSkip } from 'features/controlLayers/store/paramsSlice';
import { CLIP_SKIP_MAP } from 'features/parameters/types/constants';
-import { selectCLIPSkipConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
+const CONSTRAINTS = {
+ initial: 0,
+ sliderMin: 0,
+ numberInputMin: 0,
+ fineStep: 1,
+ coarseStep: 1,
+};
+
const ParamClipSkip = () => {
const clipSkip = useAppSelector(selectCLIPSkip);
- const config = useAppSelector(selectCLIPSkipConfig);
const model = useAppSelector(selectModel);
const dispatch = useAppDispatch();
@@ -47,21 +53,21 @@ const ParamClipSkip = () => {
diff --git a/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxAspectRatioSelect.tsx b/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxAspectRatioSelect.tsx
index 40145839085..7fa250caad4 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxAspectRatioSelect.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxAspectRatioSelect.tsx
@@ -3,20 +3,9 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { bboxAspectRatioIdChanged } from 'features/controlLayers/store/canvasSlice';
import { useCanvasIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
-import {
- selectIsChatGPT4o,
- selectIsFluxKontext,
- selectIsImagen3,
- selectIsImagen4,
-} from 'features/controlLayers/store/paramsSlice';
+import { selectIsFluxKontext } from 'features/controlLayers/store/paramsSlice';
import { selectAspectRatioID } from 'features/controlLayers/store/selectors';
-import {
- isAspectRatioID,
- zAspectRatioID,
- zChatGPT4oAspectRatioID,
- zFluxKontextAspectRatioID,
- zImagen3AspectRatioID,
-} from 'features/controlLayers/store/types';
+import { isAspectRatioID, zAspectRatioID, zFluxKontextAspectRatioID } from 'features/controlLayers/store/types';
import type { ChangeEventHandler } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -27,24 +16,14 @@ export const BboxAspectRatioSelect = memo(() => {
const dispatch = useAppDispatch();
const id = useAppSelector(selectAspectRatioID);
const isStaging = useCanvasIsStaging();
- const isImagen3 = useAppSelector(selectIsImagen3);
- const isChatGPT4o = useAppSelector(selectIsChatGPT4o);
- const isImagen4 = useAppSelector(selectIsImagen4);
const isFluxKontext = useAppSelector(selectIsFluxKontext);
const options = useMemo(() => {
- // Imagen3 and ChatGPT4o have different aspect ratio options, and do not support freeform sizes
- if (isImagen3 || isImagen4) {
- return zImagen3AspectRatioID.options;
- }
- if (isChatGPT4o) {
- return zChatGPT4oAspectRatioID.options;
- }
if (isFluxKontext) {
return zFluxKontextAspectRatioID.options;
}
// All other models
return zAspectRatioID.options;
- }, [isImagen3, isChatGPT4o, isImagen4, isFluxKontext]);
+ }, [isFluxKontext]);
const onChange = useCallback>(
(e) => {
diff --git a/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxHeight.tsx
index dd8e319447d..cfaee3d0c95 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxHeight.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxHeight.tsx
@@ -4,16 +4,24 @@ import { InformationalPopover } from 'common/components/InformationalPopover/Inf
import { bboxHeightChanged } from 'features/controlLayers/store/canvasSlice';
import { selectGridSize, selectHeight, selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { useIsBboxSizeLocked } from 'features/parameters/components/Bbox/use-is-bbox-size-locked';
-import { selectHeightConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
+const CONSTRAINTS = {
+ initial: 512,
+ sliderMin: 64,
+ sliderMax: 1536,
+ numberInputMin: 64,
+ numberInputMax: 4096,
+ fineStep: 8,
+ coarseStep: 64,
+};
+
export const BboxHeight = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const optimalDimension = useAppSelector(selectOptimalDimension);
const height = useAppSelector(selectHeight);
- const config = useAppSelector(selectHeightConfig);
const isBboxSizeLocked = useIsBboxSizeLocked();
const gridSize = useAppSelector(selectGridSize);
@@ -24,10 +32,7 @@ export const BboxHeight = memo(() => {
[dispatch]
);
- const marks = useMemo(
- () => [config.sliderMin, optimalDimension, config.sliderMax],
- [config.sliderMin, config.sliderMax, optimalDimension]
- );
+ const marks = useMemo(() => [CONSTRAINTS.sliderMin, optimalDimension, CONSTRAINTS.sliderMax], [optimalDimension]);
return (
@@ -38,9 +43,9 @@ export const BboxHeight = memo(() => {
value={height}
defaultValue={optimalDimension}
onChange={onChange}
- min={config.sliderMin}
- max={config.sliderMax}
- step={config.coarseStep}
+ min={CONSTRAINTS.sliderMin}
+ max={CONSTRAINTS.sliderMax}
+ step={CONSTRAINTS.coarseStep}
fineStep={gridSize}
marks={marks}
/>
@@ -48,9 +53,9 @@ export const BboxHeight = memo(() => {
value={height}
defaultValue={optimalDimension}
onChange={onChange}
- min={config.numberInputMin}
- max={config.numberInputMax}
- step={config.coarseStep}
+ min={CONSTRAINTS.numberInputMin}
+ max={CONSTRAINTS.numberInputMax}
+ step={CONSTRAINTS.coarseStep}
fineStep={gridSize}
/>
diff --git a/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxScaledHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxScaledHeight.tsx
index da7338e72e3..db9b53f6ece 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxScaledHeight.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxScaledHeight.tsx
@@ -4,16 +4,21 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { bboxScaledHeightChanged } from 'features/controlLayers/store/canvasSlice';
import { selectCanvasSlice, selectGridSize, selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { useIsBboxSizeLocked } from 'features/parameters/components/Bbox/use-is-bbox-size-locked';
-import { selectConfigSlice } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const selectIsManual = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.scaleMethod === 'manual');
const selectScaledHeight = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.scaledSize.height);
-const selectScaledBoundingBoxHeightConfig = createSelector(
- selectConfigSlice,
- (config) => config.sd.scaledBoundingBoxHeight
-);
+
+const CONSTRAINTS = {
+ initial: 512,
+ sliderMin: 64,
+ sliderMax: 1536,
+ numberInputMin: 64,
+ numberInputMax: 4096,
+ fineStep: 8,
+ coarseStep: 64,
+};
const BboxScaledHeight = () => {
const { t } = useTranslation();
@@ -22,7 +27,6 @@ const BboxScaledHeight = () => {
const optimalDimension = useAppSelector(selectOptimalDimension);
const isManual = useAppSelector(selectIsManual);
const scaledHeight = useAppSelector(selectScaledHeight);
- const config = useAppSelector(selectScaledBoundingBoxHeightConfig);
const gridSize = useAppSelector(selectGridSize);
const onChange = useCallback(
@@ -36,9 +40,9 @@ const BboxScaledHeight = () => {
{t('parameters.scaledHeight')}
{
defaultValue={optimalDimension}
/>
canvas.bbox.scaleMethod === 'manual');
const selectScaledWidth = createSelector(selectCanvasSlice, (canvas) => canvas.bbox.scaledSize.width);
-const selectScaledBoundingBoxWidthConfig = createSelector(
- selectConfigSlice,
- (config) => config.sd.scaledBoundingBoxWidth
-);
+
+const CONSTRAINTS = {
+ initial: 512,
+ sliderMin: 64,
+ sliderMax: 1536,
+ numberInputMin: 64,
+ numberInputMax: 4096,
+ fineStep: 8,
+ coarseStep: 64,
+};
const BboxScaledWidth = () => {
const { t } = useTranslation();
@@ -22,7 +27,6 @@ const BboxScaledWidth = () => {
const optimalDimension = useAppSelector(selectOptimalDimension);
const isManual = useAppSelector(selectIsManual);
const scaledWidth = useAppSelector(selectScaledWidth);
- const config = useAppSelector(selectScaledBoundingBoxWidthConfig);
const gridSize = useAppSelector(selectGridSize);
const onChange = useCallback(
@@ -36,9 +40,9 @@ const BboxScaledWidth = () => {
{t('parameters.scaledWidth')}
{
marks
/>
{
- const supportsAspectRatio = useAppSelector(selectModelSupportsAspectRatio);
- const supportsPixelDimensions = useAppSelector(selectModelSupportsPixelDimensions);
-
- if (!supportsAspectRatio) {
- return null;
- }
-
return (
@@ -30,20 +17,11 @@ export const BboxSettings = memo(() => {
- {supportsPixelDimensions && (
- <>
-
-
- >
- )}
+
+
- {supportsPixelDimensions && (
- <>
-
-
- >
- )}
- {!supportsPixelDimensions && }
+
+
diff --git a/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxWidth.tsx
index 8ad457da7ac..740df6cf218 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxWidth.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Bbox/BboxWidth.tsx
@@ -4,16 +4,24 @@ import { InformationalPopover } from 'common/components/InformationalPopover/Inf
import { bboxWidthChanged } from 'features/controlLayers/store/canvasSlice';
import { selectGridSize, selectOptimalDimension, selectWidth } from 'features/controlLayers/store/selectors';
import { useIsBboxSizeLocked } from 'features/parameters/components/Bbox/use-is-bbox-size-locked';
-import { selectWidthConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
+const CONSTRAINTS = {
+ initial: 512,
+ sliderMin: 64,
+ sliderMax: 1536,
+ numberInputMin: 64,
+ numberInputMax: 4096,
+ fineStep: 8,
+ coarseStep: 64,
+};
+
export const BboxWidth = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const width = useAppSelector(selectWidth);
const optimalDimension = useAppSelector(selectOptimalDimension);
- const config = useAppSelector(selectWidthConfig);
const isBboxSizeLocked = useIsBboxSizeLocked();
const gridSize = useAppSelector(selectGridSize);
@@ -24,10 +32,7 @@ export const BboxWidth = memo(() => {
[dispatch]
);
- const marks = useMemo(
- () => [config.sliderMin, optimalDimension, config.sliderMax],
- [config.sliderMax, config.sliderMin, optimalDimension]
- );
+ const marks = useMemo(() => [CONSTRAINTS.sliderMin, optimalDimension, CONSTRAINTS.sliderMax], [optimalDimension]);
return (
@@ -38,9 +43,9 @@ export const BboxWidth = memo(() => {
value={width}
onChange={onChange}
defaultValue={optimalDimension}
- min={config.sliderMin}
- max={config.sliderMax}
- step={config.coarseStep}
+ min={CONSTRAINTS.sliderMin}
+ max={CONSTRAINTS.sliderMax}
+ step={CONSTRAINTS.coarseStep}
fineStep={gridSize}
marks={marks}
/>
@@ -48,9 +53,9 @@ export const BboxWidth = memo(() => {
value={width}
onChange={onChange}
defaultValue={optimalDimension}
- min={config.numberInputMin}
- max={config.numberInputMax}
- step={config.coarseStep}
+ min={CONSTRAINTS.numberInputMin}
+ max={CONSTRAINTS.numberInputMax}
+ step={CONSTRAINTS.coarseStep}
fineStep={gridSize}
/>
diff --git a/invokeai/frontend/web/src/features/parameters/components/Bbox/use-is-bbox-size-locked.ts b/invokeai/frontend/web/src/features/parameters/components/Bbox/use-is-bbox-size-locked.ts
index 57b55d8a21e..eaf13811088 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Bbox/use-is-bbox-size-locked.ts
+++ b/invokeai/frontend/web/src/features/parameters/components/Bbox/use-is-bbox-size-locked.ts
@@ -1,9 +1,6 @@
-import { useAppSelector } from 'app/store/storeHooks';
import { useCanvasIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
-import { selectIsApiBaseModel } from 'features/controlLayers/store/paramsSlice';
export const useIsBboxSizeLocked = () => {
const isStaging = useCanvasIsStaging();
- const isApiModel = useAppSelector(selectIsApiBaseModel);
- return isApiModel || isStaging;
+ return isStaging;
};
diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/Compositing/CoherencePass/ParamCanvasCoherenceEdgeSize.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/Compositing/CoherencePass/ParamCanvasCoherenceEdgeSize.tsx
index 007b2b04887..ed830413967 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Canvas/Compositing/CoherencePass/ParamCanvasCoherenceEdgeSize.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/Compositing/CoherencePass/ParamCanvasCoherenceEdgeSize.tsx
@@ -2,14 +2,22 @@ import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { selectCanvasCoherenceEdgeSize, setCanvasCoherenceEdgeSize } from 'features/controlLayers/store/paramsSlice';
-import { selectCanvasCoherenceEdgeSizeConfig } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
+const CONSTRAINTS = {
+ initial: 16,
+ sliderMin: 0,
+ sliderMax: 128,
+ numberInputMin: 0,
+ numberInputMax: 1024,
+ fineStep: 8,
+ coarseStep: 16,
+};
+
const ParamCanvasCoherenceEdgeSize = () => {
const dispatch = useAppDispatch();
const canvasCoherenceEdgeSize = useAppSelector(selectCanvasCoherenceEdgeSize);
- const config = useAppSelector(selectCanvasCoherenceEdgeSizeConfig);
const { t } = useTranslation();
@@ -26,22 +34,22 @@ const ParamCanvasCoherenceEdgeSize = () => {
{t('parameters.coherenceEdgeSize')}
diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/Compositing/MaskAdjustment/ParamMaskBlur.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/Compositing/MaskAdjustment/ParamMaskBlur.tsx
index a165388fdcd..082e9ee8097 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Canvas/Compositing/MaskAdjustment/ParamMaskBlur.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/Compositing/MaskAdjustment/ParamMaskBlur.tsx
@@ -2,15 +2,23 @@ import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { selectMaskBlur, setMaskBlur } from 'features/controlLayers/store/paramsSlice';
-import { selectMaskBlurConfig } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
+const CONSTRAINTS = {
+ initial: 16,
+ sliderMin: 0,
+ sliderMax: 128,
+ numberInputMin: 0,
+ numberInputMax: 512,
+ fineStep: 1,
+ coarseStep: 1,
+};
+
const ParamMaskBlur = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const maskBlur = useAppSelector(selectMaskBlur);
- const config = useAppSelector(selectMaskBlurConfig);
const handleChange = useCallback(
(v: number) => {
@@ -27,21 +35,21 @@ const ParamMaskBlur = () => {
);
diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillPatchmatchDownscaleSize.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillPatchmatchDownscaleSize.tsx
index f2998b9f84b..5b50bdbacd3 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillPatchmatchDownscaleSize.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillPatchmatchDownscaleSize.tsx
@@ -6,15 +6,23 @@ import {
selectInfillPatchmatchDownscaleSize,
setInfillPatchmatchDownscaleSize,
} from 'features/controlLayers/store/paramsSlice';
-import { selectInfillPatchmatchDownscaleSizeConfig } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
+const CONSTRAINTS = {
+ initial: 1,
+ sliderMin: 1,
+ sliderMax: 10,
+ numberInputMin: 1,
+ numberInputMax: 10,
+ fineStep: 1,
+ coarseStep: 1,
+};
+
const ParamInfillPatchmatchDownscaleSize = () => {
const dispatch = useAppDispatch();
const infillMethod = useAppSelector(selectInfillMethod);
const infillPatchmatchDownscaleSize = useAppSelector(selectInfillPatchmatchDownscaleSize);
- const config = useAppSelector(selectInfillPatchmatchDownscaleSizeConfig);
const { t } = useTranslation();
@@ -34,20 +42,20 @@ const ParamInfillPatchmatchDownscaleSize = () => {
value={infillPatchmatchDownscaleSize}
onChange={handleChange}
marks
- defaultValue={config.initial}
- min={config.sliderMin}
- max={config.sliderMax}
- step={config.coarseStep}
- fineStep={config.fineStep}
+ defaultValue={CONSTRAINTS.initial}
+ min={CONSTRAINTS.sliderMin}
+ max={CONSTRAINTS.sliderMax}
+ step={CONSTRAINTS.coarseStep}
+ fineStep={CONSTRAINTS.fineStep}
/>
);
diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillTilesize.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillTilesize.tsx
index 3df4b3e9282..dde61cc6271 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillTilesize.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillTilesize.tsx
@@ -1,14 +1,22 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectInfillMethod, selectInfillTileSize, setInfillTileSize } from 'features/controlLayers/store/paramsSlice';
-import { selectInfillTileSizeConfig } from 'features/system/store/configSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
+const CONSTRAINTS = {
+ initial: 32,
+ sliderMin: 16,
+ sliderMax: 64,
+ numberInputMin: 16,
+ numberInputMax: 256,
+ fineStep: 1,
+ coarseStep: 1,
+};
+
const ParamInfillTileSize = () => {
const dispatch = useAppDispatch();
const infillTileSize = useAppSelector(selectInfillTileSize);
- const config = useAppSelector(selectInfillTileSizeConfig);
const infillMethod = useAppSelector(selectInfillMethod);
const { t } = useTranslation();
@@ -26,21 +34,21 @@ const ParamInfillTileSize = () => {
);
diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamCFGScale.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamCFGScale.tsx
index 145ca6f2da7..c0e7a3c2a72 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamCFGScale.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamCFGScale.tsx
@@ -2,19 +2,25 @@ import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { selectCFGScale, setCfgScale } from 'features/controlLayers/store/paramsSlice';
-import { selectCFGScaleConfig } from 'features/system/store/configSlice';
-import { memo, useCallback, useMemo } from 'react';
+import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
+export const CONSTRAINTS = {
+ initial: 7,
+ sliderMin: 1,
+ sliderMax: 20,
+ numberInputMin: 1,
+ numberInputMax: 200,
+ fineStep: 0.1,
+ coarseStep: 0.5,
+};
+
+export const MARKS = [CONSTRAINTS.sliderMin, Math.floor(CONSTRAINTS.sliderMax / 2), CONSTRAINTS.sliderMax];
+
const ParamCFGScale = () => {
const cfgScale = useAppSelector(selectCFGScale);
- const config = useAppSelector(selectCFGScaleConfig);
const dispatch = useAppDispatch();
const { t } = useTranslation();
- const marks = useMemo(
- () => [config.sliderMin, Math.floor(config.sliderMax / 2), config.sliderMax],
- [config.sliderMax, config.sliderMin]
- );
const onChange = useCallback((v: number) => dispatch(setCfgScale(v)), [dispatch]);
return (
@@ -24,21 +30,21 @@ const ParamCFGScale = () => {
diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamGuidance.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamGuidance.tsx
index 86740e0846d..290f170b6ce 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamGuidance.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamGuidance.tsx
@@ -2,23 +2,29 @@ import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { selectGuidance, setGuidance } from 'features/controlLayers/store/paramsSlice';
-import { selectGuidanceConfig } from 'features/system/store/configSlice';
-import { memo, useCallback, useMemo } from 'react';
+import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
+export const CONSTRAINTS = {
+ initial: 4,
+ sliderMin: 2,
+ sliderMax: 6,
+ numberInputMin: 1,
+ numberInputMax: 20,
+ fineStep: 0.1,
+ coarseStep: 0.5,
+};
+
+export const MARKS = [
+ CONSTRAINTS.sliderMin,
+ Math.floor(CONSTRAINTS.sliderMax - (CONSTRAINTS.sliderMax - CONSTRAINTS.sliderMin) / 2),
+ CONSTRAINTS.sliderMax,
+];
+
const ParamGuidance = () => {
const guidance = useAppSelector(selectGuidance);
- const config = useAppSelector(selectGuidanceConfig);
const dispatch = useAppDispatch();
const { t } = useTranslation();
- const marks = useMemo(
- () => [
- config.sliderMin,
- Math.floor(config.sliderMax - (config.sliderMax - config.sliderMin) / 2),
- config.sliderMax,
- ],
- [config.sliderMax, config.sliderMin]
- );
const onChange = useCallback((v: number) => dispatch(setGuidance(v)), [dispatch]);
return (
@@ -28,21 +34,21 @@ const ParamGuidance = () => {
diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx
index 8001d81c9f2..73d22f0eac7 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx
@@ -1,5 +1,4 @@
import { Box, Flex, Textarea } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHooks';
import { usePersistedTextAreaSize } from 'common/hooks/usePersistedTextareaSize';
import {
@@ -8,17 +7,12 @@ import {
selectPositivePrompt,
selectPositivePromptHistory,
} from 'features/controlLayers/store/paramsSlice';
-import { promptGenerationFromImageDndTarget } from 'features/dnd/dnd';
-import { DndDropTarget } from 'features/dnd/DndDropTarget';
import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton';
import { NegativePromptToggleButton } from 'features/parameters/components/Core/NegativePromptToggleButton';
import { PromptLabel } from 'features/parameters/components/Prompts/PromptLabel';
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
import { ViewModePrompt } from 'features/parameters/components/Prompts/ViewModePrompt';
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
-import { PromptExpansionMenu } from 'features/prompt/PromptExpansion/PromptExpansionMenu';
-import { PromptExpansionOverlay } from 'features/prompt/PromptExpansion/PromptExpansionOverlay';
-import { promptExpansionApi } from 'features/prompt/PromptExpansion/state';
import { PromptPopover } from 'features/prompt/PromptPopover';
import { usePrompt } from 'features/prompt/usePrompt';
import {
@@ -26,9 +20,7 @@ import {
selectStylePresetViewMode,
} from 'features/stylePresets/store/stylePresetSlice';
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
-import { selectAllowPromptExpansion } from 'features/system/store/configSlice';
-import { selectActiveTab } from 'features/ui/store/uiSelectors';
-import React, { memo, useCallback, useMemo, useRef } from 'react';
+import React, { memo, useCallback, useRef } from 'react';
import type { HotkeyCallback } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { useClickAway } from 'react-use';
@@ -120,9 +112,6 @@ export const ParamPositivePrompt = memo(() => {
const viewMode = useAppSelector(selectStylePresetViewMode);
const activeStylePresetId = useAppSelector(selectStylePresetActivePresetId);
const modelSupportsNegativePrompt = useAppSelector(selectModelSupportsNegativePrompt);
- const { isPending: isPromptExpansionPending } = useStore(promptExpansionApi.$state);
- const isPromptExpansionEnabled = useAppSelector(selectAllowPromptExpansion);
- const activeTab = useAppSelector(selectActiveTab);
const promptHistoryApi = usePromptHistory();
@@ -153,7 +142,6 @@ export const ParamPositivePrompt = memo(() => {
prompt,
textareaRef: textareaRef,
onChange: handleChange,
- isDisabled: isPromptExpansionPending,
});
// When the user clicks away from the textarea, reset the prompt history state.
@@ -204,8 +192,6 @@ export const ParamPositivePrompt = memo(() => {
dependencies: [promptHistoryApi.next, isPromptFocused],
});
- const dndTargetData = useMemo(() => promptGenerationFromImageDndTarget.getData(), []);
-
return (
@@ -224,17 +210,15 @@ export const ParamPositivePrompt = memo(() => {
paddingTop={0}
paddingBottom={3}
resize="vertical"
- minH={isPromptExpansionEnabled ? 44 : 32}
- isDisabled={isPromptExpansionPending}
+ minH={32}
/>
- {activeTab !== 'video' && modelSupportsNegativePrompt && }
+ {modelSupportsNegativePrompt && }
- {isPromptExpansionEnabled && }
{viewMode && (
@@ -244,15 +228,6 @@ export const ParamPositivePrompt = memo(() => {
label={`${t('parameters.positivePromptPlaceholder')} (${t('stylePresets.preview')})`}
/>
)}
- {isPromptExpansionEnabled && (
-
- )}
-
diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamSteps.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamSteps.tsx
index f7ef4660b58..31efe5d0a6f 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamSteps.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamSteps.tsx
@@ -2,19 +2,25 @@ import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { selectSteps, setSteps } from 'features/controlLayers/store/paramsSlice';
-import { selectStepsConfig } from 'features/system/store/configSlice';
-import { memo, useCallback, useMemo } from 'react';
+import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
+export const CONSTRAINTS = {
+ initial: 30,
+ sliderMin: 1,
+ sliderMax: 100,
+ numberInputMin: 1,
+ numberInputMax: 500,
+ fineStep: 1,
+ coarseStep: 1,
+};
+
+export const MARKS = [CONSTRAINTS.sliderMin, Math.floor(CONSTRAINTS.sliderMax / 2), CONSTRAINTS.sliderMax];
+
const ParamSteps = () => {
const steps = useAppSelector(selectSteps);
- const config = useAppSelector(selectStepsConfig);
const dispatch = useAppDispatch();
const { t } = useTranslation();
- const marks = useMemo(
- () => [config.sliderMin, Math.floor(config.sliderMax / 2), config.sliderMax],
- [config.sliderMax, config.sliderMin]
- );
const onChange = useCallback(
(v: number) => {
dispatch(setSteps(v));
@@ -29,21 +35,21 @@ const ParamSteps = () => {
diff --git a/invokeai/frontend/web/src/features/parameters/components/Dimensions/Dimensions.tsx b/invokeai/frontend/web/src/features/parameters/components/Dimensions/Dimensions.tsx
index 9bcac785b36..05653c6295e 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Dimensions/Dimensions.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Dimensions/Dimensions.tsx
@@ -1,11 +1,5 @@
import type { FormLabelProps } from '@invoke-ai/ui-library';
import { Flex, FormControlGroup } from '@invoke-ai/ui-library';
-import { useAppSelector } from 'app/store/storeHooks';
-import {
- selectModelSupportsAspectRatio,
- selectModelSupportsPixelDimensions,
-} from 'features/controlLayers/store/paramsSlice';
-import { PixelDimensionsUnsupportedAlert } from 'features/parameters/components/PixelDimensionsUnsupportedAlert';
import { memo } from 'react';
import { DimensionsAspectRatioSelect } from './DimensionsAspectRatioSelect';
@@ -17,13 +11,6 @@ import { DimensionsSwapButton } from './DimensionsSwapButton';
import { DimensionsWidth } from './DimensionsWidth';
export const Dimensions = memo(() => {
- const supportsAspectRatio = useAppSelector(selectModelSupportsAspectRatio);
- const supportsPixelDimensions = useAppSelector(selectModelSupportsPixelDimensions);
-
- if (!supportsAspectRatio) {
- return null;
- }
-
return (
@@ -31,20 +18,11 @@ export const Dimensions = memo(() => {
- {supportsPixelDimensions && (
- <>
-
-
- >
- )}
+
+
- {supportsPixelDimensions && (
- <>
-
-
- >
- )}
- {!supportsPixelDimensions && }
+
+
diff --git a/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsAspectRatioSelect.tsx b/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsAspectRatioSelect.tsx
index bd0c0d03a6b..7f7d9893dc0 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsAspectRatioSelect.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsAspectRatioSelect.tsx
@@ -4,20 +4,9 @@ import { InformationalPopover } from 'common/components/InformationalPopover/Inf
import {
aspectRatioIdChanged,
selectAspectRatioID,
- selectIsChatGPT4o,
selectIsFluxKontext,
- selectIsGemini2_5,
- selectIsImagen3,
- selectIsImagen4,
} from 'features/controlLayers/store/paramsSlice';
-import {
- isAspectRatioID,
- zAspectRatioID,
- zChatGPT4oAspectRatioID,
- zFluxKontextAspectRatioID,
- zGemini2_5AspectRatioID,
- zImagen3AspectRatioID,
-} from 'features/controlLayers/store/types';
+import { isAspectRatioID, zAspectRatioID, zFluxKontextAspectRatioID } from 'features/controlLayers/store/types';
import type { ChangeEventHandler } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -27,29 +16,15 @@ export const DimensionsAspectRatioSelect = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const id = useAppSelector(selectAspectRatioID);
- const isImagen3 = useAppSelector(selectIsImagen3);
- const isChatGPT4o = useAppSelector(selectIsChatGPT4o);
- const isImagen4 = useAppSelector(selectIsImagen4);
const isFluxKontext = useAppSelector(selectIsFluxKontext);
- const isGemini2_5 = useAppSelector(selectIsGemini2_5);
const options = useMemo(() => {
- // Imagen3 and ChatGPT4o have different aspect ratio options, and do not support freeform sizes
- if (isImagen3 || isImagen4) {
- return zImagen3AspectRatioID.options;
- }
- if (isChatGPT4o) {
- return zChatGPT4oAspectRatioID.options;
- }
if (isFluxKontext) {
return zFluxKontextAspectRatioID.options;
}
- if (isGemini2_5) {
- return zGemini2_5AspectRatioID.options;
- }
// All other models
return zAspectRatioID.options;
- }, [isImagen3, isChatGPT4o, isImagen4, isFluxKontext, isGemini2_5]);
+ }, [isFluxKontext]);
const onChange = useCallback>(
(e) => {
diff --git a/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsHeight.tsx
index a2f84f360a0..924187c1ed0 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsHeight.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsHeight.tsx
@@ -1,20 +1,27 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
-import { heightChanged, selectHeight, selectIsApiBaseModel } from 'features/controlLayers/store/paramsSlice';
+import { heightChanged, selectHeight } from 'features/controlLayers/store/paramsSlice';
import { selectGridSize, selectOptimalDimension } from 'features/controlLayers/store/selectors';
-import { selectHeightConfig } from 'features/system/store/configSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
+export const CONSTRAINTS = {
+ initial: 512,
+ sliderMin: 64,
+ sliderMax: 1536,
+ numberInputMin: 64,
+ numberInputMax: 4096,
+ fineStep: 8,
+ coarseStep: 64,
+};
+
export const DimensionsHeight = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const optimalDimension = useAppSelector(selectOptimalDimension);
const height = useAppSelector(selectHeight);
- const config = useAppSelector(selectHeightConfig);
const gridSize = useAppSelector(selectGridSize);
- const isApiModel = useAppSelector(selectIsApiBaseModel);
const onChange = useCallback(
(v: number) => {
@@ -23,13 +30,10 @@ export const DimensionsHeight = memo(() => {
[dispatch]
);
- const marks = useMemo(
- () => [config.sliderMin, optimalDimension, config.sliderMax],
- [config.sliderMin, config.sliderMax, optimalDimension]
- );
+ const marks = useMemo(() => [CONSTRAINTS.sliderMin, optimalDimension, CONSTRAINTS.sliderMax], [optimalDimension]);
return (
-
+
{t('parameters.height')}
@@ -37,9 +41,9 @@ export const DimensionsHeight = memo(() => {
value={height}
defaultValue={optimalDimension}
onChange={onChange}
- min={config.sliderMin}
- max={config.sliderMax}
- step={config.coarseStep}
+ min={CONSTRAINTS.sliderMin}
+ max={CONSTRAINTS.sliderMax}
+ step={CONSTRAINTS.coarseStep}
fineStep={gridSize}
marks={marks}
/>
@@ -47,9 +51,9 @@ export const DimensionsHeight = memo(() => {
value={height}
defaultValue={optimalDimension}
onChange={onChange}
- min={config.numberInputMin}
- max={config.numberInputMax}
- step={config.coarseStep}
+ min={CONSTRAINTS.numberInputMin}
+ max={CONSTRAINTS.numberInputMax}
+ step={CONSTRAINTS.coarseStep}
fineStep={gridSize}
/>
diff --git a/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsLockAspectRatioButton.tsx b/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsLockAspectRatioButton.tsx
index 2de397cc784..6ab17147a74 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsLockAspectRatioButton.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsLockAspectRatioButton.tsx
@@ -1,10 +1,6 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import {
- aspectRatioLockToggled,
- selectAspectRatioIsLocked,
- selectIsApiBaseModel,
-} from 'features/controlLayers/store/paramsSlice';
+import { aspectRatioLockToggled, selectAspectRatioIsLocked } from 'features/controlLayers/store/paramsSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiLockSimpleFill, PiLockSimpleOpenBold } from 'react-icons/pi';
@@ -13,7 +9,6 @@ export const DimensionsLockAspectRatioButton = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const isLocked = useAppSelector(selectAspectRatioIsLocked);
- const isApiModel = useAppSelector(selectIsApiBaseModel);
const onClick = useCallback(() => {
dispatch(aspectRatioLockToggled());
@@ -27,7 +22,6 @@ export const DimensionsLockAspectRatioButton = memo(() => {
variant={isLocked ? 'outline' : 'ghost'}
size="sm"
icon={isLocked ? : }
- isDisabled={isApiModel}
/>
);
});
diff --git a/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsSetOptimalSizeButton.tsx b/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsSetOptimalSizeButton.tsx
index eda44ba925d..c1c43f0cec4 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsSetOptimalSizeButton.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsSetOptimalSizeButton.tsx
@@ -1,11 +1,6 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import {
- selectHeight,
- selectIsApiBaseModel,
- selectWidth,
- sizeOptimized,
-} from 'features/controlLayers/store/paramsSlice';
+import { selectHeight, selectWidth, sizeOptimized } from 'features/controlLayers/store/paramsSlice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { getIsSizeTooLarge, getIsSizeTooSmall } from 'features/parameters/util/optimalDimension';
import { memo, useCallback, useMemo } from 'react';
@@ -15,7 +10,6 @@ import { PiSparkleFill } from 'react-icons/pi';
export const DimensionsSetOptimalSizeButton = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
- const isApiModel = useAppSelector(selectIsApiBaseModel);
const width = useAppSelector(selectWidth);
const height = useAppSelector(selectHeight);
const optimalDimension = useAppSelector(selectOptimalDimension);
@@ -49,7 +43,6 @@ export const DimensionsSetOptimalSizeButton = memo(() => {
size="sm"
icon={}
colorScheme={isSizeTooSmall || isSizeTooLarge ? 'warning' : 'base'}
- isDisabled={isApiModel}
/>
);
});
diff --git a/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsWidth.tsx
index eb2f96af96a..20a754c5c30 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsWidth.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Dimensions/DimensionsWidth.tsx
@@ -1,22 +1,27 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
-import { selectIsApiBaseModel, selectWidth, widthChanged } from 'features/controlLayers/store/paramsSlice';
+import { selectWidth, widthChanged } from 'features/controlLayers/store/paramsSlice';
import { selectGridSize, selectOptimalDimension } from 'features/controlLayers/store/selectors';
-import { selectWidthConfig } from 'features/system/store/configSlice';
-import { selectActiveTab } from 'features/ui/store/uiSelectors';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
+export const CONSTRAINTS = {
+ initial: 512,
+ sliderMin: 64,
+ sliderMax: 1536,
+ numberInputMin: 64,
+ numberInputMax: 4096,
+ fineStep: 8,
+ coarseStep: 64,
+};
+
export const DimensionsWidth = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const width = useAppSelector(selectWidth);
const optimalDimension = useAppSelector(selectOptimalDimension);
- const config = useAppSelector(selectWidthConfig);
- const isApiModel = useAppSelector(selectIsApiBaseModel);
const gridSize = useAppSelector(selectGridSize);
- const activeTab = useAppSelector(selectActiveTab);
const onChange = useCallback(
(v: number) => {
@@ -25,13 +30,10 @@ export const DimensionsWidth = memo(() => {
[dispatch]
);
- const marks = useMemo(
- () => [config.sliderMin, optimalDimension, config.sliderMax],
- [config.sliderMax, config.sliderMin, optimalDimension]
- );
+ const marks = useMemo(() => [CONSTRAINTS.sliderMin, optimalDimension, CONSTRAINTS.sliderMax], [optimalDimension]);
return (
-
+
{t('parameters.width')}
@@ -39,9 +41,9 @@ export const DimensionsWidth = memo(() => {
value={width}
onChange={onChange}
defaultValue={optimalDimension}
- min={config.sliderMin}
- max={config.sliderMax}
- step={config.coarseStep}
+ min={CONSTRAINTS.sliderMin}
+ max={CONSTRAINTS.sliderMax}
+ step={CONSTRAINTS.coarseStep}
fineStep={gridSize}
marks={marks}
/>
@@ -49,9 +51,9 @@ export const DimensionsWidth = memo(() => {
value={width}
onChange={onChange}
defaultValue={optimalDimension}
- min={config.numberInputMin}
- max={config.numberInputMax}
- step={config.coarseStep}
+ min={CONSTRAINTS.numberInputMin}
+ max={CONSTRAINTS.numberInputMax}
+ step={CONSTRAINTS.coarseStep}
fineStep={gridSize}
/>
diff --git a/invokeai/frontend/web/src/features/parameters/components/MainModel/DisabledModelWarning.tsx b/invokeai/frontend/web/src/features/parameters/components/MainModel/DisabledModelWarning.tsx
deleted file mode 100644
index 87871387be2..00000000000
--- a/invokeai/frontend/web/src/features/parameters/components/MainModel/DisabledModelWarning.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Flex, Link, Text } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { $accountSettingsLink } from 'app/store/nanostores/accountSettingsLink';
-import { useAppSelector } from 'app/store/storeHooks';
-import { selectModel } from 'features/controlLayers/store/paramsSlice';
-import { useIsModelDisabled } from 'features/parameters/hooks/useIsModelDisabled';
-import { Trans, useTranslation } from 'react-i18next';
-
-export const DisabledModelWarning = () => {
- const { t } = useTranslation();
- const model = useAppSelector(selectModel);
-
- const accountSettingsLink = useStore($accountSettingsLink);
- const { isChatGPT4oHighModelDisabled } = useIsModelDisabled();
-
- if (!model || !isChatGPT4oHighModelDisabled(model)) {
- return null;
- }
-
- return (
-
-
-
- {t('parameters.invoke.accountSettings')}
-
- ),
- }}
- />
-
-
- );
-};
diff --git a/invokeai/frontend/web/src/features/parameters/components/MainModel/NavigateToModelManagerButton.tsx b/invokeai/frontend/web/src/features/parameters/components/MainModel/NavigateToModelManagerButton.tsx
index 6d97b6bdd71..5a06bf8c514 100644
--- a/invokeai/frontend/web/src/features/parameters/components/MainModel/NavigateToModelManagerButton.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/MainModel/NavigateToModelManagerButton.tsx
@@ -1,34 +1,23 @@
import type { IconButtonProps } from '@invoke-ai/ui-library';
import { IconButton } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { $onClickGoToModelManager } from 'app/store/nanostores/onClickGoToModelManager';
-import { useAppSelector } from 'app/store/storeHooks';
-import { selectIsModelsTabDisabled } from 'features/system/store/configSlice';
import { navigationApi } from 'features/ui/layouts/navigation-api';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiCubeBold } from 'react-icons/pi';
export const NavigateToModelManagerButton = memo((props: Omit) => {
- const isModelsTabDisabled = useAppSelector(selectIsModelsTabDisabled);
- const onClickGoToModelManager = useStore($onClickGoToModelManager);
-
const { t } = useTranslation();
const onClick = useCallback(() => {
navigationApi.switchToTab('models');
}, []);
- if (isModelsTabDisabled && !onClickGoToModelManager) {
- return null;
- }
-
return (
}
tooltip={`${t('modelManager.manageModels')}`}
aria-label={`${t('modelManager.manageModels')}`}
- onClick={onClickGoToModelManager ?? onClick}
+ onClick={onClick}
size="sm"
variant="ghost"
{...props}
diff --git a/invokeai/frontend/web/src/features/parameters/components/ModelPicker.tsx b/invokeai/frontend/web/src/features/parameters/components/ModelPicker.tsx
index 14d19934510..48e30cc4af3 100644
--- a/invokeai/frontend/web/src/features/parameters/components/ModelPicker.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/ModelPicker.tsx
@@ -12,10 +12,8 @@ import {
Spacer,
Text,
} from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
import { EMPTY_ARRAY } from 'app/store/constants';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
-import { $onClickGoToModelManager } from 'app/store/nanostores/onClickGoToModelManager';
import { useAppSelector } from 'app/store/storeHooks';
import type { Group, PickerContextState } from 'common/components/Picker/Picker';
import { buildGroup, getRegex, isGroup, Picker, usePickerContext } from 'common/components/Picker/Picker';
@@ -24,18 +22,11 @@ import { typedMemo } from 'common/util/typedMemo';
import { uniq } from 'es-toolkit/compat';
import { selectLoRAsSlice } from 'features/controlLayers/store/lorasSlice';
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
-import {
- API_BASE_MODELS,
- MODEL_BASE_TO_COLOR,
- MODEL_BASE_TO_LONG_NAME,
- MODEL_BASE_TO_SHORT_NAME,
-} from 'features/modelManagerV2/models';
+import { MODEL_BASE_TO_COLOR, MODEL_BASE_TO_LONG_NAME, MODEL_BASE_TO_SHORT_NAME } from 'features/modelManagerV2/models';
import { setInstallModelsTabByName } from 'features/modelManagerV2/store/installModelsStore';
import ModelImage from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelImage';
import type { BaseModelType } from 'features/nodes/types/common';
import { NavigateToModelManagerButton } from 'features/parameters/components/MainModel/NavigateToModelManagerButton';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
-import { selectIsModelsTabDisabled } from 'features/system/store/configSlice';
import { navigationApi } from 'features/ui/layouts/navigation-api';
import { filesize } from 'filesize';
import { memo, useCallback, useMemo, useRef } from 'react';
@@ -76,22 +67,12 @@ type WithStarred = T & { starred?: boolean };
const getOptionId = (modelConfig: WithStarred) => modelConfig.key;
const ModelManagerLink = memo((props: ButtonProps) => {
- const onClickGoToModelManager = useStore($onClickGoToModelManager);
const onClick = useCallback(() => {
navigationApi.switchToTab('models');
setInstallModelsTabByName('launchpad');
}, []);
- return (
-
- );
+ return ;
});
ModelManagerLink.displayName = 'ModelManagerLink';
@@ -101,47 +82,31 @@ const components = {
const NoOptionsFallback = memo(({ noOptionsText }: { noOptionsText?: string }) => {
const { t } = useTranslation();
- const isModelsTabDisabled = useAppSelector(selectIsModelsTabDisabled);
- const onClickGoToModelManager = useStore($onClickGoToModelManager);
return (
{noOptionsText ?? t('modelManager.modelPickerFallbackNoModelsInstalled')}
- {(!isModelsTabDisabled || onClickGoToModelManager) && (
-
-
-
- )}
+
+
+
);
});
NoOptionsFallback.displayName = 'NoOptionsFallback';
const getGroupIDFromModelConfig = (modelConfig: AnyModelConfig): string => {
- if (API_BASE_MODELS.includes(modelConfig.base)) {
- return 'api';
- }
return modelConfig.base;
};
const getGroupNameFromModelConfig = (modelConfig: AnyModelConfig): string => {
- if (API_BASE_MODELS.includes(modelConfig.base)) {
- return 'External API';
- }
return MODEL_BASE_TO_LONG_NAME[modelConfig.base];
};
const getGroupShortNameFromModelConfig = (modelConfig: AnyModelConfig): string => {
- if (API_BASE_MODELS.includes(modelConfig.base)) {
- return 'api';
- }
return MODEL_BASE_TO_SHORT_NAME[modelConfig.base];
};
const getGroupColorSchemeFromModelConfig = (modelConfig: AnyModelConfig): string => {
- if (API_BASE_MODELS.includes(modelConfig.base)) {
- return 'pink';
- }
return MODEL_BASE_TO_COLOR[modelConfig.base];
};
@@ -199,11 +164,9 @@ export const ModelPicker = typedMemo(
}) => {
const { t } = useTranslation();
const selectedKeys = useAppSelector(selectSelectedModelKeys);
- const isModelRelationshipsEnabled = useFeatureStatus('modelRelationships');
const { relatedModelKeys } = useGetRelatedModelIdsBatchQuery(selectedKeys, {
...relatedModelKeysQueryOptions,
- skip: !isModelRelationshipsEnabled,
});
const options = useMemo[] | Group>[]>(() => {
@@ -447,18 +410,6 @@ const PickerOptionComponent = typedMemo(
{filesize(option.file_size)}
)}
- {option.usage_info && (
-
- {option.usage_info}
-
- )}
{option.description && !isCompactView && (
diff --git a/invokeai/frontend/web/src/features/parameters/components/PixelDimensionsUnsupportedAlert.tsx b/invokeai/frontend/web/src/features/parameters/components/PixelDimensionsUnsupportedAlert.tsx
deleted file mode 100644
index febb0e94c41..00000000000
--- a/invokeai/frontend/web/src/features/parameters/components/PixelDimensionsUnsupportedAlert.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Alert, Text } from '@invoke-ai/ui-library';
-import { memo } from 'react';
-
-export const PixelDimensionsUnsupportedAlert = memo(() => {
- return (
-
-
- Select an aspect ratio to control the size of the resulting image from this model.
-
-
- );
-});
-
-PixelDimensionsUnsupportedAlert.displayName = 'PixelDimensionsUnsupportedAlert';
diff --git a/invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx b/invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx
index 3393c0e14e7..9de73262700 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Prompts/Prompts.tsx
@@ -8,20 +8,18 @@ import {
} from 'features/controlLayers/store/paramsSlice';
import { ParamNegativePrompt } from 'features/parameters/components/Core/ParamNegativePrompt';
import { ParamPositivePrompt } from 'features/parameters/components/Core/ParamPositivePrompt';
-import { selectActiveTab } from 'features/ui/store/uiSelectors';
import { memo } from 'react';
export const Prompts = memo(() => {
const modelSupportsNegativePrompt = useAppSelector(selectModelSupportsNegativePrompt);
const modelSupportsRefImages = useAppSelector(selectModelSupportsRefImages);
const hasNegativePrompt = useAppSelector(selectHasNegativePrompt);
- const activeTab = useAppSelector(selectActiveTab);
return (
- {activeTab !== 'video' && modelSupportsNegativePrompt && hasNegativePrompt && }
- {activeTab !== 'video' && modelSupportsRefImages && }
+ {modelSupportsNegativePrompt && hasNegativePrompt && }
+ {modelSupportsRefImages && }
);
});
diff --git a/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamUpscaleCFGScale.tsx b/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamUpscaleCFGScale.tsx
index 9af368cc018..df48c52724d 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamUpscaleCFGScale.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamUpscaleCFGScale.tsx
@@ -2,19 +2,25 @@ import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { selectUpscaleCfgScale, setUpscaleCfgScale } from 'features/controlLayers/store/paramsSlice';
-import { selectCFGScaleConfig } from 'features/system/store/configSlice';
-import { memo, useCallback, useMemo } from 'react';
+import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
+const CONSTRAINTS = {
+ initial: 7,
+ sliderMin: 1,
+ sliderMax: 20,
+ numberInputMin: 1,
+ numberInputMax: 200,
+ fineStep: 0.1,
+ coarseStep: 0.5,
+};
+
+const MARKS = [CONSTRAINTS.sliderMin, Math.floor(CONSTRAINTS.sliderMax / 2), CONSTRAINTS.sliderMax];
+
const ParamUpscaleCFGScale = () => {
const cfgScale = useAppSelector(selectUpscaleCfgScale);
- const config = useAppSelector(selectCFGScaleConfig);
const dispatch = useAppDispatch();
const { t } = useTranslation();
- const marks = useMemo(
- () => [config.sliderMin, Math.floor(config.sliderMax / 2), config.sliderMax],
- [config.sliderMax, config.sliderMin]
- );
const onChange = useCallback((v: number) => dispatch(setUpscaleCfgScale(v)), [dispatch]);
return (
@@ -24,21 +30,21 @@ const ParamUpscaleCFGScale = () => {
diff --git a/invokeai/frontend/web/src/features/parameters/components/Video/ParamDuration.tsx b/invokeai/frontend/web/src/features/parameters/components/Video/ParamDuration.tsx
deleted file mode 100644
index aa7bb212a7b..00000000000
--- a/invokeai/frontend/web/src/features/parameters/components/Video/ParamDuration.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import type { ComboboxOnChange } from '@invoke-ai/ui-library';
-import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import {
- isRunwayDurationID,
- isVeo3DurationID,
- RUNWAY_DURATIONS,
- VEO3_DURATIONS,
-} from 'features/controlLayers/store/types';
-import { selectVideoDuration, selectVideoModel, videoDurationChanged } from 'features/parameters/store/videoSlice';
-import { useCallback, useMemo } from 'react';
-import { useTranslation } from 'react-i18next';
-
-export const ParamDuration = () => {
- const videoDuration = useAppSelector(selectVideoDuration);
- const { t } = useTranslation();
- const dispatch = useAppDispatch();
- const model = useAppSelector(selectVideoModel);
-
- const options = useMemo(() => {
- if (model?.base === 'veo3') {
- return Object.entries(VEO3_DURATIONS).map(([key, value]) => ({
- label: value,
- value: key,
- }));
- } else if (model?.base === 'runway') {
- return Object.entries(RUNWAY_DURATIONS).map(([key, value]) => ({
- label: value,
- value: key,
- }));
- } else {
- return [];
- }
- }, [model]);
-
- const onChange = useCallback(
- (v) => {
- const duration = v?.value;
- if (!isVeo3DurationID(duration) && !isRunwayDurationID(duration)) {
- return;
- }
-
- dispatch(videoDurationChanged(duration));
- },
- [dispatch]
- );
-
- const value = useMemo(() => options.find((o) => o.value === videoDuration), [videoDuration, options]);
-
- return (
-
- {t('parameters.duration')}
-
-
- );
-};
diff --git a/invokeai/frontend/web/src/features/parameters/components/Video/ParamResolution.tsx b/invokeai/frontend/web/src/features/parameters/components/Video/ParamResolution.tsx
deleted file mode 100644
index d4000fda643..00000000000
--- a/invokeai/frontend/web/src/features/parameters/components/Video/ParamResolution.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import type { ComboboxOnChange } from '@invoke-ai/ui-library';
-import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import {
- isRunwayResolution,
- isVeo3Resolution,
- zRunwayResolution,
- zVeo3Resolution,
-} from 'features/controlLayers/store/types';
-import { selectVideoModel, selectVideoResolution, videoResolutionChanged } from 'features/parameters/store/videoSlice';
-import { useCallback, useMemo } from 'react';
-import { useTranslation } from 'react-i18next';
-
-export const ParamResolution = () => {
- const videoResolution = useAppSelector(selectVideoResolution);
- const { t } = useTranslation();
- const dispatch = useAppDispatch();
- const model = useAppSelector(selectVideoModel);
-
- const options = useMemo(() => {
- if (model?.base === 'veo3') {
- return zVeo3Resolution.options.map((o) => ({ label: o, value: o }));
- } else if (model?.base === 'runway') {
- return zRunwayResolution.options.map((o) => ({ label: o, value: o }));
- } else {
- return [];
- }
- }, [model]);
-
- const onChange = useCallback(
- (v) => {
- const resolution = v?.value;
- if (!isVeo3Resolution(resolution) && !isRunwayResolution(resolution)) {
- return;
- }
-
- dispatch(videoResolutionChanged(resolution));
- },
- [dispatch]
- );
-
- const value = useMemo(() => options.find((o) => o.value === videoResolution), [videoResolution, options]);
-
- return (
-
- {t('parameters.resolution')}
-
-
- );
-};
diff --git a/invokeai/frontend/web/src/features/parameters/components/Video/VideoDimensions.tsx b/invokeai/frontend/web/src/features/parameters/components/Video/VideoDimensions.tsx
deleted file mode 100644
index fec3ea407c3..00000000000
--- a/invokeai/frontend/web/src/features/parameters/components/Video/VideoDimensions.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Flex } from '@invoke-ai/ui-library';
-import { memo } from 'react';
-
-import { ParamResolution } from './ParamResolution';
-import { VideoDimensionsAspectRatioSelect } from './VideoDimensionsAspectRatioSelect';
-import { VideoDimensionsPreview } from './VideoDimensionsPreview';
-
-export const VideoDimensions = memo(() => {
- return (
-
-
-
-
-
-
-
-
-
- );
-});
-
-VideoDimensions.displayName = 'VideoDimensions';
diff --git a/invokeai/frontend/web/src/features/parameters/components/Video/VideoDimensionsAspectRatioSelect.tsx b/invokeai/frontend/web/src/features/parameters/components/Video/VideoDimensionsAspectRatioSelect.tsx
deleted file mode 100644
index 072b6c8a481..00000000000
--- a/invokeai/frontend/web/src/features/parameters/components/Video/VideoDimensionsAspectRatioSelect.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { ComboboxOnChange } from '@invoke-ai/ui-library';
-import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
-import { isVideoAspectRatio, zRunwayAspectRatioID, zVeo3AspectRatioID } from 'features/controlLayers/store/types';
-import {
- selectIsRunway,
- selectIsVeo3,
- selectVideoAspectRatio,
- videoAspectRatioChanged,
-} from 'features/parameters/store/videoSlice';
-import { memo, useCallback, useMemo } from 'react';
-import { useTranslation } from 'react-i18next';
-
-export const VideoDimensionsAspectRatioSelect = memo(() => {
- const { t } = useTranslation();
- const dispatch = useAppDispatch();
- const id = useAppSelector(selectVideoAspectRatio);
- const isVeo3 = useAppSelector(selectIsVeo3);
- const isRunway = useAppSelector(selectIsRunway);
- const options = useMemo(() => {
- if (isVeo3) {
- return zVeo3AspectRatioID.options.map((o) => ({ label: o, value: o }));
- }
- if (isRunway) {
- return zRunwayAspectRatioID.options.map((o) => ({ label: o, value: o }));
- }
- // All other models
- return [];
- }, [isVeo3, isRunway]);
-
- const onChange = useCallback(
- (v) => {
- if (!isVideoAspectRatio(v?.value)) {
- return;
- }
- dispatch(videoAspectRatioChanged(v.value));
- },
- [dispatch]
- );
-
- const value = useMemo(() => options.find((o) => o.value === id), [id, options]);
-
- return (
-
-
- {t('parameters.aspect')}
-
-
-
- );
-});
-
-VideoDimensionsAspectRatioSelect.displayName = 'VideoDimensionsAspectRatioSelect';
diff --git a/invokeai/frontend/web/src/features/parameters/components/Video/VideoDimensionsPreview.tsx b/invokeai/frontend/web/src/features/parameters/components/Video/VideoDimensionsPreview.tsx
deleted file mode 100644
index a64b7e21102..00000000000
--- a/invokeai/frontend/web/src/features/parameters/components/Video/VideoDimensionsPreview.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import { Flex, Grid, GridItem, Text } from '@invoke-ai/ui-library';
-import { useAppSelector } from 'app/store/storeHooks';
-import { ASPECT_RATIO_MAP } from 'features/controlLayers/store/types';
-import { useCurrentVideoDimensions } from 'features/parameters/hooks/useCurrentVideoDimensions';
-import { selectVideoAspectRatio } from 'features/parameters/store/videoSlice';
-import { memo, useMemo } from 'react';
-import { useMeasure } from 'react-use';
-
-export const VideoDimensionsPreview = memo(() => {
- const aspectRatio = useAppSelector(selectVideoAspectRatio);
- const [ref, dims] = useMeasure();
-
- const currentVideoDimensions = useCurrentVideoDimensions();
-
- const previewBoxSize = useMemo(() => {
- if (!dims) {
- return { width: 0, height: 0 };
- }
-
- const aspectRatioValue = ASPECT_RATIO_MAP[aspectRatio]?.ratio ?? 1;
-
- let width = currentVideoDimensions.width;
- let height = currentVideoDimensions.height;
-
- if (currentVideoDimensions.width > currentVideoDimensions.height) {
- width = dims.width;
- height = width / aspectRatioValue;
- } else {
- height = dims.height;
- width = height * aspectRatioValue;
- }
-
- return { width, height };
- }, [dims, currentVideoDimensions, aspectRatio]);
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {currentVideoDimensions.width}x{currentVideoDimensions.height}
-
-
-
-
- );
-});
-
-VideoDimensionsPreview.displayName = 'VideoDimensionsPreview';
diff --git a/invokeai/frontend/web/src/features/parameters/hooks/useCurrentVideoDimensions.ts b/invokeai/frontend/web/src/features/parameters/hooks/useCurrentVideoDimensions.ts
deleted file mode 100644
index 25f6299c619..00000000000
--- a/invokeai/frontend/web/src/features/parameters/hooks/useCurrentVideoDimensions.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { useAppSelector } from 'app/store/storeHooks';
-import type { AspectRatioID } from 'features/controlLayers/store/types';
-import { ASPECT_RATIO_MAP, RESOLUTION_MAP } from 'features/controlLayers/store/types';
-import { selectVideoAspectRatio, selectVideoResolution } from 'features/parameters/store/videoSlice';
-import { useMemo } from 'react';
-
-export const useCurrentVideoDimensions = () => {
- const videoAspectRatio = useAppSelector(selectVideoAspectRatio);
- const videoResolution = useAppSelector(selectVideoResolution);
-
- const currentVideoDimensions = useMemo(() => {
- // Default fallback dimensions
- const fallback = { width: 1280, height: 720 };
-
- if (!videoAspectRatio || !videoResolution) {
- return fallback;
- }
-
- // Get base resolution dimensions from the resolution tables
- let baseWidth: number;
- let baseHeight: number;
-
- const resolutionDims = RESOLUTION_MAP[videoResolution];
- baseWidth = resolutionDims.width;
- baseHeight = resolutionDims.height;
-
- // Get the aspect ratio value from the map
- const aspectRatioData = ASPECT_RATIO_MAP[videoAspectRatio as Exclude];
- if (!aspectRatioData) {
- return { width: baseWidth, height: baseHeight };
- }
-
- const targetRatio = aspectRatioData.ratio;
-
- // Calculate dimensions that maintain the aspect ratio while respecting the resolution
- // We use the resolution as a constraint on the total pixel count
- const totalPixels = baseWidth * baseHeight;
-
- // Calculate dimensions that match the aspect ratio and approximate the target pixel count
- // width * height = totalPixels
- // width / height = targetRatio
- // Therefore: width = sqrt(totalPixels * targetRatio) and height = sqrt(totalPixels / targetRatio)
- const calculatedWidth = Math.round(Math.sqrt(totalPixels * targetRatio));
- const calculatedHeight = Math.round(Math.sqrt(totalPixels / targetRatio));
-
- // Ensure dimensions are even numbers (common requirement for video encoding)
- const width = calculatedWidth % 2 === 0 ? calculatedWidth : calculatedWidth + 1;
- const height = calculatedHeight % 2 === 0 ? calculatedHeight : calculatedHeight + 1;
-
- return { width, height };
- }, [videoAspectRatio, videoResolution]);
-
- return currentVideoDimensions;
-};
diff --git a/invokeai/frontend/web/src/features/parameters/hooks/useIsModelDisabled.ts b/invokeai/frontend/web/src/features/parameters/hooks/useIsModelDisabled.ts
deleted file mode 100644
index dfd4e823a51..00000000000
--- a/invokeai/frontend/web/src/features/parameters/hooks/useIsModelDisabled.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { ParameterModel } from 'features/parameters/types/parameterSchemas';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
-import { useCallback } from 'react';
-
-export const useIsModelDisabled = () => {
- const isChatGPT4oHighEnabled = useFeatureStatus('chatGPT4oHigh');
-
- const isChatGPT4oHighModelDisabled = useCallback(
- (model: ParameterModel) => {
- return model?.base === 'chatgpt-4o' && model.name.toLowerCase().includes('high') && !isChatGPT4oHighEnabled;
- },
- [isChatGPT4oHighEnabled]
- );
-
- return { isChatGPT4oHighModelDisabled };
-};
diff --git a/invokeai/frontend/web/src/features/parameters/hooks/useIsTooLargeToUpscale.ts b/invokeai/frontend/web/src/features/parameters/hooks/useIsTooLargeToUpscale.ts
deleted file mode 100644
index 7428d50c9b3..00000000000
--- a/invokeai/frontend/web/src/features/parameters/hooks/useIsTooLargeToUpscale.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { createSelector } from '@reduxjs/toolkit';
-import { useAppSelector } from 'app/store/storeHooks';
-import type { ImageWithDims } from 'features/controlLayers/store/types';
-import { selectUpscaleSlice } from 'features/parameters/store/upscaleSlice';
-import { selectConfigSlice } from 'features/system/store/configSlice';
-import { useMemo } from 'react';
-
-const createIsTooLargeToUpscaleSelector = (imageWithDims?: ImageWithDims | null) =>
- createSelector(selectUpscaleSlice, selectConfigSlice, (upscale, config) => {
- const { upscaleModel, scale } = upscale;
- const { maxUpscaleDimension } = config;
-
- if (!maxUpscaleDimension || !upscaleModel || !imageWithDims) {
- // When these are missing, another warning will be shown
- return false;
- }
-
- const { width, height } = imageWithDims;
-
- const maxPixels = maxUpscaleDimension ** 2;
- const upscaledPixels = width * scale * height * scale;
-
- return upscaledPixels > maxPixels;
- });
-
-export const useIsTooLargeToUpscale = (imageWithDims?: ImageWithDims | null) => {
- const selectIsTooLargeToUpscale = useMemo(() => createIsTooLargeToUpscaleSelector(imageWithDims), [imageWithDims]);
- return useAppSelector(selectIsTooLargeToUpscale);
-};
diff --git a/invokeai/frontend/web/src/features/parameters/store/videoSlice.ts b/invokeai/frontend/web/src/features/parameters/store/videoSlice.ts
deleted file mode 100644
index f210cb91aaa..00000000000
--- a/invokeai/frontend/web/src/features/parameters/store/videoSlice.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-import type { PayloadAction, Selector } from '@reduxjs/toolkit';
-import { createSelector, createSlice } from '@reduxjs/toolkit';
-import type { RootState } from 'app/store/store';
-import type { SliceConfig } from 'app/store/types';
-import { isPlainObject } from 'es-toolkit';
-import type {
- CroppableImageWithDims,
- VideoAspectRatio,
- VideoDuration,
- VideoResolution,
-} from 'features/controlLayers/store/types';
-import {
- isRunwayAspectRatioID,
- isRunwayDurationID,
- isRunwayResolution,
- isVeo3AspectRatioID,
- isVeo3DurationID,
- isVeo3Resolution,
- zCroppableImageWithDims,
- zVideoAspectRatio,
- zVideoDuration,
- zVideoResolution,
-} from 'features/controlLayers/store/types';
-import { REQUIRES_STARTING_FRAME_BASE_MODELS } from 'features/modelManagerV2/models';
-import type { ModelIdentifierField } from 'features/nodes/types/common';
-import { zModelIdentifierField } from 'features/nodes/types/common';
-import { modelConfigsAdapterSelectors, selectModelConfigsQuery } from 'services/api/endpoints/models';
-import { isVideoModelConfig } from 'services/api/types';
-import { assert } from 'tsafe';
-import z from 'zod';
-
-const zVideoState = z.object({
- _version: z.literal(2),
- startingFrameImage: zCroppableImageWithDims.nullable(),
- videoModel: zModelIdentifierField.nullable(),
- videoResolution: zVideoResolution,
- videoDuration: zVideoDuration,
- videoAspectRatio: zVideoAspectRatio,
-});
-
-export type VideoState = z.infer;
-
-const getInitialState = (): VideoState => {
- return {
- _version: 2,
- startingFrameImage: null,
- videoModel: null,
- videoResolution: '1080p',
- videoDuration: '8',
- videoAspectRatio: '16:9',
- };
-};
-
-const slice = createSlice({
- name: 'video',
- initialState: getInitialState(),
- reducers: {
- startingFrameImageChanged: (state, action: PayloadAction) => {
- state.startingFrameImage = action.payload;
- },
-
- videoModelChanged: (state, action: PayloadAction<{ videoModel: ModelIdentifierField | null }>) => {
- const { videoModel } = action.payload;
-
- state.videoModel = videoModel;
-
- if (videoModel?.base === 'veo3') {
- if (!state.videoResolution || !isVeo3Resolution(state.videoResolution)) {
- state.videoResolution = '1080p';
- }
- if (!state.videoDuration || !isVeo3DurationID(state.videoDuration)) {
- state.videoDuration = '8';
- }
- if (!state.videoAspectRatio || !isVeo3AspectRatioID(state.videoAspectRatio)) {
- state.videoAspectRatio = '16:9';
- }
- } else if (videoModel?.base === 'runway') {
- if (!state.videoResolution || !isRunwayResolution(state.videoResolution)) {
- state.videoResolution = '720p';
- }
- if (!state.videoDuration || !isRunwayDurationID(state.videoDuration)) {
- state.videoDuration = '5';
- }
- if (!state.videoAspectRatio || !isRunwayAspectRatioID(state.videoAspectRatio)) {
- state.videoAspectRatio = '16:9';
- }
- }
- },
-
- videoResolutionChanged: (state, action: PayloadAction) => {
- state.videoResolution = action.payload;
- },
-
- videoDurationChanged: (state, action: PayloadAction) => {
- state.videoDuration = action.payload;
- },
-
- videoAspectRatioChanged: (state, action: PayloadAction) => {
- state.videoAspectRatio = action.payload;
- },
- },
-});
-
-export const {
- startingFrameImageChanged,
- videoModelChanged,
- videoResolutionChanged,
- videoDurationChanged,
- videoAspectRatioChanged,
-} = slice.actions;
-
-export const videoSliceConfig: SliceConfig = {
- slice,
- schema: zVideoState,
- getInitialState,
- persistConfig: {
- migrate: (state) => {
- assert(isPlainObject(state));
- if (!('_version' in state)) {
- state._version = 1;
- }
- if (state._version === 1) {
- state._version = 2;
- if (state.startingFrameImage) {
- // startingFrameImage changed from ImageWithDims to CroppableImageWithDims
- state.startingFrameImage = zCroppableImageWithDims.parse({ original: state.startingFrameImage });
- }
- }
- return zVideoState.parse(state);
- },
- },
-};
-
-export const selectVideoSlice = (state: RootState) => state.video;
-const createVideoSelector = (selector: Selector) => createSelector(selectVideoSlice, selector);
-
-export const selectStartingFrameImage = createVideoSelector((video) => video.startingFrameImage);
-export const selectVideoModel = createVideoSelector((video) => video.videoModel);
-export const selectVideoModelKey = createVideoSelector((video) => video.videoModel?.key);
-export const selectVideoResolution = createVideoSelector((video) => video.videoResolution);
-export const selectVideoDuration = createVideoSelector((video) => video.videoDuration);
-export const selectVideoAspectRatio = createVideoSelector((video) => video.videoAspectRatio);
-export const selectIsVeo3 = createVideoSelector((video) => video.videoModel?.base === 'veo3');
-export const selectIsRunway = createVideoSelector((video) => video.videoModel?.base === 'runway');
-export const selectVideoModelConfig = createSelector(
- selectModelConfigsQuery,
- selectVideoSlice,
- (modelConfigs, { videoModel }) => {
- if (!modelConfigs.data) {
- return null;
- }
- if (!videoModel) {
- return null;
- }
- const modelConfig = modelConfigsAdapterSelectors.selectById(modelConfigs.data, videoModel.key);
- if (!modelConfig) {
- return null;
- }
- if (!isVideoModelConfig(modelConfig)) {
- return null;
- }
- return modelConfig;
- }
-);
-export const selectVideoModelRequiresStartingFrame = createSelector(
- selectVideoModel,
- (model) => !!model && REQUIRES_STARTING_FRAME_BASE_MODELS.includes(model.base)
-);
diff --git a/invokeai/frontend/web/src/features/parameters/types/constants.ts b/invokeai/frontend/web/src/features/parameters/types/constants.ts
index 3f879db257c..b14e566d3d1 100644
--- a/invokeai/frontend/web/src/features/parameters/types/constants.ts
+++ b/invokeai/frontend/web/src/features/parameters/types/constants.ts
@@ -37,26 +37,6 @@ export const CLIP_SKIP_MAP: { [key in BaseModelType]?: { maxClip: number; marker
maxClip: 0,
markers: [],
},
- imagen3: {
- maxClip: 0,
- markers: [],
- },
- imagen4: {
- maxClip: 0,
- markers: [],
- },
- 'chatgpt-4o': {
- maxClip: 0,
- markers: [],
- },
- 'flux-kontext': {
- maxClip: 0,
- markers: [],
- },
- 'gemini-2.5': {
- maxClip: 0,
- markers: [],
- },
};
/**
diff --git a/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts b/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts
index d1bccc691ab..57d4e8acb50 100644
--- a/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts
+++ b/invokeai/frontend/web/src/features/parameters/types/parameterSchemas.ts
@@ -133,6 +133,15 @@ export type ParameterSpandrelImageToImageModel = z.infer;
+export const PARAMETER_STRENGTH_CONSTRAINTS = {
+ initial: 0.7,
+ sliderMin: 0,
+ sliderMax: 1,
+ numberInputMin: 0,
+ numberInputMax: 1,
+ fineStep: 0.01,
+ coarseStep: 0.05,
+};
// #endregion
// #region SeamlessX
diff --git a/invokeai/frontend/web/src/features/parameters/util/optimalDimension.ts b/invokeai/frontend/web/src/features/parameters/util/optimalDimension.ts
index 4318c65144b..7fe7311db93 100644
--- a/invokeai/frontend/web/src/features/parameters/util/optimalDimension.ts
+++ b/invokeai/frontend/web/src/features/parameters/util/optimalDimension.ts
@@ -17,11 +17,6 @@ export const getOptimalDimension = (base?: BaseModelType | null): number => {
case 'flux':
case 'sd-3':
case 'cogview4':
- case 'imagen3':
- case 'imagen4':
- case 'chatgpt-4o':
- case 'flux-kontext':
- case 'gemini-2.5':
default:
return 1024;
}
@@ -80,9 +75,6 @@ export const getGridSize = (base?: BaseModelType | null): number => {
case 'sd-1':
case 'sd-2':
case 'sdxl':
- case 'imagen3':
- case 'chatgpt-4o':
- case 'flux-kontext':
default:
return 8;
}
diff --git a/invokeai/frontend/web/src/features/prompt/PromptExpansion/PromptExpansionMenu.tsx b/invokeai/frontend/web/src/features/prompt/PromptExpansion/PromptExpansionMenu.tsx
deleted file mode 100644
index d337926fdb0..00000000000
--- a/invokeai/frontend/web/src/features/prompt/PromptExpansion/PromptExpansionMenu.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { IconButton, Menu, MenuButton, MenuItem, MenuList, Text } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { useAppStore } from 'app/store/storeHooks';
-import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
-import { WrappedError } from 'common/util/result';
-import { toast } from 'features/toast/toast';
-import { useCallback } from 'react';
-import { useTranslation } from 'react-i18next';
-import { PiMagicWandBold } from 'react-icons/pi';
-import type { ImageDTO } from 'services/api/types';
-
-import { expandPrompt } from './expand';
-import { promptExpansionApi } from './state';
-
-export const PromptExpansionMenu = () => {
- const { dispatch, getState } = useAppStore();
- const { t } = useTranslation();
- const { isPending } = useStore(promptExpansionApi.$state);
-
- const onUploadStarted = useCallback(() => {
- promptExpansionApi.setPending();
- }, []);
-
- const onUpload = useCallback(
- (imageDTO: ImageDTO) => {
- promptExpansionApi.setPending(imageDTO);
- expandPrompt({ dispatch, getState, imageDTO });
- },
- [dispatch, getState]
- );
-
- const onUploadError = useCallback(
- (error: unknown) => {
- const wrappedError = WrappedError.wrap(error);
- promptExpansionApi.setError(wrappedError);
- toast({
- id: 'UPLOAD_AND_PROMPT_GENERATION_FAILED',
- title: t('toast.uploadAndPromptGenerationFailed'),
- status: 'error',
- });
- },
- [t]
- );
-
- const uploadApi = useImageUploadButton({
- allowMultiple: false,
- onUpload,
- onUploadStarted,
- onError: onUploadError,
- });
-
- const onClickExpandPrompt = useCallback(() => {
- promptExpansionApi.setPending();
- expandPrompt({ dispatch, getState });
- }, [dispatch, getState]);
-
- return (
- <>
-
-
- >
- );
-};
diff --git a/invokeai/frontend/web/src/features/prompt/PromptExpansion/PromptExpansionOverlay.tsx b/invokeai/frontend/web/src/features/prompt/PromptExpansion/PromptExpansionOverlay.tsx
deleted file mode 100644
index ccc2ff7853a..00000000000
--- a/invokeai/frontend/web/src/features/prompt/PromptExpansion/PromptExpansionOverlay.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Box, Flex, Image, Spinner, Text } from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
-import { PromptExpansionResultOverlay } from 'features/prompt/PromptExpansion/PromptExpansionResultOverlay';
-import { memo } from 'react';
-import { useTranslation } from 'react-i18next';
-import { PiMagicWandBold } from 'react-icons/pi';
-
-import { promptExpansionApi } from './state';
-
-export const PromptExpansionOverlay = memo(() => {
- const { isSuccess, isPending, result, imageDTO } = useStore(promptExpansionApi.$state);
- const { t } = useTranslation();
-
- // Show result overlay when completed
- if (isSuccess) {
- return ;
- }
-
- // Show pending overlay when pending
- if (!isPending) {
- return null;
- }
-
- return (
-
- {/* Show dimmed source image if available */}
- {imageDTO && (
-
-
-
- )}
-
-
-
-
-
-
-
- {t('prompt.expandingPrompt')}
-
-
-
- );
-});
-
-PromptExpansionOverlay.displayName = 'PromptExpansionOverlay';
diff --git a/invokeai/frontend/web/src/features/prompt/PromptExpansion/PromptExpansionResultOverlay.tsx b/invokeai/frontend/web/src/features/prompt/PromptExpansion/PromptExpansionResultOverlay.tsx
deleted file mode 100644
index 015bf5946d1..00000000000
--- a/invokeai/frontend/web/src/features/prompt/PromptExpansion/PromptExpansionResultOverlay.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { ButtonGroup, Flex, Icon, IconButton, Text, Tooltip } from '@invoke-ai/ui-library';
-import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { positivePromptChanged, selectPositivePrompt } from 'features/controlLayers/store/paramsSlice';
-import { useCallback } from 'react';
-import { PiCheckBold, PiMagicWandBold, PiPlusBold, PiXBold } from 'react-icons/pi';
-
-import { promptExpansionApi } from './state';
-
-interface PromptExpansionResultOverlayProps {
- expandedText: string;
-}
-
-export const PromptExpansionResultOverlay = ({ expandedText }: PromptExpansionResultOverlayProps) => {
- const dispatch = useAppDispatch();
- const positivePrompt = useAppSelector(selectPositivePrompt);
-
- const handleReplace = useCallback(() => {
- dispatch(positivePromptChanged(expandedText));
- promptExpansionApi.reset();
- }, [dispatch, expandedText]);
-
- const handleInsert = useCallback(() => {
- const currentText = positivePrompt;
- const newText = currentText ? `${currentText}\n${expandedText}` : expandedText;
- dispatch(positivePromptChanged(newText));
- promptExpansionApi.reset();
- }, [dispatch, expandedText, positivePrompt]);
-
- const handleDiscard = useCallback(() => {
- promptExpansionApi.reset();
- }, []);
-
- return (
-
-
-
-
- {expandedText}
-
-
-
-
-
-
- }
- colorScheme="invokeGreen"
- size="xs"
- aria-label="Replace"
- />
-
-
-
- }
- colorScheme="invokeBlue"
- size="xs"
- aria-label="Insert"
- />
-
-
-
- }
- colorScheme="invokeRed"
- size="xs"
- aria-label="Discard"
- />
-
-
-
- );
-};
diff --git a/invokeai/frontend/web/src/features/prompt/PromptExpansion/expand.ts b/invokeai/frontend/web/src/features/prompt/PromptExpansion/expand.ts
deleted file mode 100644
index dbd8187b461..00000000000
--- a/invokeai/frontend/web/src/features/prompt/PromptExpansion/expand.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import type { AppDispatch, AppGetState } from 'app/store/store';
-import { toast } from 'features/toast/toast';
-import { t } from 'i18next';
-import { buildRunGraphDependencies, runGraph } from 'services/api/run-graph';
-import type { ImageDTO } from 'services/api/types';
-import { $socket } from 'services/events/stores';
-import { assert } from 'tsafe';
-
-import { buildPromptExpansionGraph } from './graph';
-import { promptExpansionApi } from './state';
-
-export const expandPrompt = async (arg: { dispatch: AppDispatch; getState: AppGetState; imageDTO?: ImageDTO }) => {
- const { dispatch, getState, imageDTO } = arg;
- const socket = $socket.get();
- if (!socket) {
- return;
- }
- const { graph, outputNodeId } = buildPromptExpansionGraph({
- state: getState(),
- imageDTO,
- });
- const dependencies = buildRunGraphDependencies(dispatch, socket);
- try {
- const { output } = await runGraph({
- graph,
- outputNodeId,
- dependencies,
- options: {
- prepend: true,
- },
- });
- assert(output.type === 'string_output');
- promptExpansionApi.setSuccess(output.value);
- } catch {
- promptExpansionApi.reset();
- toast({
- id: 'PROMPT_EXPANSION_FAILED',
- title: t('toast.promptExpansionFailed'),
- status: 'error',
- });
- }
-};
diff --git a/invokeai/frontend/web/src/features/prompt/PromptExpansion/graph.ts b/invokeai/frontend/web/src/features/prompt/PromptExpansion/graph.ts
deleted file mode 100644
index dcd4b8ab6d3..00000000000
--- a/invokeai/frontend/web/src/features/prompt/PromptExpansion/graph.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { RootState } from 'app/store/store';
-import { getPrefixedId } from 'features/controlLayers/konva/util';
-import { selectBase, selectPositivePrompt } from 'features/controlLayers/store/paramsSlice';
-import { imageDTOToImageField } from 'features/controlLayers/store/util';
-import { Graph } from 'features/nodes/util/graph/generation/Graph';
-import type { ImageDTO } from 'services/api/types';
-import { assert } from 'tsafe';
-
-export const buildPromptExpansionGraph = ({
- state,
- imageDTO,
-}: {
- state: RootState;
- imageDTO?: ImageDTO;
-}): { graph: Graph; outputNodeId: string } => {
- const base = selectBase(state);
- assert(base, 'No main model found in state');
-
- const architecture = ['sdxl', 'sdxl-refiner'].includes(base) ? 'tag_based' : 'sentence_based';
-
- if (imageDTO) {
- const graph = new Graph(getPrefixedId('claude-analyze-image-graph'));
- const outputNode = graph.addNode({
- // @ts-expect-error: These nodes are not available in the OSS application
- type: 'claude_analyze_image',
- id: getPrefixedId('claude_analyze_image'),
- model_architecture: architecture,
- image: imageDTOToImageField(imageDTO),
- });
- return { graph, outputNodeId: outputNode.id };
- } else {
- const positivePrompt = selectPositivePrompt(state);
- const graph = new Graph(getPrefixedId('claude-expand-prompt-graph'));
- const outputNode = graph.addNode({
- // @ts-expect-error: These nodes are not available in the OSS application
- type: 'claude_expand_prompt',
- id: getPrefixedId('claude_expand_prompt'),
- model_architecture: architecture,
- prompt: positivePrompt,
- });
- return { graph, outputNodeId: outputNode.id };
- }
-};
diff --git a/invokeai/frontend/web/src/features/prompt/PromptExpansion/state.ts b/invokeai/frontend/web/src/features/prompt/PromptExpansion/state.ts
deleted file mode 100644
index 14cafe664a6..00000000000
--- a/invokeai/frontend/web/src/features/prompt/PromptExpansion/state.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { deepClone } from 'common/util/deepClone';
-import { atom } from 'nanostores';
-import type { ImageDTO } from 'services/api/types';
-
-type SuccessState = {
- isSuccess: true;
- isError: false;
- isPending: false;
- result: string;
- error: null;
- imageDTO?: ImageDTO;
-};
-
-type ErrorState = {
- isSuccess: false;
- isError: true;
- isPending: false;
- result: null;
- error: Error;
- imageDTO?: ImageDTO;
-};
-
-type PendingState = {
- isSuccess: false;
- isError: false;
- isPending: true;
- result: null;
- error: null;
- imageDTO?: ImageDTO;
-};
-
-type IdleState = {
- isSuccess: false;
- isError: false;
- isPending: false;
- result: null;
- error: null;
- imageDTO?: ImageDTO;
-};
-
-export type PromptExpansionRequestState = IdleState | PendingState | SuccessState | ErrorState;
-
-const IDLE_STATE: IdleState = {
- isSuccess: false,
- isError: false,
- isPending: false,
- result: null,
- error: null,
- imageDTO: undefined,
-};
-
-const $state = atom(deepClone(IDLE_STATE));
-
-const reset = () => {
- $state.set(deepClone(IDLE_STATE));
-};
-
-const setPending = (imageDTO?: ImageDTO) => {
- $state.set({
- ...$state.get(),
- isSuccess: false,
- isError: false,
- isPending: true,
- result: null,
- error: null,
- imageDTO,
- });
-};
-
-const setSuccess = (result: string) => {
- $state.set({
- ...$state.get(),
- isSuccess: true,
- isError: false,
- isPending: false,
- result,
- error: null,
- });
-};
-
-const setError = (error: Error) => {
- $state.set({
- ...$state.get(),
- isSuccess: false,
- isError: true,
- isPending: false,
- result: null,
- error,
- });
-};
-
-export const promptExpansionApi = {
- $state,
- reset,
- setPending,
- setSuccess,
- setError,
-};
diff --git a/invokeai/frontend/web/src/features/queue/components/ClearModelCacheButton.tsx b/invokeai/frontend/web/src/features/queue/components/ClearModelCacheButton.tsx
index 26458fae6c1..e6169b210bb 100644
--- a/invokeai/frontend/web/src/features/queue/components/ClearModelCacheButton.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/ClearModelCacheButton.tsx
@@ -1,12 +1,10 @@
import { Button } from '@invoke-ai/ui-library';
-import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { toast } from 'features/toast/toast';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useEmptyModelCacheMutation } from 'services/api/endpoints/models';
const ClearModelCacheButton = () => {
- const isModelCacheEnabled = useFeatureStatus('modelCache');
const [emptyModelCache, { isLoading }] = useEmptyModelCacheMutation();
const { t } = useTranslation();
@@ -25,10 +23,6 @@ const ClearModelCacheButton = () => {
}
}, [emptyModelCache, t]);
- if (!isModelCacheEnabled) {
- return <>>;
- }
-
return (
- }>
- {t('workflows.builder.unpublish')}
-
-
- );
-});
-PublishedWorkflowPanelContent.displayName = 'PublishedWorkflowPanelContent';
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/NodeFieldElementEditMode.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/NodeFieldElementEditMode.tsx
index aacfba41c42..9fa81f5a1ec 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/NodeFieldElementEditMode.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/NodeFieldElementEditMode.tsx
@@ -108,7 +108,7 @@ const nodeFieldOverlaySx: SystemStyleObject = {
},
};
-export const NodeFieldElementOverlay = memo(({ nodeId }: { nodeId: string }) => {
+const NodeFieldElementOverlay = memo(({ nodeId }: { nodeId: string }) => {
const mouseOverNode = useMouseOverNode(nodeId);
const mouseOverFormField = useMouseOverFormField(nodeId);
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryListItemActions/LockedWorkflowIcon.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryListItemActions/LockedWorkflowIcon.tsx
deleted file mode 100644
index 4b08b72bf77..00000000000
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryListItemActions/LockedWorkflowIcon.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { IconButton, Tooltip } from '@invoke-ai/ui-library';
-import { memo } from 'react';
-import { useTranslation } from 'react-i18next';
-import { PiLockBold } from 'react-icons/pi';
-
-export const LockedWorkflowIcon = memo(() => {
- const { t } = useTranslation();
-
- return (
-
- }
- />
-
- );
-});
-
-LockedWorkflowIcon.displayName = 'LockedWorkflowIcon';
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryModal.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryModal.tsx
index 18dc640e90f..a86f3291e0b 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryModal.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibraryModal.tsx
@@ -8,16 +8,11 @@ import {
ModalHeader,
ModalOverlay,
} from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { useWorkflowLibraryModal } from 'features/nodes/store/workflowLibraryModal';
-import {
- $workflowLibraryCategoriesOptions,
- selectWorkflowLibraryView,
- workflowLibraryViewChanged,
-} from 'features/nodes/store/workflowLibrarySlice';
-import { memo, useEffect, useMemo, useState } from 'react';
+import { selectWorkflowLibraryView, workflowLibraryViewChanged } from 'features/nodes/store/workflowLibrarySlice';
+import { memo, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetCountsByCategoryQuery } from 'services/api/endpoints/workflows';
@@ -59,6 +54,26 @@ export const WorkflowLibraryModal = memo(() => {
});
WorkflowLibraryModal.displayName = 'WorkflowLibraryModal';
+const recentWorkflowsCountQueryArg = {
+ categories: ['user', 'default'],
+ has_been_opened: true,
+} satisfies Parameters[0];
+
+const yourWorkflowsCountQueryArg = {
+ categories: ['user'],
+} satisfies Parameters[0];
+
+const queryOptions = {
+ selectFromResult: ({ data, isLoading }) => {
+ if (!data) {
+ return { count: 0, isLoading: true };
+ }
+ return {
+ count: Object.values(data).reduce((acc, count) => acc + count, 0),
+ isLoading,
+ };
+ },
+} satisfies Parameters[1];
/**
* On first app load, if the user's selected view has no workflows, switches to the next available view.
@@ -66,38 +81,7 @@ WorkflowLibraryModal.displayName = 'WorkflowLibraryModal';
const useSyncInitialWorkflowLibraryCategories = () => {
const dispatch = useAppDispatch();
const view = useAppSelector(selectWorkflowLibraryView);
- const categoryOptions = useStore($workflowLibraryCategoriesOptions);
const [didSync, setDidSync] = useState(false);
- const recentWorkflowsCountQueryArg = useMemo(
- () =>
- ({
- categories: ['user', 'project', 'default'],
- has_been_opened: true,
- }) satisfies Parameters[0],
- []
- );
- const yourWorkflowsCountQueryArg = useMemo(
- () =>
- ({
- categories: ['user', 'project'],
- }) satisfies Parameters[0],
- []
- );
- const queryOptions = useMemo(
- () =>
- ({
- selectFromResult: ({ data, isLoading }) => {
- if (!data) {
- return { count: 0, isLoading: true };
- }
- return {
- count: Object.values(data).reduce((acc, count) => acc + count, 0),
- isLoading,
- };
- },
- }) satisfies Parameters[1],
- []
- );
const { count: recentWorkflowsCount, isLoading: isLoadingRecentWorkflowsCount } = useGetCountsByCategoryQuery(
recentWorkflowsCountQueryArg,
@@ -119,7 +103,7 @@ const useSyncInitialWorkflowLibraryCategories = () => {
} else {
dispatch(workflowLibraryViewChanged('defaults'));
}
- } else if (yourWorkflowsCount === 0 && (view === 'yours' || view === 'shared' || view === 'private')) {
+ } else if (yourWorkflowsCount === 0 && view === 'yours') {
if (recentWorkflowsCount > 0) {
dispatch(workflowLibraryViewChanged('recent'));
} else {
@@ -128,7 +112,6 @@ const useSyncInitialWorkflowLibraryCategories = () => {
}
setDidSync(true);
}, [
- categoryOptions,
didSync,
dispatch,
isLoadingRecentWorkflowsCount,
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx
index 5000d7f564b..dedfdcc6599 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowLibrarySideNav.tsx
@@ -12,16 +12,14 @@ import {
Text,
Tooltip,
} from '@invoke-ai/ui-library';
-import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { getOverlayScrollbarsParams, overlayScrollbarsStyles } from 'common/components/OverlayScrollbars/constants';
import type { WorkflowLibraryView, WorkflowTagCategory } from 'features/nodes/store/workflowLibrarySlice';
import {
- $workflowLibraryCategoriesOptions,
- $workflowLibraryTagCategoriesOptions,
- $workflowLibraryTagOptions,
selectWorkflowLibrarySelectedTags,
selectWorkflowLibraryView,
+ WORKFLOW_LIBRARY_TAG_CATEGORIES,
+ WORKFLOW_LIBRARY_TAGS,
workflowLibraryTagsReset,
workflowLibraryTagToggled,
workflowLibraryViewChanged,
@@ -31,33 +29,18 @@ import { UploadWorkflowButton } from 'features/workflowLibrary/components/Upload
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
-import { PiArrowCounterClockwiseBold, PiStarFill, PiUsersBold } from 'react-icons/pi';
+import { PiArrowCounterClockwiseBold, PiStarFill } from 'react-icons/pi';
import { useDispatch } from 'react-redux';
import { useGetCountsByTagQuery } from 'services/api/endpoints/workflows';
export const WorkflowLibrarySideNav = () => {
const { t } = useTranslation();
- const categoryOptions = useStore($workflowLibraryCategoriesOptions);
- const view = useAppSelector(selectWorkflowLibraryView);
return (
{t('workflows.recentlyOpened')}
{t('workflows.yourWorkflows')}
- {categoryOptions.includes('project') && (
-
-
-
- {t('workflows.private')}
-
- } view="shared">
- {t('workflows.shared')}
-
-
-
-
- )}
@@ -107,7 +90,6 @@ BrowseWorkflowsButton.displayName = 'BrowseWorkflowsButton';
const overlayscrollbarsOptions = getOverlayScrollbarsParams({ visibility: 'visible' }).options;
const DefaultsViewCheckboxesCollapsible = memo(() => {
- const tagCategoryOptions = useStore($workflowLibraryTagCategoriesOptions);
const view = useAppSelector(selectWorkflowLibraryView);
return (
@@ -115,7 +97,7 @@ const DefaultsViewCheckboxesCollapsible = memo(() => {
- {tagCategoryOptions.map((tagCategory) => (
+ {WORKFLOW_LIBRARY_TAG_CATEGORIES.map((tagCategory) => (
))}
@@ -126,16 +108,12 @@ const DefaultsViewCheckboxesCollapsible = memo(() => {
});
DefaultsViewCheckboxesCollapsible.displayName = 'DefaultsViewCheckboxes';
+const tagCountQueryArg = {
+ tags: WORKFLOW_LIBRARY_TAGS.map((tag) => tag.label),
+ categories: ['default'],
+} satisfies Parameters[0];
+
const useCountForIndividualTag = (tag: string) => {
- const allTags = useStore($workflowLibraryTagOptions);
- const queryArg = useMemo(
- () =>
- ({
- tags: allTags.map((tag) => tag.label),
- categories: ['default'],
- }) satisfies Parameters[0],
- [allTags]
- );
const queryOptions = useMemo(
() =>
({
@@ -146,21 +124,12 @@ const useCountForIndividualTag = (tag: string) => {
[tag]
);
- const { count } = useGetCountsByTagQuery(queryArg, queryOptions);
+ const { count } = useGetCountsByTagQuery(tagCountQueryArg, queryOptions);
return count;
};
const useCountForTagCategory = (tagCategory: WorkflowTagCategory) => {
- const allTags = useStore($workflowLibraryTagOptions);
- const queryArg = useMemo(
- () =>
- ({
- tags: allTags.map((tag) => tag.label),
- categories: ['default'], // We only allow filtering by tag for default workflows
- }) satisfies Parameters[0],
- [allTags]
- );
const queryOptions = useMemo(
() =>
({
@@ -176,7 +145,7 @@ const useCountForTagCategory = (tagCategory: WorkflowTagCategory) => {
[tagCategory]
);
- const { count } = useGetCountsByTagQuery(queryArg, queryOptions);
+ const { count } = useGetCountsByTagQuery(tagCountQueryArg, queryOptions);
return count;
};
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowList.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowList.tsx
index 34b40e98473..203a1f7a319 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowList.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowList.tsx
@@ -29,15 +29,9 @@ const getCategories = (view: WorkflowLibraryView): WorkflowCategory[] => {
case 'defaults':
return ['default'];
case 'recent':
- return ['user', 'project', 'default'];
+ return ['user', 'default'];
case 'yours':
- return ['user', 'project'];
- case 'private':
return ['user'];
- case 'shared':
- return ['project'];
- case 'published':
- return ['user', 'project', 'default'];
default:
assert>(false);
}
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowListItem.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowListItem.tsx
index 34913434bc8..8ea1bbf511f 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowListItem.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLibrary/WorkflowListItem.tsx
@@ -7,7 +7,7 @@ import { useLoadWorkflowWithDialog } from 'features/workflowLibrary/components/L
import InvokeLogo from 'public/assets/images/invoke-symbol-wht-lrg.svg';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
-import { PiImage, PiUsersBold } from 'react-icons/pi';
+import { PiImage } from 'react-icons/pi';
import type { WorkflowRecordListItemWithThumbnailDTO } from 'services/api/types';
import { DeleteWorkflow } from './WorkflowLibraryListItemActions/DeleteWorkflow';
@@ -92,7 +92,6 @@ export const WorkflowListItem = memo(({ workflow }: { workflow: WorkflowRecordLi
{t('workflows.opened')}
)}
- {workflow.category === 'project' && }
{workflow.category === 'default' && (
{
const orderBy = useAppSelector(selectWorkflowLibraryOrderBy);
const direction = useAppSelector(selectWorkflowLibraryDirection);
- const sortOptions = useStore($workflowLibrarySortOptions);
const ORDER_BY_LABELS = useMemo(
() => ({
@@ -68,19 +66,12 @@ export const WorkflowSortControl = () => {
[dispatch]
);
- useEffect(() => {
- if (!sortOptions.includes('opened_at')) {
- dispatch(workflowLibraryOrderByChanged('name'));
- dispatch(workflowLibraryDirectionChanged('ASC'));
- }
- }, [sortOptions, dispatch]);
-
return (
{t('common.orderBy')}
-
diff --git a/invokeai/frontend/web/src/services/api/endpoints/images.ts b/invokeai/frontend/web/src/services/api/endpoints/images.ts
index 1c24f32fc69..7b150ac3572 100644
--- a/invokeai/frontend/web/src/services/api/endpoints/images.ts
+++ b/invokeai/frontend/web/src/services/api/endpoints/images.ts
@@ -475,7 +475,6 @@ export const {
useGetImageWorkflowQuery,
useLazyGetImageWorkflowQuery,
useUploadImageMutation,
- useCreateImageUploadEntryMutation,
useClearIntermediatesMutation,
useAddImagesToBoardMutation,
useRemoveImagesFromBoardMutation,
@@ -529,25 +528,6 @@ export const getImageDTO = (
return req.unwrap();
};
-/**
- * Imperative RTKQ helper to fetch an image's metadata.
- * @param image_name The name of the image
- * @param options The options for the query. By default, the query will not subscribe to the store.
- * @raises Error if the image metadata is not found or there is an error fetching the image metadata. Images without
- * metadata will return undefined.
- */
-export const getImageMetadata = (
- image_name: string,
- options?: Parameters[1]
-): Promise => {
- const _options = {
- subscribe: false,
- ...options,
- };
- const req = getStore().dispatch(imagesApi.endpoints.getImageMetadata.initiate(image_name, _options));
- return req.unwrap();
-};
-
export const uploadImage = (arg: UploadImageArg): Promise => {
const { dispatch } = getStore();
const req = dispatch(imagesApi.endpoints.uploadImage.initiate(arg, { track: false }));
diff --git a/invokeai/frontend/web/src/services/api/endpoints/stylePresets.ts b/invokeai/frontend/web/src/services/api/endpoints/stylePresets.ts
index 25d80dc47e2..f04f0a4ded6 100644
--- a/invokeai/frontend/web/src/services/api/endpoints/stylePresets.ts
+++ b/invokeai/frontend/web/src/services/api/endpoints/stylePresets.ts
@@ -1,4 +1,3 @@
-import { getStore } from 'app/store/nanostores/store';
import type { paths } from 'services/api/schema';
import type { S } from 'services/api/types';
@@ -129,22 +128,4 @@ export const {
useImportStylePresetsMutation,
} = stylePresetsApi;
-/**
- * Imperative RTKQ helper to fetch a style preset.
- * @param style_preset_id The id of the style preset to fetch
- * @param options The options for the query. By default, the query will not subscribe to the store.
- * @raises Error if the style preset is not found or there is an error fetching the style preset
- */
-export const getStylePreset = (
- style_preset_id: string,
- options?: Parameters[1]
-): Promise => {
- const _options = {
- subscribe: false,
- ...options,
- };
- const req = getStore().dispatch(stylePresetsApi.endpoints.getStylePreset.initiate(style_preset_id, _options));
- return req.unwrap();
-};
-
export const selectListStylePresetsRequestState = stylePresetsApi.endpoints.listStylePresets.select();
diff --git a/invokeai/frontend/web/src/services/api/endpoints/workflows.ts b/invokeai/frontend/web/src/services/api/endpoints/workflows.ts
index 70cbc76044f..b9a02204fc4 100644
--- a/invokeai/frontend/web/src/services/api/endpoints/workflows.ts
+++ b/invokeai/frontend/web/src/services/api/endpoints/workflows.ts
@@ -148,13 +148,6 @@ export const workflowsApi = api.injectEndpoints({
}),
invalidatesTags: (result, error, workflow_id) => [{ type: 'Workflow', id: workflow_id }],
}),
- unpublishWorkflow: build.mutation({
- query: (workflow_id) => ({
- url: buildWorkflowsUrl(`i/${workflow_id}/unpublish`),
- method: 'POST',
- }),
- invalidatesTags: (result, error, workflow_id) => [{ type: 'Workflow', id: workflow_id }],
- }),
}),
});
@@ -170,5 +163,4 @@ export const {
useListWorkflowsInfiniteInfiniteQuery,
useSetWorkflowThumbnailMutation,
useDeleteWorkflowThumbnailMutation,
- useUnpublishWorkflowMutation,
} = workflowsApi;
diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts
index c06c58c577c..4da374c26f1 100644
--- a/invokeai/frontend/web/src/services/api/schema.ts
+++ b/invokeai/frontend/web/src/services/api/schema.ts
@@ -24292,7 +24292,7 @@ export type components = {
* WorkflowCategory
* @enum {string}
*/
- WorkflowCategory: "user" | "default" | "project";
+ WorkflowCategory: "user" | "default";
/** WorkflowMeta */
WorkflowMeta: {
/**
diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts
index 96d15409b85..d5f7eedaf4c 100644
--- a/invokeai/frontend/web/src/services/api/types.ts
+++ b/invokeai/frontend/web/src/services/api/types.ts
@@ -102,7 +102,6 @@ type CLIPVisionDiffusersConfig = Extract;
export type FLUXReduxModelConfig = Extract;
type ApiModelConfig = Extract;
-export type VideoApiModelConfig = Extract;
type UnknownModelConfig = Extract;
export type FLUXKontextModelConfig = MainModelConfig;
export type ChatGPT4oModelConfig = ApiModelConfig;
diff --git a/invokeai/frontend/web/src/services/api/util/tagInvalidation.ts b/invokeai/frontend/web/src/services/api/util/tagInvalidation.ts
index b7a0c73df1a..02d18bf6d3e 100644
--- a/invokeai/frontend/web/src/services/api/util/tagInvalidation.ts
+++ b/invokeai/frontend/web/src/services/api/util/tagInvalidation.ts
@@ -57,20 +57,3 @@ export const getTagsToInvalidateForImageMutation = (image_names: string[]): ApiT
return tags;
};
-
-export const getTagsToInvalidateForVideoMutation = (video_ids: string[]): ApiTagDescription[] => {
- const tags: ApiTagDescription[] = [];
-
- for (const video_id of video_ids) {
- tags.push({
- type: 'Video',
- id: video_id,
- });
- // tags.push({
- // type: 'VideoMetadata',
- // id: video_id,
- // });
- }
-
- return tags;
-};
From b3594c5c929257d03ca9239577e3d6c724449004 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Mon, 13 Oct 2025 15:24:47 +1100
Subject: [PATCH 03/20] tidy: removing unused code paths 3
---
invokeai/frontend/web/public/locales/en.json | 54 ++-----------------
.../src/app/components/GlobalImageHotkeys.tsx | 2 +-
.../frontend/web/src/app/logging/logger.ts | 1 -
.../listeners/appStarted.ts | 4 +-
.../listeners/boardIdSelected.ts | 10 ++--
.../frontend/web/src/common/hooks/focus.ts | 1 -
.../web/src/common/hooks/useGlobalHotkeys.ts | 6 +--
.../features/deleteImageModal/store/state.ts | 10 ++--
.../ContextMenuItemLocateInGalery.tsx | 2 +-
.../MenuItems/ContextMenuItemOpenInViewer.tsx | 4 +-
.../MultipleSelectionMenuItems.tsx | 10 ++--
.../components/ImageGrid/GalleryImage.tsx | 20 +++----
.../GalleryItemOpenInViewerIconButton.tsx | 4 +-
.../ImageGrid/GallerySelectionCountTag.tsx | 3 +-
.../ImageViewer/CurrentImageButtons.tsx | 2 +-
.../ImageViewer/ImageComparison.tsx | 2 +-
.../components/ImageViewer/ImageViewer.tsx | 2 +-
.../ImageViewer/ImageViewerToolbar.tsx | 2 +-
.../gallery/components/ImageViewer/common.ts | 2 +-
.../gallery/components/NewGallery.tsx | 10 ++--
.../components/NextPrevItemButtons.tsx | 14 ++---
.../gallery/store/gallerySelectors.ts | 3 --
.../features/gallery/store/gallerySlice.ts | 20 +++----
.../web/src/features/gallery/store/types.ts | 4 +-
.../nodes/CurrentImage/CurrentImageNode.tsx | 2 +-
.../components/VideosModal/VideoCard.tsx | 10 +---
.../components/VideosModal/VideosModal.tsx | 31 +----------
.../VideosModal/VideosModalButton.tsx | 6 +--
.../web/src/features/system/store/actions.ts | 4 --
.../services/events/onInvocationComplete.tsx | 6 +--
30 files changed, 71 insertions(+), 180 deletions(-)
delete mode 100644 invokeai/frontend/web/src/features/system/store/actions.ts
diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index aacd5c728f1..d88ba697c58 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -43,8 +43,6 @@
"move": "Move",
"movingImagesToBoard_one": "Moving {{count}} image to board:",
"movingImagesToBoard_other": "Moving {{count}} images to board:",
- "movingVideosToBoard_one": "Moving {{count}} video to board:",
- "movingVideosToBoard_other": "Moving {{count}} videos to board:",
"myBoard": "My Board",
"noBoards": "No {{boardType}} Boards",
"noMatching": "No matching Boards",
@@ -61,8 +59,6 @@
"imagesWithCount_other": "{{count}} images",
"assetsWithCount_one": "{{count}} asset",
"assetsWithCount_other": "{{count}} assets",
- "videosWithCount_one": "{{count}} video",
- "videosWithCount_other": "{{count}} videos",
"updateBoardError": "Error updating board"
},
"accordions": {
@@ -375,9 +371,6 @@
"deleteImage_one": "Delete Image",
"deleteImage_other": "Delete {{count}} Images",
"deleteImagePermanent": "Deleted images cannot be restored.",
- "deleteVideo_one": "Delete Video",
- "deleteVideo_other": "Delete {{count}} Videos",
- "deleteVideoPermanent": "Deleted videos cannot be restored.",
"displayBoardSearch": "Board Search",
"displaySearch": "Image Search",
"download": "Download",
@@ -397,7 +390,6 @@
"sortDirection": "Sort Direction",
"showStarredImagesFirst": "Show Starred Images First",
"noImageSelected": "No Image Selected",
- "noVideoSelected": "No Video Selected",
"noImagesInGallery": "No Images to Display",
"starImage": "Star",
"unstarImage": "Unstar",
@@ -429,9 +421,7 @@
"openViewer": "Open Viewer",
"closeViewer": "Close Viewer",
"move": "Move",
- "useForPromptGeneration": "Use for Prompt Generation",
- "videos": "Videos",
- "videosTab": "Videos you've created and saved within Invoke."
+ "useForPromptGeneration": "Use for Prompt Generation"
},
"hotkeys": {
"hotkeys": "Hotkeys",
@@ -476,10 +466,6 @@
"title": "Select the Queue Tab",
"desc": "Selects the Queue tab."
},
- "selectVideoTab": {
- "title": "Select the Video Tab",
- "desc": "Selects the Video tab."
- },
"focusPrompt": {
"title": "Focus Prompt",
"desc": "Move cursor focus to the positive prompt."
@@ -514,9 +500,6 @@
"key": "1"
}
},
- "video": {
- "title": "Video"
- },
"canvas": {
"title": "Canvas",
"selectBrushTool": {
@@ -823,13 +806,11 @@
"guidance": "Guidance",
"height": "Height",
"imageDetails": "Image Details",
- "videoDetails": "Video Details",
"imageDimensions": "Image Dimensions",
"metadata": "Metadata",
"model": "Model",
"negativePrompt": "Negative Prompt",
"noImageDetails": "No image details found",
- "noVideoDetails": "No video details found",
"noMetaData": "No metadata found",
"noRecallParameters": "No parameters to recall found",
"parameterSet": "Parameter {{parameter}} set",
@@ -847,11 +828,7 @@
"vae": "VAE",
"width": "Width",
"workflow": "Workflow",
- "canvasV2Metadata": "Canvas Layers",
- "videoModel": "Model",
- "videoDuration": "Duration",
- "videoAspectRatio": "Aspect Ratio",
- "videoResolution": "Resolution"
+ "canvasV2Metadata": "Canvas Layers"
},
"modelManager": {
"active": "active",
@@ -1269,13 +1246,9 @@
"images": "Images",
"images_withCount_one": "Image",
"images_withCount_other": "Images",
- "videos_withCount_one": "Video",
- "videos_withCount_other": "Videos",
"infillMethod": "Infill Method",
"infillColorValue": "Fill Color",
"info": "Info",
- "startingFrameImage": "Start Frame",
- "startingFrameImageAspectRatioWarning": "Image aspect ratio does not match the video aspect ratio ({{videoAspectRatio}}). This could lead to unexpected cropping during video generation.",
"invoke": {
"addingImagesTo": "Adding images to",
"modelDisabledForTrial": "Generating with {{modelName}} is not available on trial accounts. Visit your account settings to upgrade.",
@@ -1322,8 +1295,7 @@
"noNodesInGraph": "No nodes in graph",
"systemDisconnected": "System disconnected",
"promptExpansionPending": "Prompt expansion in progress",
- "promptExpansionResultPending": "Please accept or discard your prompt expansion result",
- "videoIsDisabled": "Video generation is not enabled for {{accountType}} accounts."
+ "promptExpansionResultPending": "Please accept or discard your prompt expansion result"
},
"maskBlur": "Mask Blur",
"negativePromptPlaceholder": "Negative Prompt",
@@ -1341,11 +1313,9 @@
"seamlessXAxis": "Seamless X Axis",
"seamlessYAxis": "Seamless Y Axis",
"seed": "Seed",
- "videoActions": "Video Actions",
"imageActions": "Image Actions",
"sendToCanvas": "Send To Canvas",
"sendToUpscale": "Send To Upscale",
- "sendToVideo": "Send To Video",
"showOptionsPanel": "Show Side Panel (O or T)",
"shuffle": "Shuffle Seed",
"steps": "Steps",
@@ -1357,7 +1327,6 @@
"postProcessing": "Post-Processing (Shift + U)",
"processImage": "Process Image",
"upscaling": "Upscaling",
- "video": "Video",
"useAll": "Use All",
"useSize": "Use Size",
"useCpuNoise": "Use CPU Noise",
@@ -2660,30 +2629,19 @@
"queue": "Queue",
"upscaling": "Upscaling",
"upscalingTab": "$t(ui.tabs.upscaling) $t(common.tab)",
- "video": "Video",
"gallery": "Gallery"
},
"panels": {
"launchpad": "Launchpad",
"workflowEditor": "Workflow Editor",
"imageViewer": "Viewer",
- "canvas": "Canvas",
- "video": "Video"
+ "canvas": "Canvas"
},
"launchpad": {
"workflowsTitle": "Go deep with Workflows.",
"upscalingTitle": "Upscale and add detail.",
"canvasTitle": "Edit and refine on Canvas.",
"generateTitle": "Generate images from text prompts.",
- "videoTitle": "Generate videos from text prompts.",
- "video": {
- "startingFrameCalloutTitle": "Add a Starting Frame",
- "startingFrameCalloutDesc": "Add an image to control the first frame of your video."
- },
- "addStartingFrame": {
- "title": "Add a Starting Frame",
- "description": "Add an image to control the first frame of your video."
- },
"modelGuideText": "Want to learn what prompts work best for each model?",
"modelGuideLink": "Check out our Model Guide.",
"createNewWorkflowFromScratch": "Create a new Workflow from scratch",
@@ -2758,10 +2716,6 @@
}
}
},
- "video": {
- "noVideoSelected": "No video selected",
- "selectFromGallery": "Select a video from the gallery to play"
- },
"system": {
"enableLogging": "Enable Logging",
"logLevel": {
diff --git a/invokeai/frontend/web/src/app/components/GlobalImageHotkeys.tsx b/invokeai/frontend/web/src/app/components/GlobalImageHotkeys.tsx
index c86faa50bbf..dd1595bdd74 100644
--- a/invokeai/frontend/web/src/app/components/GlobalImageHotkeys.tsx
+++ b/invokeai/frontend/web/src/app/components/GlobalImageHotkeys.tsx
@@ -16,7 +16,7 @@ import type { ImageDTO } from 'services/api/types';
export const GlobalImageHotkeys = memo(() => {
useAssertSingleton('GlobalImageHotkeys');
const lastSelectedItem = useAppSelector(selectLastSelectedItem);
- const imageDTO = useImageDTO(lastSelectedItem?.type === 'image' ? lastSelectedItem.id : null);
+ const imageDTO = useImageDTO(lastSelectedItem ?? null);
if (!imageDTO) {
return null;
diff --git a/invokeai/frontend/web/src/app/logging/logger.ts b/invokeai/frontend/web/src/app/logging/logger.ts
index 4e024f1516a..7c6b4ecdde0 100644
--- a/invokeai/frontend/web/src/app/logging/logger.ts
+++ b/invokeai/frontend/web/src/app/logging/logger.ts
@@ -26,7 +26,6 @@ export const zLogNamespace = z.enum([
'system',
'queue',
'workflows',
- 'video',
]);
export type LogNamespace = z.infer;
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/appStarted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/appStarted.ts
index 5ed60447aae..794d1a1af60 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/appStarted.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/appStarted.ts
@@ -1,7 +1,7 @@
import { createAction } from '@reduxjs/toolkit';
import type { AppStartListening } from 'app/store/store';
import { selectLastSelectedItem } from 'features/gallery/store/gallerySelectors';
-import { itemSelected } from 'features/gallery/store/gallerySlice';
+import { imageSelected } from 'features/gallery/store/gallerySlice';
import { imagesApi } from 'services/api/endpoints/images';
export const appStarted = createAction('app/appStarted');
@@ -23,7 +23,7 @@ export const addAppStartedListener = (startAppListening: AppStartListening) => {
return;
}
if (payload.image_names[0]) {
- dispatch(itemSelected({ type: 'image', id: payload.image_names[0] }));
+ dispatch(imageSelected(payload.image_names[0]));
}
}
},
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts
index a408a94c041..9fd777fb29b 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardIdSelected.ts
@@ -1,7 +1,7 @@
import { isAnyOf } from '@reduxjs/toolkit';
import type { AppStartListening } from 'app/store/store';
import { selectGetImageNamesQueryArgs, selectSelectedBoardId } from 'features/gallery/store/gallerySelectors';
-import { boardIdSelected, galleryViewChanged, itemSelected } from 'features/gallery/store/gallerySlice';
+import { boardIdSelected, galleryViewChanged, imageSelected } from 'features/gallery/store/gallerySlice';
import { imagesApi } from 'services/api/endpoints/images';
export const addBoardIdSelectedListener = (startAppListening: AppStartListening) => {
@@ -29,7 +29,7 @@ export const addBoardIdSelectedListener = (startAppListening: AppStartListening)
);
if (!isSuccess) {
- dispatch(itemSelected(null));
+ dispatch(imageSelected(null));
return;
}
@@ -38,11 +38,7 @@ export const addBoardIdSelectedListener = (startAppListening: AppStartListening)
const imageToSelect = imageNames && imageNames.length > 0 ? imageNames[0] : null;
- if (imageToSelect) {
- dispatch(itemSelected({ type: 'image', id: imageToSelect }));
- } else {
- dispatch(itemSelected(null));
- }
+ dispatch(imageSelected(imageToSelect ?? null));
},
});
};
diff --git a/invokeai/frontend/web/src/common/hooks/focus.ts b/invokeai/frontend/web/src/common/hooks/focus.ts
index 8a04608a13d..4e093c5c631 100644
--- a/invokeai/frontend/web/src/common/hooks/focus.ts
+++ b/invokeai/frontend/web/src/common/hooks/focus.ts
@@ -37,7 +37,6 @@ const REGION_NAMES = [
'workflows',
'progress',
'settings',
- 'video',
] as const;
/**
* The names of the focus regions.
diff --git a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts
index 69b0b2b0fc2..dd43c0b0947 100644
--- a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts
+++ b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts
@@ -131,11 +131,7 @@ export const useGlobalHotkeys = () => {
if (!selection.length) {
return;
}
- if (selection.every(({ type }) => type === 'image')) {
- deleteImageModalApi.delete(selection.map((s) => s.id));
- } else {
- // no-op, we expect selections to always be only images or only video
- }
+ deleteImageModalApi.delete(selection);
},
dependencies: [getState, deleteImageModalApi],
});
diff --git a/invokeai/frontend/web/src/features/deleteImageModal/store/state.ts b/invokeai/frontend/web/src/features/deleteImageModal/store/state.ts
index 38aa8b039f3..c50aa9465f5 100644
--- a/invokeai/frontend/web/src/features/deleteImageModal/store/state.ts
+++ b/invokeai/frontend/web/src/features/deleteImageModal/store/state.ts
@@ -12,7 +12,7 @@ import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import type { CanvasState, RefImagesState } from 'features/controlLayers/store/types';
import type { ImageUsage } from 'features/deleteImageModal/store/types';
import { selectGetImageNamesQueryArgs } from 'features/gallery/store/gallerySelectors';
-import { itemSelected } from 'features/gallery/store/gallerySlice';
+import { imageSelected } from 'features/gallery/store/gallerySlice';
import { fieldImageCollectionValueChanged, fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import type { NodesState } from 'features/nodes/store/types';
@@ -89,14 +89,12 @@ const handleDeletions = async (image_names: string[], store: AppStore) => {
const newImageNames = data?.image_names.filter((name) => !deleted_images.includes(name)) || [];
const newSelectedImage = newImageNames[index ?? 0] || null;
- const galleryImageNames = state.gallery.selection.map((s) => s.id);
-
- if (intersection(galleryImageNames, image_names).length > 0) {
+ if (intersection(state.gallery.selection, image_names).length > 0) {
if (newSelectedImage) {
// Some selected images were deleted, clear selection
- dispatch(itemSelected({ type: 'image', id: newSelectedImage }));
+ dispatch(imageSelected(newSelectedImage));
} else {
- dispatch(itemSelected(null));
+ dispatch(imageSelected(null));
}
}
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemLocateInGalery.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemLocateInGalery.tsx
index 85a42299fea..0a557710975 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemLocateInGalery.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemLocateInGalery.tsx
@@ -30,7 +30,7 @@ export const ContextMenuItemLocateInGalery = memo(() => {
boardIdSelected({
boardId: imageDTO.board_id ?? 'none',
select: {
- selection: [{ type: 'image', id: imageDTO.image_name }],
+ selection: [imageDTO.image_name],
galleryView: IMAGE_CATEGORIES.includes(imageDTO.image_category) ? 'images' : 'assets',
},
})
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemOpenInViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemOpenInViewer.tsx
index e09b256e85a..7a58f6e007b 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemOpenInViewer.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MenuItems/ContextMenuItemOpenInViewer.tsx
@@ -1,7 +1,7 @@
import { useAppDispatch } from 'app/store/storeHooks';
import { IconMenuItem } from 'common/components/IconMenuItem';
import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
-import { imageToCompareChanged, itemSelected } from 'features/gallery/store/gallerySlice';
+import { imageSelected, imageToCompareChanged } from 'features/gallery/store/gallerySlice';
import { navigationApi } from 'features/ui/layouts/navigation-api';
import { VIEWER_PANEL_ID } from 'features/ui/layouts/shared';
import { memo, useCallback } from 'react';
@@ -14,7 +14,7 @@ export const ContextMenuItemOpenInViewer = memo(() => {
const imageDTO = useImageDTOContext();
const onClick = useCallback(() => {
dispatch(imageToCompareChanged(null));
- dispatch(itemSelected({ type: 'image', id: imageDTO.image_name }));
+ dispatch(imageSelected(imageDTO.image_name));
navigationApi.focusPanelInActiveTab(VIEWER_PANEL_ID);
}, [dispatch, imageDTO]);
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MultipleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MultipleSelectionMenuItems.tsx
index 0e086ad5e4e..d148332943c 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MultipleSelectionMenuItems.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/MultipleSelectionMenuItems.tsx
@@ -22,24 +22,24 @@ const MultipleSelectionMenuItems = () => {
const [bulkDownload] = useBulkDownloadImagesMutation();
const handleChangeBoard = useCallback(() => {
- dispatch(imagesToChangeSelected(selection.map((s) => s.id)));
+ dispatch(imagesToChangeSelected(selection));
dispatch(isModalOpenChanged(true));
}, [dispatch, selection]);
const handleDeleteSelection = useCallback(() => {
- deleteImageModal.delete(selection.map((s) => s.id));
+ deleteImageModal.delete(selection);
}, [deleteImageModal, selection]);
const handleStarSelection = useCallback(() => {
- starImages({ image_names: selection.map((s) => s.id) });
+ starImages({ image_names: selection });
}, [starImages, selection]);
const handleUnstarSelection = useCallback(() => {
- unstarImages({ image_names: selection.map((s) => s.id) });
+ unstarImages({ image_names: selection });
}, [unstarImages, selection]);
const handleBulkDownload = useCallback(() => {
- bulkDownload({ image_names: selection.map((s) => s.id) });
+ bulkDownload({ image_names: selection });
}, [selection, bulkDownload]);
return (
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx
index 764201222b5..ccd58992ef6 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx
@@ -46,7 +46,7 @@ const buildOnClick =
if (imageNames.length === 0) {
// For basic click without modifiers, we can still set selection
if (!shiftKey && !ctrlKey && !metaKey && !altKey) {
- dispatch(selectionChanged([{ type: 'image', id: imageName }]));
+ dispatch(selectionChanged([imageName]));
}
return;
}
@@ -61,7 +61,7 @@ const buildOnClick =
}
} else if (shiftKey) {
const rangeEndImageName = imageName;
- const lastSelectedImage = selection.at(-1)?.id;
+ const lastSelectedImage = selection.at(-1);
const lastClickedIndex = imageNames.findIndex((name) => name === lastSelectedImage);
const currentClickedIndex = imageNames.findIndex((name) => name === rangeEndImageName);
if (lastClickedIndex > -1 && currentClickedIndex > -1) {
@@ -72,16 +72,16 @@ const buildOnClick =
if (currentClickedIndex < lastClickedIndex) {
imagesToSelect.reverse();
}
- dispatch(selectionChanged(uniq(selection.concat(imagesToSelect.map((name) => ({ type: 'image', id: name }))))));
+ dispatch(selectionChanged(uniq(selection.concat(imagesToSelect))));
}
} else if (ctrlKey || metaKey) {
- if (selection.some((n) => n.id === imageName) && selection.length > 1) {
- dispatch(selectionChanged(uniq(selection.filter((n) => n.id !== imageName))));
+ if (selection.some((n) => n === imageName) && selection.length > 1) {
+ dispatch(selectionChanged(uniq(selection.filter((n) => n !== imageName))));
} else {
- dispatch(selectionChanged(uniq(selection.concat({ type: 'image', id: imageName }))));
+ dispatch(selectionChanged(uniq(selection.concat(imageName))));
}
} else {
- dispatch(selectionChanged([{ type: 'image', id: imageName }]));
+ dispatch(selectionChanged([imageName]));
}
};
@@ -98,7 +98,7 @@ export const GalleryImage = memo(({ imageDTO }: Props) => {
);
const isSelectedForCompare = useAppSelector(selectIsSelectedForCompare);
const selectIsSelected = useMemo(
- () => createSelector(selectGallerySlice, (gallery) => gallery.selection.some((s) => s.id === imageDTO.image_name)),
+ () => createSelector(selectGallerySlice, (gallery) => gallery.selection.some((n) => n === imageDTO.image_name)),
[imageDTO.image_name]
);
const isSelected = useAppSelector(selectIsSelected);
@@ -118,9 +118,9 @@ export const GalleryImage = memo(({ imageDTO }: Props) => {
// When we have multiple images selected, and the dragged image is part of the selection, initiate a
// multi-image drag.
- if (selection.length > 1 && selection.some((s) => s.id === imageDTO.image_name)) {
+ if (selection.length > 1 && selection.some((n) => n === imageDTO.image_name)) {
return multipleImageDndSource.getData({
- image_names: selection.map((s) => s.id),
+ image_names: selection,
board_id: boardId,
});
}
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemOpenInViewerIconButton.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemOpenInViewerIconButton.tsx
index ae7821a3f1d..21641d69fcd 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemOpenInViewerIconButton.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryItemOpenInViewerIconButton.tsx
@@ -1,6 +1,6 @@
import { useAppDispatch } from 'app/store/storeHooks';
import { DndImageIcon } from 'features/dnd/DndImageIcon';
-import { imageToCompareChanged, itemSelected } from 'features/gallery/store/gallerySlice';
+import { imageSelected, imageToCompareChanged } from 'features/gallery/store/gallerySlice';
import { navigationApi } from 'features/ui/layouts/navigation-api';
import { VIEWER_PANEL_ID } from 'features/ui/layouts/shared';
import { memo, useCallback } from 'react';
@@ -18,7 +18,7 @@ export const GalleryItemOpenInViewerIconButton = memo(({ imageDTO }: Props) => {
const onClick = useCallback(() => {
dispatch(imageToCompareChanged(null));
- dispatch(itemSelected({ type: 'image', id: imageDTO.image_name }));
+ dispatch(imageSelected(imageDTO.image_name));
navigationApi.focusPanelInActiveTab(VIEWER_PANEL_ID);
}, [dispatch, imageDTO]);
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySelectionCountTag.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySelectionCountTag.tsx
index 0939e168e51..28c1c689396 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySelectionCountTag.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GallerySelectionCountTag.tsx
@@ -15,8 +15,7 @@ export const GallerySelectionCountTag = memo(() => {
const isGalleryFocused = useIsRegionFocused('gallery');
const onSelectPage = useCallback(() => {
- const selection = imageNames.map((name) => ({ type: 'image' as const, id: name }));
- dispatch(selectionChanged(selection));
+ dispatch(selectionChanged(imageNames));
}, [dispatch, imageNames]);
useRegisteredHotkeys({
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 603768e693a..bd9dc31a570 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImageButtons.tsx
@@ -51,7 +51,7 @@ export const CurrentImageButtons = memo(({ imageDTO }: { imageDTO: ImageDTO }) =
boardIdSelected({
boardId: imageDTO.board_id ?? 'none',
select: {
- selection: [{ type: 'image', id: imageDTO.image_name }],
+ selection: [imageDTO.image_name],
galleryView: IMAGE_CATEGORIES.includes(imageDTO.image_category) ? 'images' : 'assets',
},
})
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparison.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparison.tsx
index e3bf51bbdbf..80dbad347c7 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparison.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparison.tsx
@@ -40,7 +40,7 @@ ImageComparisonContent.displayName = 'ImageComparisonContent';
export const ImageComparison = memo(() => {
const lastSelectedItem = useAppSelector(selectLastSelectedItem);
- const lastSelectedImageDTO = useImageDTO(lastSelectedItem?.id);
+ const lastSelectedImageDTO = useImageDTO(lastSelectedItem);
const comparisonImageDTO = useImageDTO(useAppSelector(selectImageToCompare));
const [rect, setRect] = useState(null);
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx
index 72d4a79e890..ce9795ee8b0 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewer.tsx
@@ -16,7 +16,7 @@ export const ImageViewer = memo(() => {
const { t } = useTranslation();
const lastSelectedItem = useAppSelector(selectLastSelectedItem);
- const lastSelectedImageDTO = useImageDTO(lastSelectedItem?.type === 'image' ? lastSelectedItem.id : null);
+ const lastSelectedImageDTO = useImageDTO(lastSelectedItem ?? null);
return (
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewerToolbar.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewerToolbar.tsx
index 7e5f9cd9cea..b963f5a80d6 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewerToolbar.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageViewerToolbar.tsx
@@ -10,7 +10,7 @@ import { ToggleProgressButton } from './ToggleProgressButton';
export const ImageViewerToolbar = memo(() => {
const lastSelectedItem = useAppSelector(selectLastSelectedItem);
- const imageDTO = useImageDTO(lastSelectedItem?.id);
+ const imageDTO = useImageDTO(lastSelectedItem);
return (
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/common.ts b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/common.ts
index 0953a96156b..31bb1648b8f 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/common.ts
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/common.ts
@@ -65,7 +65,7 @@ export const getSecondImageDims = (
return { width, height };
};
export const selectComparisonImages = createMemoizedSelector(selectGallerySlice, (gallerySlice) => {
- const firstImage = gallerySlice.selection.slice(-1)[0]?.id ?? null;
+ const firstImage = gallerySlice.selection.at(-1) ?? null;
const secondImage = gallerySlice.imageToCompare;
return { firstImage, secondImage };
});
diff --git a/invokeai/frontend/web/src/features/gallery/components/NewGallery.tsx b/invokeai/frontend/web/src/features/gallery/components/NewGallery.tsx
index 026319392c8..d586dc979d9 100644
--- a/invokeai/frontend/web/src/features/gallery/components/NewGallery.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/NewGallery.tsx
@@ -126,8 +126,8 @@ const useKeyboardNavigation = (
const imageName = event.altKey
? // When the user holds alt, we are changing the image to compare - if no image to compare is currently selected,
// we start from the last selected image
- (selectImageToCompare(state) ?? selectLastSelectedItem(state)?.id)
- : selectLastSelectedItem(state)?.id;
+ (selectImageToCompare(state) ?? selectLastSelectedItem(state))
+ : selectLastSelectedItem(state);
const currentIndex = getItemIndex(imageName ?? null, imageNames);
@@ -174,7 +174,7 @@ const useKeyboardNavigation = (
if (event.altKey) {
dispatch(imageToCompareChanged(newImageName));
} else {
- dispatch(selectionChanged([{ type: 'image', id: newImageName }]));
+ dispatch(selectionChanged([newImageName]));
}
}
}
@@ -261,7 +261,7 @@ const useKeepSelectedImageInView = (
const selection = useAppSelector(selectSelection);
useEffect(() => {
- const targetImageName = selection.at(-1)?.id;
+ const targetImageName = selection.at(-1);
const virtuosoGridHandle = virtuosoRef.current;
const rootEl = rootRef.current;
const range = rangeRef.current;
@@ -280,7 +280,7 @@ const useStarImageHotkey = () => {
const lastSelectedItem = useAppSelector(selectLastSelectedItem);
const selectionCount = useAppSelector(selectSelectionCount);
const isGalleryFocused = useIsRegionFocused('gallery');
- const imageDTO = useImageDTO(lastSelectedItem?.id);
+ const imageDTO = useImageDTO(lastSelectedItem);
const [starImages] = useStarImagesMutation();
const [unstarImages] = useUnstarImagesMutation();
diff --git a/invokeai/frontend/web/src/features/gallery/components/NextPrevItemButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/NextPrevItemButtons.tsx
index b59965c28e3..96b3699cafb 100644
--- a/invokeai/frontend/web/src/features/gallery/components/NextPrevItemButtons.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/NextPrevItemButtons.tsx
@@ -3,7 +3,7 @@ import { Box, IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { clamp } from 'es-toolkit/compat';
import { selectLastSelectedItem } from 'features/gallery/store/gallerySelectors';
-import { itemSelected } from 'features/gallery/store/gallerySlice';
+import { imageSelected } from 'features/gallery/store/gallerySlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiCaretLeftBold, PiCaretRightBold } from 'react-icons/pi';
@@ -17,32 +17,32 @@ const NextPrevItemButtons = ({ inset = 8 }: { inset?: ChakraProps['insetInlineSt
const { imageNames, isFetching } = useGalleryImageNames();
const isOnFirstItem = useMemo(
- () => (lastSelectedItem ? imageNames.at(0) === lastSelectedItem.id : false),
+ () => (lastSelectedItem ? imageNames.at(0) === lastSelectedItem : false),
[imageNames, lastSelectedItem]
);
const isOnLastItem = useMemo(
- () => (lastSelectedItem ? imageNames.at(-1) === lastSelectedItem.id : false),
+ () => (lastSelectedItem ? imageNames.at(-1) === lastSelectedItem : false),
[imageNames, lastSelectedItem]
);
const onClickLeftArrow = useCallback(() => {
- const targetIndex = lastSelectedItem ? imageNames.findIndex((n) => n === lastSelectedItem.id) - 1 : 0;
+ const targetIndex = lastSelectedItem ? imageNames.findIndex((n) => n === lastSelectedItem) - 1 : 0;
const clampedIndex = clamp(targetIndex, 0, imageNames.length - 1);
const n = imageNames.at(clampedIndex);
if (!n) {
return;
}
- dispatch(itemSelected({ type: lastSelectedItem?.type ?? 'image', id: n }));
+ dispatch(imageSelected(n));
}, [dispatch, imageNames, lastSelectedItem]);
const onClickRightArrow = useCallback(() => {
- const targetIndex = lastSelectedItem ? imageNames.findIndex((n) => n === lastSelectedItem.id) + 1 : 0;
+ const targetIndex = lastSelectedItem ? imageNames.findIndex((n) => n === lastSelectedItem) + 1 : 0;
const clampedIndex = clamp(targetIndex, 0, imageNames.length - 1);
const n = imageNames.at(clampedIndex);
if (!n) {
return;
}
- dispatch(itemSelected({ type: lastSelectedItem?.type ?? 'image', id: n }));
+ dispatch(imageSelected(n));
}, [dispatch, imageNames, lastSelectedItem]);
return (
diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
index ab6b584a15e..aad849fdb59 100644
--- a/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/gallerySelectors.ts
@@ -24,9 +24,6 @@ const selectGalleryQueryCategories = createSelector(selectGalleryView, (galleryV
if (galleryView === 'images') {
return IMAGE_CATEGORIES;
}
- if (galleryView === 'videos') {
- return [];
- }
return ASSETS_CATEGORIES;
});
const selectGallerySearchTerm = createSelector(selectGallerySlice, (gallery) => gallery.searchTerm);
diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
index 1f99fc7a6d3..d66feefa2c9 100644
--- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
@@ -2,7 +2,7 @@ import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store';
import type { SliceConfig } from 'app/store/types';
-import { isPlainObject } from 'es-toolkit';
+import { isPlainObject, uniq } from 'es-toolkit';
import type { BoardRecordOrderBy } from 'services/api/types';
import { assert } from 'tsafe';
@@ -40,7 +40,7 @@ const slice = createSlice({
name: 'gallery',
initialState: getInitialState(),
reducers: {
- itemSelected: (state, action: PayloadAction<{ type: 'image' | 'video'; id: string } | null>) => {
+ imageSelected: (state, action: PayloadAction) => {
const selectedItem = action.payload;
if (!selectedItem) {
@@ -49,14 +49,8 @@ const slice = createSlice({
state.selection = [selectedItem];
}
},
- selectionChanged: (state, action: PayloadAction<{ type: 'image' | 'video'; id: string }[]>) => {
- const uniqueById = new Map();
- for (const item of action.payload) {
- if (!uniqueById.has(item.id)) {
- uniqueById.set(item.id, item);
- }
- }
- state.selection = Array.from(uniqueById.values());
+ selectionChanged: (state, action: PayloadAction) => {
+ state.selection = uniq(action.payload);
},
imageToCompareChanged: (state, action: PayloadAction) => {
state.imageToCompare = action.payload;
@@ -122,8 +116,8 @@ const slice = createSlice({
comparedImagesSwapped: (state) => {
if (state.imageToCompare) {
const oldSelection = state.selection;
- state.selection = [{ type: 'image', id: state.imageToCompare }];
- state.imageToCompare = oldSelection[0]?.id ?? null;
+ state.selection = [state.imageToCompare];
+ state.imageToCompare = oldSelection[0] ?? null;
}
},
comparisonFitChanged: (state, action: PayloadAction<'contain' | 'fill'>) => {
@@ -151,7 +145,7 @@ const slice = createSlice({
});
export const {
- itemSelected,
+ imageSelected,
shouldAutoSwitchChanged,
autoAssignBoardOnClickChanged,
setGalleryImageMinimumWidth,
diff --git a/invokeai/frontend/web/src/features/gallery/store/types.ts b/invokeai/frontend/web/src/features/gallery/store/types.ts
index 0a03c7d2662..addeefe870f 100644
--- a/invokeai/frontend/web/src/features/gallery/store/types.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/types.ts
@@ -1,7 +1,7 @@
import type { ImageCategory } from 'services/api/types';
import z from 'zod';
-const zGalleryView = z.enum(['images', 'assets', 'videos']);
+const zGalleryView = z.enum(['images', 'assets']);
export type GalleryView = z.infer;
const zBoardId = z.string();
// TS hack to get autocomplete for "none" but accept any string
@@ -19,7 +19,7 @@ export const IMAGE_CATEGORIES: ImageCategory[] = ['general'];
export const ASSETS_CATEGORIES: ImageCategory[] = ['control', 'mask', 'user', 'other'];
export const zGalleryState = z.object({
- selection: z.array(z.object({ type: z.enum(['image', 'video']), id: z.string() })),
+ selection: z.array(z.string()),
shouldAutoSwitch: z.boolean(),
autoAssignBoardOnClick: z.boolean(),
autoAddBoardId: zBoardId,
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/CurrentImage/CurrentImageNode.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/CurrentImage/CurrentImageNode.tsx
index 2e77415757c..c8423a8fe4e 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/CurrentImage/CurrentImageNode.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/CurrentImage/CurrentImageNode.tsx
@@ -19,7 +19,7 @@ import { $lastProgressEvent } from 'services/events/stores';
const CurrentImageNode = (props: NodeProps) => {
const lastSelectedItem = useAppSelector(selectLastSelectedItem);
const lastProgressEvent = useStore($lastProgressEvent);
- const imageDTO = useImageDTO(lastSelectedItem?.id);
+ const imageDTO = useImageDTO(lastSelectedItem);
if (lastProgressEvent?.image) {
return (
diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCard.tsx b/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCard.tsx
index d97a0bed341..4f037224a62 100644
--- a/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCard.tsx
+++ b/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCard.tsx
@@ -1,17 +1,11 @@
import { ExternalLink, Flex, Spacer, Text } from '@invoke-ai/ui-library';
-import { useAppDispatch } from 'app/store/storeHooks';
import type { VideoData } from 'features/system/components/VideosModal/data';
-import { videoModalLinkClicked } from 'features/system/store/actions';
-import { memo, useCallback } from 'react';
+import { memo } from 'react';
import { useTranslation } from 'react-i18next';
export const VideoCard = memo(({ video }: { video: VideoData }) => {
const { t } = useTranslation();
- const dispatch = useAppDispatch();
const { tKey, link } = video;
- const handleLinkClick = useCallback(() => {
- dispatch(videoModalLinkClicked(t(`supportVideos.videos.${tKey}.title`)));
- }, [dispatch, t, tKey]);
return (
@@ -20,7 +14,7 @@ export const VideoCard = memo(({ video }: { video: VideoData }) => {
{t(`supportVideos.videos.${tKey}.title`)}
-
+
{t(`supportVideos.videos.${tKey}.description`)}
diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModal.tsx b/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModal.tsx
index 2db109479f5..818f531820e 100644
--- a/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModal.tsx
+++ b/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModal.tsx
@@ -10,7 +10,6 @@ import {
ModalOverlay,
Text,
} from '@invoke-ai/ui-library';
-import { useAppDispatch } from 'app/store/storeHooks';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import { buildUseDisclosure } from 'common/hooks/useBoolean';
import {
@@ -19,62 +18,36 @@ import {
supportVideos,
} from 'features/system/components/VideosModal/data';
import { VideoCardList } from 'features/system/components/VideosModal/VideoCardList';
-import { videoModalLinkClicked } from 'features/system/store/actions';
import { discordLink } from 'features/system/store/constants';
-import { memo, useCallback } from 'react';
+import { memo } from 'react';
import { Trans, useTranslation } from 'react-i18next';
export const [useVideosModal] = buildUseDisclosure(false);
const GettingStartedPlaylistLink = () => {
- const dispatch = useAppDispatch();
- const handleLinkClick = useCallback(() => {
- dispatch(videoModalLinkClicked('Getting Started playlist'));
- }, [dispatch]);
-
return (
);
};
const StudioSessionsPlaylistLink = () => {
- const dispatch = useAppDispatch();
- const handleLinkClick = useCallback(() => {
- dispatch(videoModalLinkClicked('Studio Sessions playlist'));
- }, [dispatch]);
-
return (
);
};
const DiscordLink = () => {
- const dispatch = useAppDispatch();
- const handleLinkClick = useCallback(() => {
- dispatch(videoModalLinkClicked('Discord'));
- }, [dispatch]);
-
- return (
-
- );
+ return ;
};
const components = {
diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModalButton.tsx b/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModalButton.tsx
index 3b5169a14ae..bf99afdbc8e 100644
--- a/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModalButton.tsx
+++ b/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModalButton.tsx
@@ -1,21 +1,17 @@
import { IconButton } from '@invoke-ai/ui-library';
-import { useAppDispatch } from 'app/store/storeHooks';
import { useVideosModal } from 'features/system/components/VideosModal/VideosModal';
-import { videoModalOpened } from 'features/system/store/actions';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiYoutubeLogoFill } from 'react-icons/pi';
export const VideosModalButton = memo(() => {
const { t } = useTranslation();
- const dispatch = useAppDispatch();
const videosModal = useVideosModal();
const onClickOpen = useCallback(() => {
- dispatch(videoModalOpened());
videosModal.open();
- }, [videosModal, dispatch]);
+ }, [videosModal]);
return (
('system/videoModalLinkClicked');
-export const videoModalOpened = createAction('system/videoModalOpened');
diff --git a/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
index f9fafbbcdcd..d076b9a7303 100644
--- a/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
+++ b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
@@ -8,7 +8,7 @@ import {
selectListBoardsQueryArgs,
selectSelectedBoardId,
} from 'features/gallery/store/gallerySelectors';
-import { boardIdSelected, galleryViewChanged, itemSelected } from 'features/gallery/store/gallerySlice';
+import { boardIdSelected, galleryViewChanged, imageSelected } from 'features/gallery/store/gallerySlice';
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
import { isImageField, isImageFieldCollection } from 'features/nodes/types/common';
import { zNodeStatus } from 'features/nodes/types/invocation';
@@ -170,7 +170,7 @@ export const buildOnInvocationComplete = (
boardIdSelected({
boardId: board_id,
select: {
- selection: [{ type: 'image', id: image_name }],
+ selection: [image_name],
galleryView: 'images',
},
})
@@ -182,7 +182,7 @@ export const buildOnInvocationComplete = (
dispatch(galleryViewChanged('images'));
}
// Select the image immediately since we've optimistically updated the cache
- dispatch(itemSelected({ type: 'image', id: lastImageDTO.image_name }));
+ dispatch(imageSelected(lastImageDTO.image_name));
}
};
From 5968d71858cd953ac135d8d964c8c4a120b5cd28 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Mon, 13 Oct 2025 15:31:56 +1100
Subject: [PATCH 04/20] tidy: removing unused code paths 4
---
.../controlLayers/hooks/addLayerHooks.ts | 10 +-----
.../src/features/controlLayers/store/types.ts | 34 ++-----------------
.../InvokeButtonTooltip.tsx | 21 ------------
.../frontend/web/src/services/api/index.ts | 7 ----
.../src/services/api/util/tagInvalidation.ts | 2 +-
.../vocab.json | 4 +--
6 files changed, 6 insertions(+), 72 deletions(-)
diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts
index 8f0a1a11a6f..fe23ec9d90b 100644
--- a/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts
@@ -24,11 +24,9 @@ import {
import type {
CanvasEntityIdentifier,
CanvasRegionalGuidanceState,
- ChatGPT4oReferenceImageConfig,
ControlLoRAConfig,
ControlNetConfig,
FluxKontextReferenceImageConfig,
- Gemini2_5ReferenceImageConfig,
IPAdapterConfig,
RegionalGuidanceIPAdapterConfig,
T2IAdapterConfig,
@@ -76,13 +74,7 @@ export const selectDefaultControlAdapter = createSelector(
}
);
-export const getDefaultRefImageConfig = (
- getState: AppGetState
-):
- | IPAdapterConfig
- | ChatGPT4oReferenceImageConfig
- | FluxKontextReferenceImageConfig
- | Gemini2_5ReferenceImageConfig => {
+export const getDefaultRefImageConfig = (getState: AppGetState): IPAdapterConfig | FluxKontextReferenceImageConfig => {
const state = getState();
const mainModelConfig = selectMainModelConfig(state);
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts
index 7bdbc4f2d6f..0ec0cd7baa8 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts
@@ -313,25 +313,6 @@ const zRegionalGuidanceFLUXReduxConfig = z.object({
});
type RegionalGuidanceFLUXReduxConfig = z.infer;
-const zChatGPT4oReferenceImageConfig = z.object({
- type: z.literal('chatgpt_4o_reference_image'),
- image: zCroppableImageWithDims.nullable(),
- /**
- * TODO(psyche): Technically there is no model for ChatGPT 4o reference images - it's just a field in the API call.
- * But we use a model drop down to switch between different ref image types, so there needs to be a model here else
- * there will be no way to switch between ref image types.
- */
- model: zModelIdentifierField.nullable(),
-});
-export type ChatGPT4oReferenceImageConfig = z.infer;
-
-const zGemini2_5ReferenceImageConfig = z.object({
- type: z.literal('gemini_2_5_reference_image'),
- image: zCroppableImageWithDims.nullable(),
- model: zModelIdentifierField.nullable(),
-});
-export type Gemini2_5ReferenceImageConfig = z.infer;
-
const zFluxKontextReferenceImageConfig = z.object({
type: z.literal('flux_kontext_reference_image'),
image: zCroppableImageWithDims.nullable(),
@@ -349,13 +330,7 @@ const zCanvasEntityBase = z.object({
export const zRefImageState = z.object({
id: zId,
isEnabled: z.boolean().default(true),
- config: z.discriminatedUnion('type', [
- zIPAdapterConfig,
- zFLUXReduxConfig,
- zChatGPT4oReferenceImageConfig,
- zFluxKontextReferenceImageConfig,
- zGemini2_5ReferenceImageConfig,
- ]),
+ config: z.discriminatedUnion('type', [zIPAdapterConfig, zFLUXReduxConfig, zFluxKontextReferenceImageConfig]),
});
export type RefImageState = z.infer;
@@ -747,12 +722,7 @@ export const getInitialRefImagesState = (): RefImagesState => ({
export const zCanvasReferenceImageState_OLD = zCanvasEntityBase.extend({
type: z.literal('reference_image'),
- ipAdapter: z.discriminatedUnion('type', [
- zIPAdapterConfig,
- zFLUXReduxConfig,
- zChatGPT4oReferenceImageConfig,
- zGemini2_5ReferenceImageConfig,
- ]),
+ ipAdapter: z.discriminatedUnion('type', [zIPAdapterConfig, zFLUXReduxConfig]),
});
export const zCanvasMetadata = z.object({
diff --git a/invokeai/frontend/web/src/features/queue/components/InvokeButtonTooltip/InvokeButtonTooltip.tsx b/invokeai/frontend/web/src/features/queue/components/InvokeButtonTooltip/InvokeButtonTooltip.tsx
index df6bdc936c3..9f1d004ba87 100644
--- a/invokeai/frontend/web/src/features/queue/components/InvokeButtonTooltip/InvokeButtonTooltip.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/InvokeButtonTooltip/InvokeButtonTooltip.tsx
@@ -50,27 +50,6 @@ const TooltipContent = memo(({ prepend = false }: { prepend?: boolean }) => {
});
TooltipContent.displayName = 'TooltipContent';
-const VideoTabTooltipContent = memo(({ prepend = false }: { prepend?: boolean }) => {
- const isReady = useStore($isReadyToEnqueue);
- const reasons = useStore($reasonsWhyCannotEnqueue);
-
- return (
-
-
-
- {reasons.length > 0 && (
- <>
-
-
- >
- )}
-
-
-
- );
-});
-VideoTabTooltipContent.displayName = 'VideoTabTooltipContent';
-
const CanvasTabTooltipContent = memo(({ prepend = false }: { prepend?: boolean }) => {
const isReady = useStore($isReadyToEnqueue);
const reasons = useStore($reasonsWhyCannotEnqueue);
diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts
index 3254330d81d..d5b1e4672a8 100644
--- a/invokeai/frontend/web/src/services/api/index.ts
+++ b/invokeai/frontend/web/src/services/api/index.ts
@@ -16,7 +16,6 @@ const tagTypes = [
'Board',
'BoardImagesTotal',
'BoardAssetsTotal',
- 'BoardVideosTotal',
'HFTokenStatus',
'Image',
'ImageNameList',
@@ -53,12 +52,6 @@ const tagTypes = [
'StylePreset',
'Schema',
'QueueCountsByDestination',
- 'Video',
- 'VideoMetadata',
- 'VideoList',
- 'VideoIdList',
- 'VideoCollectionCounts',
- 'VideoCollection',
// This is invalidated on reconnect. It should be used for queries that have changing data,
// especially related to the queue and generation.
'FetchOnReconnect',
diff --git a/invokeai/frontend/web/src/services/api/util/tagInvalidation.ts b/invokeai/frontend/web/src/services/api/util/tagInvalidation.ts
index 02d18bf6d3e..477a5a03f87 100644
--- a/invokeai/frontend/web/src/services/api/util/tagInvalidation.ts
+++ b/invokeai/frontend/web/src/services/api/util/tagInvalidation.ts
@@ -4,7 +4,7 @@ import { getListImagesUrl } from 'services/api/util';
import type { ApiTagDescription } from '..';
export const getTagsToInvalidateForBoardAffectingMutation = (affected_boards: string[]): ApiTagDescription[] => {
- const tags: ApiTagDescription[] = ['ImageNameList', 'VideoIdList'];
+ const tags: ApiTagDescription[] = ['ImageNameList'];
for (const board_id of affected_boards) {
tags.push({
diff --git a/tests/model_identification/stripped_models/dc79db49-7f38-4f54-b4f1-e7c521ded481/vocab.json b/tests/model_identification/stripped_models/dc79db49-7f38-4f54-b4f1-e7c521ded481/vocab.json
index 6c49fc63bcb..b05524b241f 100644
--- a/tests/model_identification/stripped_models/dc79db49-7f38-4f54-b4f1-e7c521ded481/vocab.json
+++ b/tests/model_identification/stripped_models/dc79db49-7f38-4f54-b4f1-e7c521ded481/vocab.json
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910
-size 2776833
+oid sha256:01154a4426e6077c8a3f04fca42edb5293bac73a7faed666901f25591ef89182
+size 3383407
From ea71f1d8511bb3597b189c76f04424ca180dcb48 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Mon, 13 Oct 2025 15:42:40 +1100
Subject: [PATCH 05/20] tidy: removing unused code paths 5
---
invokeai/app/api/routers/session_queue.py | 13 +----------
invokeai/app/services/events/events_common.py | 2 --
.../session_queue/session_queue_common.py | 9 --------
.../__mocks__/mockStagingAreaApp.ts | 2 --
.../controlLayers/store/refImagesSlice.ts | 2 +-
.../QueueList/QueueItemComponent.tsx | 7 +-----
.../queue/components/QueueList/constants.ts | 1 -
.../frontend/web/src/services/api/schema.ts | 22 -------------------
.../src/services/events/setEventListeners.tsx | 2 --
9 files changed, 3 insertions(+), 57 deletions(-)
diff --git a/invokeai/app/api/routers/session_queue.py b/invokeai/app/api/routers/session_queue.py
index 5320bf18eb3..7b4242e013c 100644
--- a/invokeai/app/api/routers/session_queue.py
+++ b/invokeai/app/api/routers/session_queue.py
@@ -2,7 +2,7 @@
from fastapi import Body, HTTPException, Path, Query
from fastapi.routing import APIRouter
-from pydantic import BaseModel, Field
+from pydantic import BaseModel
from invokeai.app.api.dependencies import ApiDependencies
from invokeai.app.services.session_processor.session_processor_common import SessionProcessorStatus
@@ -16,7 +16,6 @@
DeleteAllExceptCurrentResult,
DeleteByDestinationResult,
EnqueueBatchResult,
- FieldIdentifier,
ItemIdsResult,
PruneResult,
RetryItemsResult,
@@ -37,12 +36,6 @@ class SessionQueueAndProcessorStatus(BaseModel):
processor: SessionProcessorStatus
-class ValidationRunData(BaseModel):
- workflow_id: str = Field(description="The id of the workflow being published.")
- input_fields: list[FieldIdentifier] = Body(description="The input fields for the published workflow")
- output_fields: list[FieldIdentifier] = Body(description="The output fields for the published workflow")
-
-
@session_queue_router.post(
"/{queue_id}/enqueue_batch",
operation_id="enqueue_batch",
@@ -54,10 +47,6 @@ async def enqueue_batch(
queue_id: str = Path(description="The queue id to perform this operation on"),
batch: Batch = Body(description="Batch to process"),
prepend: bool = Body(default=False, description="Whether or not to prepend this batch in the queue"),
- validation_run_data: Optional[ValidationRunData] = Body(
- default=None,
- description="The validation run data to use for this batch. This is only used if this is a validation run.",
- ),
) -> EnqueueBatchResult:
"""Processes a batch and enqueues the output graphs for execution."""
try:
diff --git a/invokeai/app/services/events/events_common.py b/invokeai/app/services/events/events_common.py
index 2f995293984..d32816f353e 100644
--- a/invokeai/app/services/events/events_common.py
+++ b/invokeai/app/services/events/events_common.py
@@ -241,7 +241,6 @@ class QueueItemStatusChangedEvent(QueueItemEventBase):
batch_status: BatchStatus = Field(description="The status of the batch")
queue_status: SessionQueueStatus = Field(description="The status of the queue")
session_id: str = Field(description="The ID of the session (aka graph execution state)")
- credits: Optional[float] = Field(default=None, description="The total credits used for this queue item")
@classmethod
def build(
@@ -264,7 +263,6 @@ def build(
completed_at=str(queue_item.completed_at) if queue_item.completed_at else None,
batch_status=batch_status,
queue_status=queue_status,
- credits=queue_item.credits,
)
diff --git a/invokeai/app/services/session_queue/session_queue_common.py b/invokeai/app/services/session_queue/session_queue_common.py
index e912753f423..57b512a8558 100644
--- a/invokeai/app/services/session_queue/session_queue_common.py
+++ b/invokeai/app/services/session_queue/session_queue_common.py
@@ -249,15 +249,6 @@ class SessionQueueItem(BaseModel):
retried_from_item_id: Optional[int] = Field(
default=None, description="The item_id of the queue item that this item was retried from"
)
- is_api_validation_run: bool = Field(
- default=False,
- description="Whether this queue item is an API validation run.",
- )
- published_workflow_id: Optional[str] = Field(
- default=None,
- description="The ID of the published workflow associated with this queue item",
- )
- credits: Optional[float] = Field(default=None, description="The total credits used for this queue item")
session: GraphExecutionState = Field(description="The fully-populated session to be executed")
workflow: Optional[WorkflowWithoutID] = Field(
default=None, description="The workflow associated with this queue item"
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/__mocks__/mockStagingAreaApp.ts b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/__mocks__/mockStagingAreaApp.ts
index 15a50ceb115..e0b56d5a439 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/__mocks__/mockStagingAreaApp.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/__mocks__/mockStagingAreaApp.ts
@@ -86,8 +86,6 @@ export const createMockQueueItem = (overrides: PartialDeep {
const isCanceled = useMemo(() => ['canceled', 'completed', 'failed'].includes(item.status), [item.status]);
const isFailed = useMemo(() => ['canceled', 'failed'].includes(item.status), [item.status]);
- const isValidationRun = useMemo(() => item.is_api_validation_run === true, [item.is_api_validation_run]);
const originText = useOriginText(item.origin);
const destinationText = useDestinationText(item.destination);
@@ -113,10 +112,6 @@ const QueueItemComponent = ({ index, item }: InnerItemProps) => {
)}
-
- {isValidationRun && {t('workflows.builder.publishingValidationRun')}}
-
-
{!isFailed && (
diff --git a/invokeai/frontend/web/src/features/queue/components/QueueList/constants.ts b/invokeai/frontend/web/src/features/queue/components/QueueList/constants.ts
index c75e205614c..e996ff7af13 100644
--- a/invokeai/frontend/web/src/features/queue/components/QueueList/constants.ts
+++ b/invokeai/frontend/web/src/features/queue/components/QueueList/constants.ts
@@ -9,6 +9,5 @@ export const COLUMN_WIDTHS = {
fieldValues: 'auto',
createdAt: '9.5rem',
completedAt: '9.5rem',
- validationRun: 'auto',
actions: 'auto',
} as const;
diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts
index 4da374c26f1..fd73c806f04 100644
--- a/invokeai/frontend/web/src/services/api/schema.ts
+++ b/invokeai/frontend/web/src/services/api/schema.ts
@@ -19503,12 +19503,6 @@ export type components = {
* @description The ID of the session (aka graph execution state)
*/
session_id: string;
- /**
- * Credits
- * @description The total credits used for this queue item
- * @default null
- */
- credits: number | null;
};
/**
* QueueItemsRetriedEvent
@@ -21285,22 +21279,6 @@ export type components = {
* @description The item_id of the queue item that this item was retried from
*/
retried_from_item_id?: number | null;
- /**
- * Is Api Validation Run
- * @description Whether this queue item is an API validation run.
- * @default false
- */
- is_api_validation_run?: boolean;
- /**
- * Published Workflow Id
- * @description The ID of the published workflow associated with this queue item
- */
- published_workflow_id?: string | null;
- /**
- * Credits
- * @description The total credits used for this queue item
- */
- credits?: number | null;
/** @description The fully-populated session to be executed */
session: components["schemas"]["GraphExecutionState"];
/** @description The workflow associated with this queue item */
diff --git a/invokeai/frontend/web/src/services/events/setEventListeners.tsx b/invokeai/frontend/web/src/services/events/setEventListeners.tsx
index 49876cee93b..d9a1d8386c1 100644
--- a/invokeai/frontend/web/src/services/events/setEventListeners.tsx
+++ b/invokeai/frontend/web/src/services/events/setEventListeners.tsx
@@ -365,7 +365,6 @@ export const setEventListeners = ({ socket, store, setIsConnected }: SetEventLis
updated_at,
completed_at,
error_traceback,
- credits,
} = data;
log.debug({ data }, `Queue item ${item_id} status updated: ${status}`);
@@ -380,7 +379,6 @@ export const setEventListeners = ({ socket, store, setIsConnected }: SetEventLis
draft.error_type = error_type;
draft.error_message = error_message;
draft.error_traceback = error_traceback;
- draft.credits = credits;
})
);
From 2810c47c785ac1e5468a730b6cbe97535158f141 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Mon, 13 Oct 2025 15:55:04 +1100
Subject: [PATCH 06/20] tidy: removing unused code paths 6
---
invokeai/app/api/routers/boards.py | 1 -
.../board_records/board_records_common.py | 4 --
invokeai/app/services/events/events_common.py | 4 --
.../style_preset_records_common.py | 1 -
.../components/Boards/DeleteBoardModal.tsx | 8 +--
.../web/src/features/nodes/store/types.ts | 2 +-
.../src/features/nodes/types/v2/workflow.ts | 2 +-
.../components/StylePresetMenu.tsx | 2 -
.../web/src/services/api/endpoints/boards.ts | 4 +-
.../frontend/web/src/services/api/schema.ts | 65 +------------------
10 files changed, 6 insertions(+), 87 deletions(-)
diff --git a/invokeai/app/api/routers/boards.py b/invokeai/app/api/routers/boards.py
index ec3b86bcfa2..cf668d5a1a4 100644
--- a/invokeai/app/api/routers/boards.py
+++ b/invokeai/app/api/routers/boards.py
@@ -33,7 +33,6 @@ class DeleteBoardResult(BaseModel):
)
async def create_board(
board_name: str = Query(description="The name of the board to create", max_length=300),
- is_private: bool = Query(default=False, description="Whether the board is private"),
) -> BoardDTO:
"""Creates a board"""
try:
diff --git a/invokeai/app/services/board_records/board_records_common.py b/invokeai/app/services/board_records/board_records_common.py
index 81d05d7f597..5067d42999b 100644
--- a/invokeai/app/services/board_records/board_records_common.py
+++ b/invokeai/app/services/board_records/board_records_common.py
@@ -26,8 +26,6 @@ class BoardRecord(BaseModelExcludeNull):
"""The name of the cover image of the board."""
archived: bool = Field(description="Whether or not the board is archived.")
"""Whether or not the board is archived."""
- is_private: Optional[bool] = Field(default=None, description="Whether the board is private.")
- """Whether the board is private."""
def deserialize_board_record(board_dict: dict) -> BoardRecord:
@@ -42,7 +40,6 @@ def deserialize_board_record(board_dict: dict) -> BoardRecord:
updated_at = board_dict.get("updated_at", get_iso_timestamp())
deleted_at = board_dict.get("deleted_at", get_iso_timestamp())
archived = board_dict.get("archived", False)
- is_private = board_dict.get("is_private", False)
return BoardRecord(
board_id=board_id,
@@ -52,7 +49,6 @@ def deserialize_board_record(board_dict: dict) -> BoardRecord:
updated_at=updated_at,
deleted_at=deleted_at,
archived=archived,
- is_private=is_private,
)
diff --git a/invokeai/app/services/events/events_common.py b/invokeai/app/services/events/events_common.py
index d32816f353e..a924f2eed9f 100644
--- a/invokeai/app/services/events/events_common.py
+++ b/invokeai/app/services/events/events_common.py
@@ -195,8 +195,6 @@ class InvocationErrorEvent(InvocationEventBase):
error_type: str = Field(description="The error type")
error_message: str = Field(description="The error message")
error_traceback: str = Field(description="The error traceback")
- user_id: Optional[str] = Field(default=None, description="The ID of the user who created the invocation")
- project_id: Optional[str] = Field(default=None, description="The ID of the user who created the invocation")
@classmethod
def build(
@@ -219,8 +217,6 @@ def build(
error_type=error_type,
error_message=error_message,
error_traceback=error_traceback,
- user_id=getattr(queue_item, "user_id", None),
- project_id=getattr(queue_item, "project_id", None),
)
diff --git a/invokeai/app/services/style_preset_records/style_preset_records_common.py b/invokeai/app/services/style_preset_records/style_preset_records_common.py
index 36153d002d0..9ea0b0219cf 100644
--- a/invokeai/app/services/style_preset_records/style_preset_records_common.py
+++ b/invokeai/app/services/style_preset_records/style_preset_records_common.py
@@ -26,7 +26,6 @@ class PresetData(BaseModel, extra="forbid"):
class PresetType(str, Enum, metaclass=MetaEnum):
User = "user"
Default = "default"
- Project = "project"
class StylePresetChanges(BaseModel, extra="forbid"):
diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx
index 47d59540f1d..b7d99301e7a 100644
--- a/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx
@@ -151,13 +151,7 @@ const DeleteBoardModal = () => {
bottomMessage={t('boards.bottomMessage')}
/>
)}
- {boardToDelete !== 'none' && (
-
- {boardToDelete.is_private
- ? t('boards.deletedPrivateBoardsCannotbeRestored')
- : t('boards.deletedBoardsCannotbeRestored')}
-
- )}
+ {boardToDelete !== 'none' && {t('boards.deletedBoardsCannotbeRestored')}}
{t('gallery.deleteImagePermanent')}
diff --git a/invokeai/frontend/web/src/features/nodes/store/types.ts b/invokeai/frontend/web/src/features/nodes/store/types.ts
index a3391d7daec..587fbfdd7b9 100644
--- a/invokeai/frontend/web/src/features/nodes/store/types.ts
+++ b/invokeai/frontend/web/src/features/nodes/store/types.ts
@@ -21,6 +21,6 @@ export const zNodesState = z.object({
nodes: z.array(zAnyNode),
edges: z.array(zAnyEdge),
formFieldInitialValues: z.record(z.string(), zStatefulFieldValue),
- ...zWorkflowV3.omit({ nodes: true, edges: true, is_published: true }).shape,
+ ...zWorkflowV3.omit({ nodes: true, edges: true }).shape,
});
export type NodesState = z.infer;
diff --git a/invokeai/frontend/web/src/features/nodes/types/v2/workflow.ts b/invokeai/frontend/web/src/features/nodes/types/v2/workflow.ts
index 511a28cdc3b..94300d034a2 100644
--- a/invokeai/frontend/web/src/features/nodes/types/v2/workflow.ts
+++ b/invokeai/frontend/web/src/features/nodes/types/v2/workflow.ts
@@ -13,7 +13,7 @@ const zXYPosition = z
const zDimension = z.number().gt(0).nullish();
-const zWorkflowCategory = z.enum(['user', 'default', 'project']);
+const zWorkflowCategory = z.enum(['user', 'default']);
// #endregion
// #region Workflow Nodes
diff --git a/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenu.tsx b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenu.tsx
index f6ba71b0b90..c42249d6114 100644
--- a/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenu.tsx
+++ b/invokeai/frontend/web/src/features/stylePresets/components/StylePresetMenu.tsx
@@ -31,8 +31,6 @@ export const StylePresetMenu = () => {
) => {
if (preset.type === 'default') {
acc.defaultPresets.push(preset);
- } else if (preset.type === 'project') {
- acc.sharedPresets.push(preset);
} else {
acc.presets.push(preset);
}
diff --git a/invokeai/frontend/web/src/services/api/endpoints/boards.ts b/invokeai/frontend/web/src/services/api/endpoints/boards.ts
index 59211439a45..9b7a4f2ad8a 100644
--- a/invokeai/frontend/web/src/services/api/endpoints/boards.ts
+++ b/invokeai/frontend/web/src/services/api/endpoints/boards.ts
@@ -100,10 +100,10 @@ export const boardsApi = api.injectEndpoints({
*/
createBoard: build.mutation({
- query: ({ board_name, is_private }) => ({
+ query: ({ board_name }) => ({
url: buildBoardsUrl(),
method: 'POST',
- params: { board_name, is_private },
+ params: { board_name },
}),
invalidatesTags: [{ type: 'Board', id: LIST_TAG }],
}),
diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts
index fd73c806f04..db4ba519cad 100644
--- a/invokeai/frontend/web/src/services/api/schema.ts
+++ b/invokeai/frontend/web/src/services/api/schema.ts
@@ -2494,11 +2494,6 @@ export type components = {
* @description Whether or not the board is archived.
*/
archived: boolean;
- /**
- * Is Private
- * @description Whether the board is private.
- */
- is_private?: boolean | null;
/**
* Image Count
* @description The number of images in the board.
@@ -2661,8 +2656,6 @@ export type components = {
* @default false
*/
prepend?: boolean;
- /** @description The validation run data to use for this batch. This is only used if this is a validation run. */
- validation_run_data?: components["schemas"]["ValidationRunData"] | null;
};
/** Body_get_images_by_names */
Body_get_images_by_names: {
@@ -7460,30 +7453,6 @@ export type components = {
*/
y: number;
};
- /** FieldIdentifier */
- FieldIdentifier: {
- /**
- * Kind
- * @description The kind of field
- * @enum {string}
- */
- kind: "input" | "output";
- /**
- * Node Id
- * @description The ID of the node
- */
- node_id: string;
- /**
- * Field Name
- * @description The name of the field
- */
- field_name: string;
- /**
- * User Label
- * @description The user label of the field, if any
- */
- user_label: string | null;
- };
/**
* FieldKind
* @description The kind of field.
@@ -12492,18 +12461,6 @@ export type components = {
* @description The error traceback
*/
error_traceback: string;
- /**
- * User Id
- * @description The ID of the user who created the invocation
- * @default null
- */
- user_id: string | null;
- /**
- * Project Id
- * @description The ID of the user who created the invocation
- * @default null
- */
- project_id: string | null;
};
InvocationOutputMap: {
add: components["schemas"]["IntegerOutput"];
@@ -19303,7 +19260,7 @@ export type components = {
* PresetType
* @enum {string}
*/
- PresetType: "user" | "default" | "project";
+ PresetType: "user" | "default";
/**
* ProgressImage
* @description The progress image sent intermittently during processing
@@ -24164,24 +24121,6 @@ export type components = {
/** Error Type */
type: string;
};
- /** ValidationRunData */
- ValidationRunData: {
- /**
- * Workflow Id
- * @description The id of the workflow being published.
- */
- workflow_id: string;
- /**
- * Input Fields
- * @description The input fields for the published workflow
- */
- input_fields: components["schemas"]["FieldIdentifier"][];
- /**
- * Output Fields
- * @description The output fields for the published workflow
- */
- output_fields: components["schemas"]["FieldIdentifier"][];
- };
/** Workflow */
Workflow: {
/**
@@ -26401,8 +26340,6 @@ export interface operations {
query: {
/** @description The name of the board to create */
board_name: string;
- /** @description Whether the board is private */
- is_private?: boolean;
};
header?: never;
path?: never;
From 373718fc4b66b0f93ab9bed7807e3c5c91048cf4 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Mon, 13 Oct 2025 16:12:15 +1100
Subject: [PATCH 07/20] tidy: removing unused code paths 7
---
README.md | 28 ++++++-------------
invokeai/frontend/web/public/locales/en.json | 7 +----
.../InformationalPopover/constants.ts | 5 ----
.../components/AboutModal/AboutModal.tsx | 3 +-
.../components/SettingsModal/SettingsMenu.tsx | 16 +----------
.../SettingsModal/SettingsUpsellMenuItem.tsx | 19 -------------
.../src/features/system/store/constants.ts | 1 -
7 files changed, 11 insertions(+), 68 deletions(-)
delete mode 100644 invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsUpsellMenuItem.tsx
diff --git a/README.md b/README.md
index 2a23dfc8541..0465e5ba792 100644
--- a/README.md
+++ b/README.md
@@ -4,38 +4,27 @@
# Invoke - Professional Creative AI Tools for Visual Media
-#### To learn more about Invoke, or implement our Business solutions, visit [invoke.com]
-
[![discord badge]][discord link] [![latest release badge]][latest release link] [![github stars badge]][github stars link] [![github forks badge]][github forks link] [![CI checks on main badge]][CI checks on main link] [![latest commit to main badge]][latest commit to main link] [![github open issues badge]][github open issues link] [![github open prs badge]][github open prs link] [![translation status badge]][translation status link]
Invoke is a leading creative engine built to empower professionals and enthusiasts alike. Generate and create stunning visual media using the latest AI-driven technologies. Invoke offers an industry leading web-based UI, and serves as the foundation for multiple commercial products.
-Invoke is available in two editions:
-
-| **Community Edition** | **Professional Edition** |
-|----------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
-| **For users looking for a locally installed, self-hosted and self-managed service** | **For users or teams looking for a cloud-hosted, fully managed service** |
-| - Free to use under a commercially-friendly license | - Monthly subscription fee with three different plan levels |
-| - Download and install on compatible hardware | - Offers additional benefits, including multi-user support, improved model training, and more |
-| - Includes all core studio features: generate, refine, iterate on images, and build workflows | - Hosted in the cloud for easy, secure model access and scalability |
-| Quick Start -> [Installation and Updates][installation docs] | More Information -> [www.invoke.com/pricing](https://www.invoke.com/pricing) |
-
+- Free to use under a commercially-friendly license
+- Download and install on compatible hardware
+- Generate, refine, iterate on images, and build workflows

# Documentation
-| **Quick Links** |
-|----------------------------------------------------------------------------------------------------------------------------|
-| [Installation and Updates][installation docs] - [Documentation and Tutorials][docs home] - [Bug Reports][github issues] - [Contributing][contributing docs] |
-# Installation
-
-To get started with Invoke, [Download the Installer](https://www.invoke.com/downloads).
+| **Quick Links** |
+| ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [Installation and Updates][installation docs] - [Documentation and Tutorials][docs home] - [Bug Reports][github issues] - [Contributing][contributing docs] |
-For detailed step by step instructions, or for instructions on manual/docker installations, visit our documentation on [Installation and Updates][installation docs]
+# Installation
+To get started with Invoke, [Download the Launcher](https://github.com/invoke-ai/launcher/releases/latest).
## Troubleshooting, FAQ and Support
@@ -90,7 +79,6 @@ Original portions of the software are Copyright © 2024 by respective contributo
[features docs]: https://invoke-ai.github.io/InvokeAI/features/database/
[faq]: https://invoke-ai.github.io/InvokeAI/faq/
[contributors]: https://invoke-ai.github.io/InvokeAI/contributing/contributors/
-[invoke.com]: https://www.invoke.com/about
[github issues]: https://github.com/invoke-ai/InvokeAI/issues
[docs home]: https://invoke-ai.github.io/InvokeAI
[installation docs]: https://invoke-ai.github.io/InvokeAI/installation/
diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index d88ba697c58..0343e1af19b 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -2612,12 +2612,7 @@
"viewModeTooltip": "This is how your prompt will look with your currently selected template. To edit your prompt, click anywhere in the text box.",
"togglePromptPreviews": "Toggle Prompt Previews"
},
- "upsell": {
- "inviteTeammates": "Invite Teammates",
- "professional": "Professional",
- "professionalUpsell": "Available in Invoke's Professional Edition. Click here or visit invoke.com/pricing for more details.",
- "shareAccess": "Share Access"
- },
+
"ui": {
"tabs": {
"generate": "Generate",
diff --git a/invokeai/frontend/web/src/common/components/InformationalPopover/constants.ts b/invokeai/frontend/web/src/common/components/InformationalPopover/constants.ts
index 22a813b6de1..6db4dcbd682 100644
--- a/invokeai/frontend/web/src/common/components/InformationalPopover/constants.ts
+++ b/invokeai/frontend/web/src/common/components/InformationalPopover/constants.ts
@@ -1,5 +1,4 @@
import type { PopoverProps } from '@invoke-ai/ui-library';
-import commercialLicenseBg from 'public/assets/images/commercial-license-bg.png';
import denoisingStrength from 'public/assets/images/denoising-strength.png';
export type Feature =
@@ -217,10 +216,6 @@ export const POPOVER_DATA: { [key in Feature]?: PopoverData } = {
seamlessTilingYAxis: {
href: 'https://support.invoke.ai/support/solutions/articles/151000178161-advanced-settings',
},
- fluxDevLicense: {
- href: 'https://www.invoke.com/get-a-commercial-license-for-flux',
- image: commercialLicenseBg,
- },
} as const;
export const OPEN_DELAY = 1000; // in milliseconds
diff --git a/invokeai/frontend/web/src/features/system/components/AboutModal/AboutModal.tsx b/invokeai/frontend/web/src/features/system/components/AboutModal/AboutModal.tsx
index bb2eb14a687..dd729f067c2 100644
--- a/invokeai/frontend/web/src/features/system/components/AboutModal/AboutModal.tsx
+++ b/invokeai/frontend/web/src/features/system/components/AboutModal/AboutModal.tsx
@@ -17,7 +17,7 @@ import {
} from '@invoke-ai/ui-library';
import { deepClone } from 'common/util/deepClone';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
-import { discordLink, githubLink, websiteLink } from 'features/system/store/constants';
+import { discordLink, githubLink } from 'features/system/store/constants';
import InvokeLogoYellow from 'public/assets/images/invoke-tag-lrg.svg';
import type { ReactElement } from 'react';
import { cloneElement, memo, useMemo } from 'react';
@@ -82,7 +82,6 @@ const AboutModal = ({ children }: AboutModalProps) => {
{t('common.aboutHeading')}
{t('common.aboutDesc')}
-
diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx
index ddddd79cc51..bbd7103d6c2 100644
--- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx
+++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsMenu.tsx
@@ -14,19 +14,10 @@ import HotkeysModal from 'features/system/components/HotkeysModal/HotkeysModal';
import { discordLink, githubLink } from 'features/system/store/constants';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
-import {
- PiBugBeetleBold,
- PiGearSixFill,
- PiInfoBold,
- PiKeyboardBold,
- PiShareNetworkFill,
- PiToggleRightFill,
- PiUsersBold,
-} from 'react-icons/pi';
+import { PiBugBeetleBold, PiGearSixFill, PiInfoBold, PiKeyboardBold, PiToggleRightFill } from 'react-icons/pi';
import { RiDiscordFill, RiGithubFill } from 'react-icons/ri';
import SettingsModal from './SettingsModal';
-import { SettingsUpsellMenuItem } from './SettingsUpsellMenuItem';
const SettingsMenu = () => {
const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure();
@@ -43,11 +34,6 @@ const SettingsMenu = () => {
/>
-
- } />
- } />
-
-
}>
{t('common.githubLabel')}
diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsUpsellMenuItem.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsUpsellMenuItem.tsx
deleted file mode 100644
index c5f9a13c2b3..00000000000
--- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsUpsellMenuItem.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Flex, Icon, MenuItem, Text, Tooltip } from '@invoke-ai/ui-library';
-import type { ReactElement } from 'react';
-import { useTranslation } from 'react-i18next';
-import { PiArrowUpBold } from 'react-icons/pi';
-
-export const SettingsUpsellMenuItem = ({ menuText, menuIcon }: { menuText: string; menuIcon: ReactElement }) => {
- const { t } = useTranslation();
-
- return (
-
-
-
- );
-};
diff --git a/invokeai/frontend/web/src/features/system/store/constants.ts b/invokeai/frontend/web/src/features/system/store/constants.ts
index 0ca2d24129e..882609e0ac7 100644
--- a/invokeai/frontend/web/src/features/system/store/constants.ts
+++ b/invokeai/frontend/web/src/features/system/store/constants.ts
@@ -1,4 +1,3 @@
export const githubLink = 'http://github.com/invoke-ai/InvokeAI';
export const githubIssuesLink = 'https://github.com/invoke-ai/InvokeAI/issues';
export const discordLink = 'https://discord.gg/ZmtBAhwWhy';
-export const websiteLink = 'https://www.invoke.com/';
From 35f542529198e8aebe52670e706c8e26d0dafac2 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Tue, 14 Oct 2025 11:09:56 +1100
Subject: [PATCH 08/20] tidy(ui): lift error boundary reset cb outside
component
---
invokeai/frontend/web/src/app/components/App.tsx | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx
index bae4f4cf633..1c1af39a000 100644
--- a/invokeai/frontend/web/src/app/components/App.tsx
+++ b/invokeai/frontend/web/src/app/components/App.tsx
@@ -3,22 +3,22 @@ import { GlobalHookIsolator } from 'app/components/GlobalHookIsolator';
import { GlobalModalIsolator } from 'app/components/GlobalModalIsolator';
import { clearStorage } from 'app/store/enhancers/reduxRemember/driver';
import { AppContent } from 'features/ui/components/AppContent';
-import { memo, useCallback } from 'react';
+import { memo } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import AppErrorBoundaryFallback from './AppErrorBoundaryFallback';
import ThemeLocaleProvider from './ThemeLocaleProvider';
-const App = () => {
- const handleReset = useCallback(() => {
- clearStorage();
- location.reload();
- return false;
- }, []);
+const errorBoundaryOnReset = () => {
+ clearStorage();
+ location.reload();
+ return false;
+};
+const App = () => {
return (
-
+
From 2ba8a9818e5f4e0dcecb152b7145b538097ca3a0 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Tue, 14 Oct 2025 11:13:54 +1100
Subject: [PATCH 09/20] docs(ui): add comments for nes
---
.../nodes/hooks/useNodeExecutionState.ts | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useNodeExecutionState.ts b/invokeai/frontend/web/src/features/nodes/hooks/useNodeExecutionState.ts
index 1b854015f53..8c6fd3ff016 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useNodeExecutionState.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useNodeExecutionState.ts
@@ -9,6 +9,14 @@ import { zNodeStatus } from 'features/nodes/types/invocation';
import { map } from 'nanostores';
import { useEffect, useMemo } from 'react';
+/**
+ * A nanostore that holds the ephemeral execution state of nodes in the graph. The execution state includes
+ * the status, error, progress, progress image, and outputs of each node.
+ *
+ * Note that, because a node can be duplicated by an iterate node, it can have multiple outputs recorded, one for each
+ * iteration. For example, consider a collection of 3 images that are passed to an iterate node, which then passes each
+ * image to a resize node. The resize node will have 3 outputs - one for each image.
+ */
export const $nodeExecutionStates = map({});
const initialNodeExecutionState: Omit = {
@@ -40,6 +48,14 @@ export const upsertExecutionState = (nodeId: string, updates?: Partial nodes.map((node) => node.id));
+/**
+ * Keeps the ephemeral store of node execution states in sync with the nodes in the graph.
+ *
+ * For example, if a node is deleted from the graph, its execution state is removed from the store, and
+ * if a new node is added to the graph, an initial execution state is added to the store.
+ *
+ * Node execution states are stored in $nodeExecutionStates nanostore.
+ */
export const useSyncExecutionState = () => {
const nodeIds = useAppSelector(selectNodeIds);
useEffect(() => {
From 2a4babf52b5b556bd8607735a1e8af9609522604 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Tue, 14 Oct 2025 11:19:03 +1100
Subject: [PATCH 10/20] docs(ui): add comments for image context menu
---
.../components/ContextMenu/ImageContextMenu.tsx | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/ImageContextMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/ImageContextMenu.tsx
index 79e75af7e32..636c090a754 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/ImageContextMenu.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/ImageContextMenu.tsx
@@ -11,6 +11,18 @@ import type { RefObject } from 'react';
import { memo, useCallback, useEffect, useRef } from 'react';
import type { ImageDTO } from 'services/api/types';
+/**
+ * The context menu is loosely based on https://github.com/lukasbach/chakra-ui-contextmenu.
+ *
+ * That library creates a component for _every_ instance of a thing that needed a context menu, which caused perf
+ * issues. This implementation uses a singleton pattern instead, with a single component that listens for context menu
+ * events and opens the menu as needed.
+ *
+ * Images register themselves with the context menu by mapping their DOM element to their image DTO. When a context
+ * menu event is fired, we look up the target element in the map (or its parents) to find the image DTO to show the
+ * context menu for.
+ */
+
/**
* The delay in milliseconds before the context menu opens on long press.
*/
From 709c9e7f897c96ca0fdf45642f6a88e99e206875 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Tue, 14 Oct 2025 11:20:11 +1100
Subject: [PATCH 11/20] tidy(ui): rename GalleryPanel file
---
.../gallery/components/{Gallery.tsx => GalleryPanel.tsx} | 0
.../web/src/features/ui/layouts/canvas-tab-auto-layout.tsx | 2 +-
.../web/src/features/ui/layouts/generate-tab-auto-layout.tsx | 2 +-
.../web/src/features/ui/layouts/upscaling-tab-auto-layout.tsx | 2 +-
.../web/src/features/ui/layouts/workflows-tab-auto-layout.tsx | 2 +-
5 files changed, 4 insertions(+), 4 deletions(-)
rename invokeai/frontend/web/src/features/gallery/components/{Gallery.tsx => GalleryPanel.tsx} (100%)
diff --git a/invokeai/frontend/web/src/features/gallery/components/Gallery.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryPanel.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/gallery/components/Gallery.tsx
rename to invokeai/frontend/web/src/features/gallery/components/GalleryPanel.tsx
diff --git a/invokeai/frontend/web/src/features/ui/layouts/canvas-tab-auto-layout.tsx b/invokeai/frontend/web/src/features/ui/layouts/canvas-tab-auto-layout.tsx
index b622b77e4de..e2cbfe2c5d2 100644
--- a/invokeai/frontend/web/src/features/ui/layouts/canvas-tab-auto-layout.tsx
+++ b/invokeai/frontend/web/src/features/ui/layouts/canvas-tab-auto-layout.tsx
@@ -2,7 +2,7 @@ import type { DockviewApi, GridviewApi, IDockviewReactProps, IGridviewReactProps
import { DockviewReact, GridviewReact, LayoutPriority, Orientation } from 'dockview';
import { CanvasLayersPanel } from 'features/controlLayers/components/CanvasLayersPanelContent';
import { BoardsPanel } from 'features/gallery/components/BoardsListPanelContent';
-import { GalleryPanel } from 'features/gallery/components/Gallery';
+import { GalleryPanel } from 'features/gallery/components/GalleryPanel';
import { ImageViewerPanel } from 'features/gallery/components/ImageViewer/ImageViewerPanel';
import { FloatingCanvasLeftPanelButtons } from 'features/ui/components/FloatingLeftPanelButtons';
import { FloatingRightPanelButtons } from 'features/ui/components/FloatingRightPanelButtons';
diff --git a/invokeai/frontend/web/src/features/ui/layouts/generate-tab-auto-layout.tsx b/invokeai/frontend/web/src/features/ui/layouts/generate-tab-auto-layout.tsx
index 81cf2885474..e60c15b5da3 100644
--- a/invokeai/frontend/web/src/features/ui/layouts/generate-tab-auto-layout.tsx
+++ b/invokeai/frontend/web/src/features/ui/layouts/generate-tab-auto-layout.tsx
@@ -1,7 +1,7 @@
import type { DockviewApi, GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
import { DockviewReact, GridviewReact, LayoutPriority, Orientation } from 'dockview';
import { BoardsPanel } from 'features/gallery/components/BoardsListPanelContent';
-import { GalleryPanel } from 'features/gallery/components/Gallery';
+import { GalleryPanel } from 'features/gallery/components/GalleryPanel';
import { ImageViewerPanel } from 'features/gallery/components/ImageViewer/ImageViewerPanel';
import { FloatingLeftPanelButtons } from 'features/ui/components/FloatingLeftPanelButtons';
import { FloatingRightPanelButtons } from 'features/ui/components/FloatingRightPanelButtons';
diff --git a/invokeai/frontend/web/src/features/ui/layouts/upscaling-tab-auto-layout.tsx b/invokeai/frontend/web/src/features/ui/layouts/upscaling-tab-auto-layout.tsx
index 7820187119c..e4f443148ff 100644
--- a/invokeai/frontend/web/src/features/ui/layouts/upscaling-tab-auto-layout.tsx
+++ b/invokeai/frontend/web/src/features/ui/layouts/upscaling-tab-auto-layout.tsx
@@ -1,7 +1,7 @@
import type { DockviewApi, GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
import { DockviewReact, GridviewReact, LayoutPriority, Orientation } from 'dockview';
import { BoardsPanel } from 'features/gallery/components/BoardsListPanelContent';
-import { GalleryPanel } from 'features/gallery/components/Gallery';
+import { GalleryPanel } from 'features/gallery/components/GalleryPanel';
import { ImageViewerPanel } from 'features/gallery/components/ImageViewer/ImageViewerPanel';
import { FloatingLeftPanelButtons } from 'features/ui/components/FloatingLeftPanelButtons';
import { FloatingRightPanelButtons } from 'features/ui/components/FloatingRightPanelButtons';
diff --git a/invokeai/frontend/web/src/features/ui/layouts/workflows-tab-auto-layout.tsx b/invokeai/frontend/web/src/features/ui/layouts/workflows-tab-auto-layout.tsx
index 06ae423e447..026b7897283 100644
--- a/invokeai/frontend/web/src/features/ui/layouts/workflows-tab-auto-layout.tsx
+++ b/invokeai/frontend/web/src/features/ui/layouts/workflows-tab-auto-layout.tsx
@@ -1,7 +1,7 @@
import type { DockviewApi, GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
import { DockviewReact, GridviewReact, LayoutPriority, Orientation } from 'dockview';
import { BoardsPanel } from 'features/gallery/components/BoardsListPanelContent';
-import { GalleryPanel } from 'features/gallery/components/Gallery';
+import { GalleryPanel } from 'features/gallery/components/GalleryPanel';
import { ImageViewerPanel } from 'features/gallery/components/ImageViewer/ImageViewerPanel';
import NodeEditor from 'features/nodes/components/NodeEditor';
import WorkflowsTabLeftPanel from 'features/nodes/components/sidePanel/WorkflowsTabLeftPanel';
From 8f08051f1fdb32d54e695da1c7da7110ddbd0a58 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Tue, 14 Oct 2025 11:32:33 +1100
Subject: [PATCH 12/20] tidy(ui): rename GalleryImageGrid and add comments
---
.../{NewGallery.tsx => GalleryImageGrid.tsx} | 25 +++++++++++++++++--
.../gallery/components/GalleryPanel.tsx | 4 +--
2 files changed, 25 insertions(+), 4 deletions(-)
rename invokeai/frontend/web/src/features/gallery/components/{NewGallery.tsx => GalleryImageGrid.tsx} (90%)
diff --git a/invokeai/frontend/web/src/features/gallery/components/NewGallery.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryImageGrid.tsx
similarity index 90%
rename from invokeai/frontend/web/src/features/gallery/components/NewGallery.tsx
rename to invokeai/frontend/web/src/features/gallery/components/GalleryImageGrid.tsx
index d586dc979d9..655e7551ded 100644
--- a/invokeai/frontend/web/src/features/gallery/components/NewGallery.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/GalleryImageGrid.tsx
@@ -42,6 +42,27 @@ type GridContext = {
imageNames: string[];
};
+/**
+ * The gallery uses a windowed list to only render the images that are currently visible in the viewport. It starts by
+ * loading a list of all image names for the selected board or view settings. react-virtuoso reports on the currently-
+ * visible range of images (plus some "overscan"). We then fetch the full image DTOs only for those images, which are
+ * cached by RTK Query. As the user scrolls, the visible range changes and we fetch more image DTOs as needed.
+ *
+ * This affords a nice UX, where the user can scroll to any part of their gallery. The scrollbar size never changes.
+ *
+ * We used other approaches in the past:
+ * - Infinite scroll: Load an initial chunk of images, then load more as the user scrolls to the bottom. The scrollbar
+ * continually shrinks as more images are loaded. This is a poor UX, as the user cannot easily scroll to a specific
+ * part of their gallery. It's also pretty complicated to implement within RTK Query, though since we switched, RTK
+ * Query now supports infinite queries. It might be easier to do this today.
+ * - Traditional pagination: Show a fixed number of images per page, with pagination controls. This is a poor UX,
+ * as the user cannot easily scroll to a specific part of their gallery. Gallerys are often very large, and the page
+ * size changes depending on the viewport size.
+ */
+
+/**
+ * Wraps an image - either the placeholder as it is being loaded or the loaded image
+ */
const ImageAtPosition = memo(({ imageName }: { index: number; imageName: string }) => {
/*
* We rely on the useRangeBasedImageFetching to fetch all image DTOs, caching them with RTK Query.
@@ -307,7 +328,7 @@ const useStarImageHotkey = () => {
});
};
-export const ImageGallery = memo(() => {
+export const GalleryImageGrid = memo(() => {
const virtuosoRef = useRef(null);
const rangeRef = useRef({ startIndex: 0, endIndex: 0 });
const rootRef = useRef(null);
@@ -378,7 +399,7 @@ export const ImageGallery = memo(() => {
);
});
-ImageGallery.displayName = 'NewGallery';
+GalleryImageGrid.displayName = 'GalleryImageGrid';
const scrollSeekConfiguration: ScrollSeekConfiguration = {
enter: (velocity) => {
diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryPanel.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryPanel.tsx
index 3864094604d..874561e2048 100644
--- a/invokeai/frontend/web/src/features/gallery/components/GalleryPanel.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/GalleryPanel.tsx
@@ -14,10 +14,10 @@ import { useTranslation } from 'react-i18next';
import { PiCaretDownBold, PiCaretUpBold, PiMagnifyingGlassBold } from 'react-icons/pi';
import { useBoardName } from 'services/api/hooks/useBoardName';
+import { GalleryImageGrid } from './GalleryImageGrid';
import { GallerySettingsPopover } from './GallerySettingsPopover/GallerySettingsPopover';
import { GalleryUploadButton } from './GalleryUploadButton';
import { GallerySearch } from './ImageGrid/GallerySearch';
-import { ImageGallery } from './NewGallery';
const COLLAPSE_STYLES: CSSProperties = { flexShrink: 0, minHeight: 0, width: '100%' };
@@ -110,7 +110,7 @@ export const GalleryPanel = memo(() => {
-
+
);
From 3eec5daa5fd7d054655c834003035c733c06de61 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Tue, 14 Oct 2025 15:33:10 +1100
Subject: [PATCH 13/20] docs(ui): add high-level readmes for various features
---
.../web/src/features/cropper/README.md | 11 +++
.../src/features/deleteImageModal/README.md | 7 ++
.../frontend/web/src/features/dnd/README.md | 41 ++++++++++
.../web/src/features/dynamicPrompts/README.md | 11 +++
.../web/src/features/gallery/README.md | 77 +++++++++++++++++++
.../ContextMenu/ImageContextMenu.tsx | 12 ---
.../gallery/components/ContextMenu/README.md | 18 +++++
.../gallery/components/GalleryImageGrid.tsx | 18 -----
.../web/src/features/imageActions/README.md | 5 ++
9 files changed, 170 insertions(+), 30 deletions(-)
create mode 100644 invokeai/frontend/web/src/features/cropper/README.md
create mode 100644 invokeai/frontend/web/src/features/deleteImageModal/README.md
create mode 100644 invokeai/frontend/web/src/features/dnd/README.md
create mode 100644 invokeai/frontend/web/src/features/dynamicPrompts/README.md
create mode 100644 invokeai/frontend/web/src/features/gallery/README.md
create mode 100644 invokeai/frontend/web/src/features/gallery/components/ContextMenu/README.md
create mode 100644 invokeai/frontend/web/src/features/imageActions/README.md
diff --git a/invokeai/frontend/web/src/features/cropper/README.md b/invokeai/frontend/web/src/features/cropper/README.md
new file mode 100644
index 00000000000..0903dab7cfc
--- /dev/null
+++ b/invokeai/frontend/web/src/features/cropper/README.md
@@ -0,0 +1,11 @@
+# Image cropper
+
+This is a simple image cropping canvas app built with KonvaJS ("native" Konva, _not_ the react bindings).
+
+The editor implementation is here: invokeai/frontend/web/src/features/cropper/lib/editor.ts
+
+It is rendered in a modal.
+
+Currently, the crop functionality is only exposed for reference images. These are the kind of images that most often need cropping (i.e. for FLUX Kontext, which is sensitive to the size/aspect ratio of its ref images). All ref image state is enriched to include a ref to the original image, the cropped image, and the crop attributes.
+
+The functionality could be extended to all images in the future, but there are some questions around whether we consider gallery images immutable. If so, we can't crop them in place. Do we instead add a new cropped image to the gallery? Or do we add a field to the image metadata that points to a cropped version of the image?
diff --git a/invokeai/frontend/web/src/features/deleteImageModal/README.md b/invokeai/frontend/web/src/features/deleteImageModal/README.md
new file mode 100644
index 00000000000..0591676530e
--- /dev/null
+++ b/invokeai/frontend/web/src/features/deleteImageModal/README.md
@@ -0,0 +1,7 @@
+# Delete image modal
+
+When users delete images, we show a confirmation dialog to prevent accidental deletions. Users can opt out of this, but we still check if dleeting an image would screw up their workspace and prompt if so.
+
+For example, if an image is currently set as a field in the workflow editor, we warn the user that deleting it will remove it from the node. We warn them even if they have opted out of the confirmation dialog.
+
+These "image usage" checks are done using redux selectors/util functions. See invokeai/frontend/web/src/features/deleteImageModal/store/state.ts
diff --git a/invokeai/frontend/web/src/features/dnd/README.md b/invokeai/frontend/web/src/features/dnd/README.md
new file mode 100644
index 00000000000..a3ab881752d
--- /dev/null
+++ b/invokeai/frontend/web/src/features/dnd/README.md
@@ -0,0 +1,41 @@
+# Drag and drop
+
+Dnd functionality is implemented with https://github.com/atlassian/pragmatic-drag-and-drop, the successor to https://github.com/atlassian/react-beautiful-dnd
+
+It uses the native HTML5 drag and drop API and is very performant, though a bit more involved to set up. The library doesn't expose a react API, but rather a set of utility functions to hook into the drag and drop events.
+
+## Implementation
+
+The core of our implementation is in invokeai/frontend/web/src/features/dnd/dnd.ts
+
+We support dragging and dropping of single or multiple images within the app. We have "dnd source" and "dnd target" abstractions.
+
+A dnd source is is anything that provides the draggable payload/data. Currently, that's either an image DTO or list of image names along with their origin board.
+
+A dnd target is anything that can accept a drop of that payload. Targets have their own data. For example, a target might be a board with a board ID, or a canvas layer with a layer ID.
+
+The library has a concept of draggable elements (dnd sources), droppable elements (dnd targets), and dnd monitors. The monitors are invisible elements that track drag events and provide information about the current drag operation.
+
+The library is a bit to wrap your head around but once you understand the concepts, it's very nice to work with and super flexible.
+
+## Type safety
+
+Native drag events do not have any built-in type safety. We inject a unique symbol into the sources and targets and check that via typeguard functions. This gives us confidence that the payload is what we expect it to be and not some other data that might have been dropped from outside the app or some other source.
+
+## Defining sources and targets
+
+These are strictly typed in the dnd.ts file. Follow the examples there to define new sources and targets.
+
+Targets are more complicated - they get an isValid callback (which is called with the currently-dragged source to determine if it can accept the drop) and a handler callback (which is called when the drop is made).
+
+Both isValid and handler get the source data, target data, and the redux getState/dispatch functions. They can do whatever they need to do to determine if the drop is valid and to handle the drop.
+
+Typically the isValid function just uses the source type guard function, and the handler function dispatches one or more redux actions to update the state.
+
+## Other uses of Dnd
+
+We use the same library for other dnd things:
+
+- When dragging over some tabbed interface, hovering the tab for a moment will switch to it. See invokeai/frontend/web/src/common/hooks/useCallbackOnDragEnter.ts for a hook that implements this functionality.
+- Reordering of canvas layer lists. See invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityGroupList.tsx and invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/useCanvasEntityListDnd.ts
+- Adding node fields to a workflow form builder and restructuring the form. This gets kinda complicated, as the form builder supports arbitrary nesting of containers with stacking of elements. See invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/dnd-hooks.ts
diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/README.md b/invokeai/frontend/web/src/features/dynamicPrompts/README.md
new file mode 100644
index 00000000000..242a497140a
--- /dev/null
+++ b/invokeai/frontend/web/src/features/dynamicPrompts/README.md
@@ -0,0 +1,11 @@
+# Dynamic prompts
+
+The backend API has a route to process a prompt into a list of prompts using the https://github.com/adieyal/dynamicprompts syntax
+
+In the UI, we watch the current positive prompt field for changes (debounced) and hit that route.
+
+When generating, we queue up a graph for each of the output prompts.
+
+There is a modal to show the list of generated prompts with a couple settings for prompt generation.
+
+The output prompts are stored in the redux slice for ease of consumption during graph building, but only the settings are persisted across page loads. Prompts are ephemeral.
diff --git a/invokeai/frontend/web/src/features/gallery/README.md b/invokeai/frontend/web/src/features/gallery/README.md
new file mode 100644
index 00000000000..8d9bad8fa69
--- /dev/null
+++ b/invokeai/frontend/web/src/features/gallery/README.md
@@ -0,0 +1,77 @@
+# Gallery Overview
+
+The gallery renders a scrollable grid of images. The image sizes adapt to the viewport size, and the user can scroll to any part of their gallery. It supports keyboard navigation, multi-select and context menus. Images can be dragged from the gallery to use them in other parts of the app (they are not removed from the gallery).
+
+There is some basic ordering and searching support.
+
+## Boards
+
+Boards act as folders for images.
+
+- Users can create any number of boards.
+- Each image can be assigned to at most one board.
+- There is a default "no board" board, labeled "Uncategorized".
+- User-created boards can be deleted. The no-board board cannot be deleted.
+- When deleting a board, users can choose to either delete all images in the board, or move them to the no-board board.
+- User-created boards can be renamed. The no-board board cannot be renamed.
+- Boards cannot be nested.
+- Boards can be archived, which hides them from the board list.
+- There is no way to show all images at once. The gallery view always shows images for a specific board.
+- Boards can be selected to show their images in the panel below the boards list; the gallery grid.
+- Boards can be set as the "auto-add" board. New images will be added to this board as they are generated.
+
+## Image viewer
+
+Clicking an image in the gallery opens it in the image viewer, which presents a larger view of the image, along with a variety of image actions.
+
+The image viewer is rendered in one of the main/center panel tabs.
+
+### Image actions
+
+A handful of common actions are available as buttons in the image viewer header, matching the context menu actions.
+
+See invokeai/frontend/web/src/features/gallery/components/ContextMenu/README.md
+
+### Progress viewer
+
+During generation, we might get "progress images" showing a low-res version of the image at each step in the denoising process. If these are available, the user can open a progress viewer overlay to see the image at each step.
+
+Socket subscriptions and related logic for handling progress images are in the image viewer context. See invokeai/frontend/web/src/features/gallery/components/ImageViewer/context.tsx
+
+### Metadata viewer
+
+The user can enable a metadata overlay to view the image metadata. This is rendered as a semi-transparent overlay on top of the image.
+
+"Metadata" refers to key-value pairs of various settings. For example, the prompt, number of steps and model used to generate the image. This metadata is embedded into the image file itself, but also stored in the database for searching and filtering.
+
+Images also have the execution graph embedded in them. This isn't stored in the database, as it can be large and complex. Instead, we extract it from the image when needed.
+
+Metadata can be recalled, and the graph can be loaded into the workflow editor.
+
+### Image comparison
+
+Users can hold Alt when click an image in the gallery to select it as the "comparison" image. The comparison image is shown alongside the current image in the image viewer with a couple modes (slider, side-by-side, hover-to-swap).
+
+## Data fetching
+
+The gallery uses a windowed list to only render the images that are currently visible in the viewport.
+
+It starts by loading a list of all image names for the selected board or view settings. react-virtuoso reports on the currently-visible range of images (plus some "overscan"). We then fetch the full image DTOs only for those images, which are cached by RTK Query. As the user scrolls, the visible range changes and we fetch more image DTOs as needed.
+
+This affords a nice UX, where the user can scroll to any part of their gallery. The scrollbar size never changes.
+
+We've tried some other approachs in the past, but they all had significant UX or implementation issues:
+
+### Infinite scroll
+
+Load an initial chunk of images, then load more as the user scrolls to the bottom.
+
+The scrollbar continually shrinks as more images are loaded.
+
+This yields a poor UX, as the user cannot easily scroll to a specific part of their gallery. It's also pretty complicated to implement within RTK Query, though since we switched, RTK Query now supports infinite queries. It might be easier to do this today.
+
+### Traditional pagination
+
+Show a fixed number of images per page, with pagination controls.
+
+This is a poor UX, as the user cannot easily scroll to a specific part of their gallery. Gallerys are often very large, and the page size changes depending on the viewport size. The gallery is also constantly inserting new images at the top of the list, which means we are constanty invalidating the current page's query cache and the page numbers are not stable.
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/ImageContextMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/ImageContextMenu.tsx
index 636c090a754..79e75af7e32 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/ImageContextMenu.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/ImageContextMenu.tsx
@@ -11,18 +11,6 @@ import type { RefObject } from 'react';
import { memo, useCallback, useEffect, useRef } from 'react';
import type { ImageDTO } from 'services/api/types';
-/**
- * The context menu is loosely based on https://github.com/lukasbach/chakra-ui-contextmenu.
- *
- * That library creates a component for _every_ instance of a thing that needed a context menu, which caused perf
- * issues. This implementation uses a singleton pattern instead, with a single component that listens for context menu
- * events and opens the menu as needed.
- *
- * Images register themselves with the context menu by mapping their DOM element to their image DTO. When a context
- * menu event is fired, we look up the target element in the map (or its parents) to find the image DTO to show the
- * context menu for.
- */
-
/**
* The delay in milliseconds before the context menu opens on long press.
*/
diff --git a/invokeai/frontend/web/src/features/gallery/components/ContextMenu/README.md b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/README.md
new file mode 100644
index 00000000000..0c96bbfa99c
--- /dev/null
+++ b/invokeai/frontend/web/src/features/gallery/components/ContextMenu/README.md
@@ -0,0 +1,18 @@
+# Image context menu
+
+The context menu is loosely based on https://github.com/lukasbach/chakra-ui-contextmenu.
+
+That library creates a component for _every_ instance of a thing that needed a context menu, which caused perf issues. This implementation uses a singleton pattern instead, with a single component that listens for context menu events and opens the menu as needed.
+
+Images register themselves with the context menu by mapping their DOM element to their image DTO. When a context menu event is fired, we look up the target element in the map (or its parents) to find the image DTO to show the context menu for.
+
+## Image actions
+
+- Recalling common individual metadata fields or all metadata
+- Opening the image in the image viewer or new tab
+- Copying the image to clipboard
+- Downloading the image
+- Selecting the image for comparison
+- Deleting the image
+- Moving the image to a different board
+- "Sending" the image to other parts of the app such as canvas
diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryImageGrid.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryImageGrid.tsx
index 655e7551ded..f2c50f786ec 100644
--- a/invokeai/frontend/web/src/features/gallery/components/GalleryImageGrid.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/GalleryImageGrid.tsx
@@ -42,24 +42,6 @@ type GridContext = {
imageNames: string[];
};
-/**
- * The gallery uses a windowed list to only render the images that are currently visible in the viewport. It starts by
- * loading a list of all image names for the selected board or view settings. react-virtuoso reports on the currently-
- * visible range of images (plus some "overscan"). We then fetch the full image DTOs only for those images, which are
- * cached by RTK Query. As the user scrolls, the visible range changes and we fetch more image DTOs as needed.
- *
- * This affords a nice UX, where the user can scroll to any part of their gallery. The scrollbar size never changes.
- *
- * We used other approaches in the past:
- * - Infinite scroll: Load an initial chunk of images, then load more as the user scrolls to the bottom. The scrollbar
- * continually shrinks as more images are loaded. This is a poor UX, as the user cannot easily scroll to a specific
- * part of their gallery. It's also pretty complicated to implement within RTK Query, though since we switched, RTK
- * Query now supports infinite queries. It might be easier to do this today.
- * - Traditional pagination: Show a fixed number of images per page, with pagination controls. This is a poor UX,
- * as the user cannot easily scroll to a specific part of their gallery. Gallerys are often very large, and the page
- * size changes depending on the viewport size.
- */
-
/**
* Wraps an image - either the placeholder as it is being loaded or the loaded image
*/
diff --git a/invokeai/frontend/web/src/features/imageActions/README.md b/invokeai/frontend/web/src/features/imageActions/README.md
new file mode 100644
index 00000000000..22668b29ac5
--- /dev/null
+++ b/invokeai/frontend/web/src/features/imageActions/README.md
@@ -0,0 +1,5 @@
+# Image actions
+
+This dir is (unintentially) a dumping ground for things that we do with images. For example, adding an image as a canvas layer.
+
+Probably these functions should be moved to more appropriate places.
From 267c24c5af28dba2d5d0a4ff33b931f46738f366 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Tue, 14 Oct 2025 15:34:14 +1100
Subject: [PATCH 14/20] tidy(ui): clean up useSyncQueueStatus
---
.../src/app/components/GlobalHookIsolator.tsx | 4 +--
.../app/hooks/useSyncFaviconQueueStatus.ts | 33 +++++++++++++++++++
.../web/src/app/hooks/useSyncQueueStatus.ts | 25 --------------
3 files changed, 35 insertions(+), 27 deletions(-)
create mode 100644 invokeai/frontend/web/src/app/hooks/useSyncFaviconQueueStatus.ts
delete mode 100644 invokeai/frontend/web/src/app/hooks/useSyncQueueStatus.ts
diff --git a/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx b/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx
index a4345f373a6..77e8412daa7 100644
--- a/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx
+++ b/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx
@@ -1,7 +1,7 @@
import { useGlobalModifiersInit } from '@invoke-ai/ui-library';
import { setupListeners } from '@reduxjs/toolkit/query';
+import { useSyncFaviconQueueStatus } from 'app/hooks/useSyncFaviconQueueStatus';
import { useSyncLangDirection } from 'app/hooks/useSyncLangDirection';
-import { useSyncQueueStatus } from 'app/hooks/useSyncQueueStatus';
import { useSyncLoggingConfig } from 'app/logging/useSyncLoggingConfig';
import { appStarted } from 'app/store/middleware/listenerMiddleware/listeners/appStarted';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
@@ -64,7 +64,7 @@ export const GlobalHookIsolator = memo(() => {
}, [dispatch]);
useStarterModelsToast();
- useSyncQueueStatus();
+ useSyncFaviconQueueStatus();
useFocusRegionWatcher();
useWorkflowBuilderWatcher();
useDynamicPromptsWatcher();
diff --git a/invokeai/frontend/web/src/app/hooks/useSyncFaviconQueueStatus.ts b/invokeai/frontend/web/src/app/hooks/useSyncFaviconQueueStatus.ts
new file mode 100644
index 00000000000..7bd55f25f4f
--- /dev/null
+++ b/invokeai/frontend/web/src/app/hooks/useSyncFaviconQueueStatus.ts
@@ -0,0 +1,33 @@
+import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
+import { useEffect } from 'react';
+import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
+
+const baseTitle = document.title;
+const invokeLogoSVG = 'assets/images/invoke-favicon.svg';
+const invokeAlertLogoSVG = 'assets/images/invoke-alert-favicon.svg';
+
+const queryOptions = {
+ selectFromResult: (res) => ({
+ queueSize: res.data ? res.data.queue.pending + res.data.queue.in_progress : 0,
+ }),
+} satisfies Parameters[1];
+
+const updateFavicon = (queueSize: number) => {
+ document.title = queueSize > 0 ? `(${queueSize}) ${baseTitle}` : baseTitle;
+ const faviconEl = document.getElementById('invoke-favicon');
+ if (faviconEl instanceof HTMLLinkElement) {
+ faviconEl.href = queueSize > 0 ? invokeAlertLogoSVG : invokeLogoSVG;
+ }
+};
+
+/**
+ * This hook synchronizes the queue status with the page's title and favicon.
+ * It should be considered a singleton and only used once in the component tree.
+ */
+export const useSyncFaviconQueueStatus = () => {
+ useAssertSingleton('useSyncFaviconQueueStatus');
+ const { queueSize } = useGetQueueStatusQuery(undefined, queryOptions);
+ useEffect(() => {
+ updateFavicon(queueSize);
+ }, [queueSize]);
+};
diff --git a/invokeai/frontend/web/src/app/hooks/useSyncQueueStatus.ts b/invokeai/frontend/web/src/app/hooks/useSyncQueueStatus.ts
deleted file mode 100644
index d6874c3bb5e..00000000000
--- a/invokeai/frontend/web/src/app/hooks/useSyncQueueStatus.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { useEffect } from 'react';
-import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
-
-const baseTitle = document.title;
-const invokeLogoSVG = 'assets/images/invoke-favicon.svg';
-const invokeAlertLogoSVG = 'assets/images/invoke-alert-favicon.svg';
-
-/**
- * This hook synchronizes the queue status with the page's title and favicon.
- * It should be considered a singleton and only used once in the component tree.
- */
-export const useSyncQueueStatus = () => {
- const { queueSize } = useGetQueueStatusQuery(undefined, {
- selectFromResult: (res) => ({
- queueSize: res.data ? res.data.queue.pending + res.data.queue.in_progress : 0,
- }),
- });
- useEffect(() => {
- document.title = queueSize > 0 ? `(${queueSize}) ${baseTitle}` : baseTitle;
- const faviconEl = document.getElementById('invoke-favicon');
- if (faviconEl instanceof HTMLLinkElement) {
- faviconEl.href = queueSize > 0 ? invokeAlertLogoSVG : invokeLogoSVG;
- }
- }, [queueSize]);
-};
From 2c632f189203a4ad7f278dd92c50be3c43bf8e7d Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Tue, 14 Oct 2025 16:07:40 +1100
Subject: [PATCH 15/20] tidy: app "config", settings modal, infill methods
We had an "infill methods" route that long ago told the frontend infill
method, upscale method (model), NSFW checker, and watermark feature
availability.
None of these were used except for the patchmatch check. Removed them,
made the check exclusively for patchmatch, updated related code in redux
app startup listeners and settings modal.
---
invokeai/app/api/routers/app_info.py | 41 +-----
.../listeners/appConfigReceived.ts | 29 -----
.../listeners/appStarted.ts | 25 +++-
invokeai/frontend/web/src/app/store/store.ts | 3 +-
.../controlLayers/store/paramsSlice.ts | 4 +-
.../src/features/controlLayers/store/types.ts | 5 +-
.../InfillAndScaling/ParamInfillMethod.tsx | 33 ++---
.../SettingsModal/SettingsModal.tsx | 118 ++++++------------
.../SettingsModal/useClearIntermediates.ts | 3 +-
.../web/src/services/api/endpoints/appInfo.ts | 11 +-
.../frontend/web/src/services/api/schema.ts | 49 +-------
.../frontend/web/src/services/api/types.ts | 1 -
12 files changed, 103 insertions(+), 219 deletions(-)
delete mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/appConfigReceived.ts
diff --git a/invokeai/app/api/routers/app_info.py b/invokeai/app/api/routers/app_info.py
index c71739fbd2a..d8f3bb2f807 100644
--- a/invokeai/app/api/routers/app_info.py
+++ b/invokeai/app/api/routers/app_info.py
@@ -1,7 +1,5 @@
-import typing
from enum import Enum
from importlib.metadata import distributions
-from pathlib import Path
import torch
from fastapi import Body
@@ -9,7 +7,6 @@
from pydantic import BaseModel, Field
from invokeai.app.api.dependencies import ApiDependencies
-from invokeai.app.invocations.upscale import ESRGAN_MODELS
from invokeai.app.services.config.config_default import InvokeAIAppConfig, get_config
from invokeai.app.services.invocation_cache.invocation_cache_common import InvocationCacheStatus
from invokeai.backend.image_util.infill_methods.patchmatch import PatchMatch
@@ -26,11 +23,6 @@ class LogLevel(int, Enum):
Critical = logging.CRITICAL
-class Upscaler(BaseModel):
- upscaling_method: str = Field(description="Name of upscaling method")
- upscaling_models: list[str] = Field(description="List of upscaling models for this method")
-
-
app_router = APIRouter(prefix="/v1/app", tags=["app"])
@@ -40,15 +32,6 @@ class AppVersion(BaseModel):
version: str = Field(description="App version")
-class AppConfig(BaseModel):
- """App Config Response"""
-
- infill_methods: list[str] = Field(description="List of available infill methods")
- upscaling_methods: list[Upscaler] = Field(description="List of upscaling methods")
- nsfw_methods: list[str] = Field(description="List of NSFW checking methods")
- watermarking_methods: list[str] = Field(description="List of invisible watermark methods")
-
-
@app_router.get("/version", operation_id="app_version", status_code=200, response_model=AppVersion)
async def get_version() -> AppVersion:
return AppVersion(version=__version__)
@@ -69,27 +52,9 @@ async def get_app_deps() -> dict[str, str]:
return sorted_deps
-@app_router.get("/config", operation_id="get_config", status_code=200, response_model=AppConfig)
-async def get_config_() -> AppConfig:
- infill_methods = ["lama", "tile", "cv2", "color"] # TODO: add mosaic back
- if PatchMatch.patchmatch_available():
- infill_methods.append("patchmatch")
-
- upscaling_models = []
- for model in typing.get_args(ESRGAN_MODELS):
- upscaling_models.append(str(Path(model).stem))
- upscaler = Upscaler(upscaling_method="esrgan", upscaling_models=upscaling_models)
-
- nsfw_methods = ["nsfw_checker"]
-
- watermarking_methods = ["invisible_watermark"]
-
- return AppConfig(
- infill_methods=infill_methods,
- upscaling_methods=[upscaler],
- nsfw_methods=nsfw_methods,
- watermarking_methods=watermarking_methods,
- )
+@app_router.get("/patchmatch_status", operation_id="get_patchmatch_status", status_code=200, response_model=bool)
+async def get_patchmatch_status() -> bool:
+ return PatchMatch.patchmatch_available()
class InvokeAIAppConfigWithSetFields(BaseModel):
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/appConfigReceived.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/appConfigReceived.ts
deleted file mode 100644
index 4c8f139779a..00000000000
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/appConfigReceived.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import type { AppStartListening } from 'app/store/store';
-import { setInfillMethod } from 'features/controlLayers/store/paramsSlice';
-import { shouldUseNSFWCheckerChanged, shouldUseWatermarkerChanged } from 'features/system/store/systemSlice';
-import { appInfoApi } from 'services/api/endpoints/appInfo';
-
-export const addAppConfigReceivedListener = (startAppListening: AppStartListening) => {
- startAppListening({
- matcher: appInfoApi.endpoints.getAppConfig.matchFulfilled,
- effect: (action, { getState, dispatch }) => {
- const { infill_methods = [], nsfw_methods = [], watermarking_methods = [] } = action.payload;
- const infillMethod = getState().params.infillMethod;
-
- if (!infill_methods.includes(infillMethod)) {
- // If the selected infill method does not exist, prefer 'lama' if it's in the list, otherwise 'tile'.
- // TODO(psyche): lama _should_ always be in the list, but the API doesn't guarantee it...
- const infillMethod = infill_methods.includes('lama') ? 'lama' : 'tile';
- dispatch(setInfillMethod(infillMethod));
- }
-
- if (!nsfw_methods.includes('nsfw_checker')) {
- dispatch(shouldUseNSFWCheckerChanged(false));
- }
-
- if (!watermarking_methods.includes('invisible_watermark')) {
- dispatch(shouldUseWatermarkerChanged(false));
- }
- },
- });
-};
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/appStarted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/appStarted.ts
index 794d1a1af60..6bff69c64a3 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/appStarted.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/appStarted.ts
@@ -1,7 +1,10 @@
import { createAction } from '@reduxjs/toolkit';
import type { AppStartListening } from 'app/store/store';
+import { noop } from 'es-toolkit';
+import { setInfillMethod } from 'features/controlLayers/store/paramsSlice';
import { selectLastSelectedItem } from 'features/gallery/store/gallerySelectors';
import { imageSelected } from 'features/gallery/store/gallerySlice';
+import { appInfoApi } from 'services/api/endpoints/appInfo';
import { imagesApi } from 'services/api/endpoints/images';
export const appStarted = createAction('app/appStarted');
@@ -9,14 +12,17 @@ export const appStarted = createAction('app/appStarted');
export const addAppStartedListener = (startAppListening: AppStartListening) => {
startAppListening({
actionCreator: appStarted,
- effect: async (action, { unsubscribe, cancelActiveListeners, take, getState, dispatch }) => {
+ effect: (action, { unsubscribe, cancelActiveListeners, take, getState, dispatch }) => {
// this should only run once
cancelActiveListeners();
unsubscribe();
// ensure an image is selected when we load the first board
- const firstImageLoad = await take(imagesApi.endpoints.getImageNames.matchFulfilled);
- if (firstImageLoad !== null) {
+ take(imagesApi.endpoints.getImageNames.matchFulfilled).then((firstImageLoad) => {
+ if (firstImageLoad === null) {
+ // timeout or cancelled
+ return;
+ }
const [{ payload }] = firstImageLoad;
const selectedImage = selectLastSelectedItem(getState());
if (selectedImage) {
@@ -25,7 +31,18 @@ export const addAppStartedListener = (startAppListening: AppStartListening) => {
if (payload.image_names[0]) {
dispatch(imageSelected(payload.image_names[0]));
}
- }
+ });
+
+ dispatch(appInfoApi.endpoints.getPatchmatchStatus.initiate())
+ .unwrap()
+ .then((isPatchmatchAvailable) => {
+ const infillMethod = getState().params.infillMethod;
+
+ if (!isPatchmatchAvailable && infillMethod === 'patchmatch') {
+ dispatch(setInfillMethod('lama'));
+ }
+ })
+ .catch(noop);
},
});
};
diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts
index 15dbfc785ca..cad6f489df7 100644
--- a/invokeai/frontend/web/src/app/store/store.ts
+++ b/invokeai/frontend/web/src/app/store/store.ts
@@ -4,7 +4,6 @@ import { logger } from 'app/logging/logger';
import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
import { addAdHocPostProcessingRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener';
import { addAnyEnqueuedListener } from 'app/store/middleware/listenerMiddleware/listeners/anyEnqueued';
-import { addAppConfigReceivedListener } from 'app/store/middleware/listenerMiddleware/listeners/appConfigReceived';
import { addAppStartedListener } from 'app/store/middleware/listenerMiddleware/listeners/appStarted';
import { addBatchEnqueuedListener } from 'app/store/middleware/listenerMiddleware/listeners/batchEnqueued';
import { addDeleteBoardAndImagesFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted';
@@ -242,6 +241,7 @@ export type AppStartListening = TypedStartListening;
export const addAppListener = addListener.withTypes();
+// To avoid circular dependencies, all listener middleware listeners are added here in the main store setup file.
const startAppListening = listenerMiddleware.startListening as AppStartListening;
addImageUploadedFulfilledListener(startAppListening);
@@ -273,7 +273,6 @@ addModelSelectedListener(startAppListening);
// app startup
addAppStartedListener(startAppListening);
addModelsLoadedListener(startAppListening);
-addAppConfigReceivedListener(startAppListening);
// Ad-hoc upscale workflwo
addAdHocPostProcessingRequestedListener(startAppListening);
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts
index 90eb53124a1..9dd85b1bc20 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/paramsSlice.ts
@@ -6,7 +6,7 @@ import { deepClone } from 'common/util/deepClone';
import { roundDownToMultiple, roundToMultiple } from 'common/util/roundDownToMultiple';
import { isPlainObject } from 'es-toolkit';
import { clamp } from 'es-toolkit/compat';
-import type { AspectRatioID, ParamsState, RgbaColor } from 'features/controlLayers/store/types';
+import type { AspectRatioID, InfillMethod, ParamsState, RgbaColor } from 'features/controlLayers/store/types';
import {
ASPECT_RATIO_MAP,
DEFAULT_ASPECT_RATIO_CONFIG,
@@ -219,7 +219,7 @@ const slice = createSlice({
setRefinerStart: (state, action: PayloadAction) => {
state.refinerStart = action.payload;
},
- setInfillMethod: (state, action: PayloadAction) => {
+ setInfillMethod: (state, action: PayloadAction) => {
state.infillMethod = action.payload;
},
setInfillTileSize: (state, action: PayloadAction) => {
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts
index 0ec0cd7baa8..87c173d7cca 100644
--- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts
@@ -562,6 +562,9 @@ const zPositivePromptHistory = z
.array(zParameterPositivePrompt)
.transform((arr) => arr.slice(0, MAX_POSITIVE_PROMPT_HISTORY));
+export const zInfillMethod = z.enum(['patchmatch', 'lama', 'cv2', 'color', 'tile']);
+export type InfillMethod = z.infer;
+
export const zParamsState = z.object({
_version: z.literal(2),
maskBlur: z.number(),
@@ -569,7 +572,7 @@ export const zParamsState = z.object({
canvasCoherenceMode: zParameterCanvasCoherenceMode,
canvasCoherenceMinDenoise: zParameterStrength,
canvasCoherenceEdgeSize: z.number(),
- infillMethod: z.string(),
+ infillMethod: zInfillMethod,
infillTileSize: z.number(),
infillPatchmatchDownscaleSize: z.number(),
infillColorValue: zRgbaColor,
diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillMethod.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillMethod.tsx
index 2ae24fdb805..5b795aeaddd 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillMethod.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillMethod.tsx
@@ -1,34 +1,39 @@
-import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
+import type { ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
+import { EMPTY_ARRAY } from 'app/store/constants';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { selectInfillMethod, setInfillMethod } from 'features/controlLayers/store/paramsSlice';
+import { zInfillMethod } from 'features/controlLayers/store/types';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
-import { useGetAppConfigQuery } from 'services/api/endpoints/appInfo';
+import { useGetPatchmatchStatusQuery } from 'services/api/endpoints/appInfo';
const ParamInfillMethod = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const infillMethod = useAppSelector(selectInfillMethod);
- const { data: appConfigData } = useGetAppConfigQuery();
- const options = useMemo(
- () =>
- appConfigData
- ? appConfigData.infill_methods.map((method) => ({
- label: method,
- value: method,
- }))
- : [],
- [appConfigData]
- );
+ const { options } = useGetPatchmatchStatusQuery(undefined, {
+ selectFromResult: ({ data: isPatchmatchAvailable }) => {
+ if (isPatchmatchAvailable === undefined) {
+ // loading...
+ return { options: EMPTY_ARRAY };
+ }
+ if (isPatchmatchAvailable) {
+ return { options: zInfillMethod.options.map((o) => ({ label: o, value: o })) };
+ }
+ return {
+ options: zInfillMethod.options.filter((o) => o !== 'patchmatch').map((o) => ({ label: o, value: o })),
+ };
+ },
+ });
const onChange = useCallback(
(v) => {
if (!v || !options.find((o) => o.value === v.value)) {
return;
}
- dispatch(setInfillMethod(v.value));
+ dispatch(setInfillMethod(zInfillMethod.parse(v.value)));
},
[dispatch, options]
);
diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx
index 1be280fa40e..dc83e3efa8d 100644
--- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx
+++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx
@@ -51,52 +51,23 @@ import { setShouldShowProgressInViewer } from 'features/ui/store/uiSlice';
import type { ChangeEvent, ReactElement } from 'react';
import { cloneElement, memo, useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
-import { useGetAppConfigQuery } from 'services/api/endpoints/appInfo';
import { SettingsLanguageSelect } from './SettingsLanguageSelect';
-type ConfigOptions = {
- shouldShowDeveloperSettings?: boolean;
- shouldShowResetWebUiText?: boolean;
- shouldShowClearIntermediates?: boolean;
- shouldShowLocalizationToggle?: boolean;
- shouldShowInvocationProgressDetailSetting?: boolean;
-};
-
-const defaultConfig: ConfigOptions = {
- shouldShowDeveloperSettings: true,
- shouldShowResetWebUiText: true,
- shouldShowClearIntermediates: true,
- shouldShowLocalizationToggle: true,
- shouldShowInvocationProgressDetailSetting: true,
-};
-
-type SettingsModalProps = {
- /* The button to open the Settings Modal */
- children: ReactElement;
- config?: ConfigOptions;
-};
-
const [useSettingsModal] = buildUseBoolean(false);
-const SettingsModal = ({ config = defaultConfig, children }: SettingsModalProps) => {
+const SettingsModal = (props: { children: ReactElement }) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
- const { isNSFWCheckerAvailable, isWatermarkerAvailable } = useGetAppConfigQuery(undefined, {
- selectFromResult: ({ data }) => ({
- isNSFWCheckerAvailable: data?.nsfw_methods.includes('nsfw_checker') ?? false,
- isWatermarkerAvailable: data?.watermarking_methods.includes('invisible_watermark') ?? false,
- }),
- });
-
const {
clearIntermediates,
hasPendingItems,
intermediatesCount,
isLoading: isLoadingClearIntermediates,
refetchIntermediatesCount,
- } = useClearIntermediates(Boolean(config?.shouldShowClearIntermediates));
+ } = useClearIntermediates();
+
const settingsModal = useSettingsModal();
const refreshModal = useRefreshAfterResetModal();
@@ -116,10 +87,11 @@ const SettingsModal = ({ config = defaultConfig, children }: SettingsModalProps)
}, [dispatch]);
useEffect(() => {
- if (settingsModal.isTrue && Boolean(config?.shouldShowClearIntermediates)) {
+ // Refetch intermediates count when modal is opened
+ if (settingsModal.isTrue) {
refetchIntermediatesCount();
}
- }, [config?.shouldShowClearIntermediates, refetchIntermediatesCount, settingsModal.isTrue]);
+ }, [refetchIntermediatesCount, settingsModal.isTrue]);
const handleClickResetWebUI = useCallback(() => {
clearStorage();
@@ -192,7 +164,7 @@ const SettingsModal = ({ config = defaultConfig, children }: SettingsModalProps)
return (
<>
- {cloneElement(children, {
+ {cloneElement(props.children, {
onClick: settingsModal.setTrue,
})}
@@ -216,11 +188,11 @@ const SettingsModal = ({ config = defaultConfig, children }: SettingsModalProps)
-
+
{t('settings.enableNSFWChecker')}
-
+
{t('settings.enableInvisibleWatermark')}
@@ -241,22 +213,20 @@ const SettingsModal = ({ config = defaultConfig, children }: SettingsModalProps)
onChange={handleChangeShouldAntialiasProgressImage}
/>
- {Boolean(config?.shouldShowInvocationProgressDetailSetting) && (
-
- {t('settings.showDetailedInvocationProgress')}
-
-
- )}
+
+ {t('settings.showDetailedInvocationProgress')}
+
+
{t('parameters.useCpuNoise')}
- {Boolean(config?.shouldShowLocalizationToggle) && }
+
{t('settings.enableInformationalPopovers')}
- {Boolean(config?.shouldShowDeveloperSettings) && (
-
-
-
-
-
- )}
+
+
+
+
+
- {Boolean(config?.shouldShowClearIntermediates) && (
-
-
- {t('settings.clearIntermediatesWithCount', {
- count: intermediatesCount ?? 0,
- })}
-
- {t('settings.clearIntermediatesDesc1')}
- {t('settings.clearIntermediatesDesc2')}
- {t('settings.clearIntermediatesDesc3')}
-
- )}
+
+
+ {t('settings.clearIntermediatesWithCount', {
+ count: intermediatesCount ?? 0,
+ })}
+
+ {t('settings.clearIntermediatesDesc1')}
+ {t('settings.clearIntermediatesDesc2')}
+ {t('settings.clearIntermediatesDesc3')}
+
{t('settings.resetWebUI')}
- {Boolean(config?.shouldShowResetWebUiText) && (
- <>
- {t('settings.resetWebUIDesc1')}
- {t('settings.resetWebUIDesc2')}
- >
- )}
+ {t('settings.resetWebUIDesc1')}
+ {t('settings.resetWebUIDesc2')}
diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/useClearIntermediates.ts b/invokeai/frontend/web/src/features/system/components/SettingsModal/useClearIntermediates.ts
index 26ad8a78ad7..c9f1e524bfa 100644
--- a/invokeai/frontend/web/src/features/system/components/SettingsModal/useClearIntermediates.ts
+++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/useClearIntermediates.ts
@@ -12,12 +12,11 @@ type UseClearIntermediatesReturn = {
refetchIntermediatesCount: () => void;
};
-export const useClearIntermediates = (shouldShowClearIntermediates: boolean): UseClearIntermediatesReturn => {
+export const useClearIntermediates = (): UseClearIntermediatesReturn => {
const { t } = useTranslation();
const { data: intermediatesCount, refetch: refetchIntermediatesCount } = useGetIntermediatesCountQuery(undefined, {
refetchOnMountOrArgChange: true,
- skip: !shouldShowClearIntermediates,
});
const [_clearIntermediates, { isLoading }] = useClearIntermediatesMutation();
diff --git a/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts b/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts
index 2388dd0c089..f72d6ad81e8 100644
--- a/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts
+++ b/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts
@@ -1,7 +1,7 @@
import type { OpenAPIV3_1 } from 'openapi-types';
import type { stringify } from 'querystring';
import type { paths } from 'services/api/schema';
-import type { AppConfig, AppVersion } from 'services/api/types';
+import type { AppVersion } from 'services/api/types';
import { api, buildV1Url } from '..';
@@ -33,9 +33,12 @@ export const appInfoApi = api.injectEndpoints({
}),
providesTags: ['FetchOnReconnect'],
}),
- getAppConfig: build.query({
+ getPatchmatchStatus: build.query<
+ paths['/api/v1/app/patchmatch_status']['get']['responses']['200']['content']['application/json'],
+ void
+ >({
query: () => ({
- url: buildAppInfoUrl('config'),
+ url: buildAppInfoUrl('patchmatch_status'),
method: 'GET',
}),
providesTags: ['FetchOnReconnect'],
@@ -90,7 +93,7 @@ export const appInfoApi = api.injectEndpoints({
export const {
useGetAppVersionQuery,
useGetAppDepsQuery,
- useGetAppConfigQuery,
+ useGetPatchmatchStatusQuery,
useGetRuntimeConfigQuery,
useClearInvocationCacheMutation,
useDisableInvocationCacheMutation,
diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts
index db4ba519cad..456d5f3d55e 100644
--- a/invokeai/frontend/web/src/services/api/schema.ts
+++ b/invokeai/frontend/web/src/services/api/schema.ts
@@ -1026,15 +1026,15 @@ export type paths = {
patch?: never;
trace?: never;
};
- "/api/v1/app/config": {
+ "/api/v1/app/patchmatch_status": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
- /** Get Config */
- get: operations["get_config"];
+ /** Get Patchmatch Status */
+ get: operations["get_patchmatch_status"];
put?: never;
post?: never;
delete?: never;
@@ -1993,32 +1993,6 @@ export type components = {
type: "alpha_mask_to_tensor";
};
AnyModelConfig: components["schemas"]["Main_Diffusers_SD1_Config"] | components["schemas"]["Main_Diffusers_SD2_Config"] | components["schemas"]["Main_Diffusers_SDXL_Config"] | components["schemas"]["Main_Diffusers_SDXLRefiner_Config"] | components["schemas"]["Main_Diffusers_SD3_Config"] | components["schemas"]["Main_Diffusers_CogView4_Config"] | components["schemas"]["Main_Checkpoint_SD1_Config"] | components["schemas"]["Main_Checkpoint_SD2_Config"] | components["schemas"]["Main_Checkpoint_SDXL_Config"] | components["schemas"]["Main_Checkpoint_SDXLRefiner_Config"] | components["schemas"]["Main_Checkpoint_FLUX_Config"] | components["schemas"]["Main_BnBNF4_FLUX_Config"] | components["schemas"]["Main_GGUF_FLUX_Config"] | components["schemas"]["VAE_Checkpoint_SD1_Config"] | components["schemas"]["VAE_Checkpoint_SD2_Config"] | components["schemas"]["VAE_Checkpoint_SDXL_Config"] | components["schemas"]["VAE_Checkpoint_FLUX_Config"] | components["schemas"]["VAE_Diffusers_SD1_Config"] | components["schemas"]["VAE_Diffusers_SDXL_Config"] | components["schemas"]["ControlNet_Checkpoint_SD1_Config"] | components["schemas"]["ControlNet_Checkpoint_SD2_Config"] | components["schemas"]["ControlNet_Checkpoint_SDXL_Config"] | components["schemas"]["ControlNet_Checkpoint_FLUX_Config"] | components["schemas"]["ControlNet_Diffusers_SD1_Config"] | components["schemas"]["ControlNet_Diffusers_SD2_Config"] | components["schemas"]["ControlNet_Diffusers_SDXL_Config"] | components["schemas"]["ControlNet_Diffusers_FLUX_Config"] | components["schemas"]["LoRA_LyCORIS_SD1_Config"] | components["schemas"]["LoRA_LyCORIS_SD2_Config"] | components["schemas"]["LoRA_LyCORIS_SDXL_Config"] | components["schemas"]["LoRA_LyCORIS_FLUX_Config"] | components["schemas"]["LoRA_OMI_SDXL_Config"] | components["schemas"]["LoRA_OMI_FLUX_Config"] | components["schemas"]["LoRA_Diffusers_SD1_Config"] | components["schemas"]["LoRA_Diffusers_SD2_Config"] | components["schemas"]["LoRA_Diffusers_SDXL_Config"] | components["schemas"]["LoRA_Diffusers_FLUX_Config"] | components["schemas"]["ControlLoRA_LyCORIS_FLUX_Config"] | components["schemas"]["T5Encoder_T5Encoder_Config"] | components["schemas"]["T5Encoder_BnBLLMint8_Config"] | components["schemas"]["TI_File_SD1_Config"] | components["schemas"]["TI_File_SD2_Config"] | components["schemas"]["TI_File_SDXL_Config"] | components["schemas"]["TI_Folder_SD1_Config"] | components["schemas"]["TI_Folder_SD2_Config"] | components["schemas"]["TI_Folder_SDXL_Config"] | components["schemas"]["IPAdapter_InvokeAI_SD1_Config"] | components["schemas"]["IPAdapter_InvokeAI_SD2_Config"] | components["schemas"]["IPAdapter_InvokeAI_SDXL_Config"] | components["schemas"]["IPAdapter_Checkpoint_SD1_Config"] | components["schemas"]["IPAdapter_Checkpoint_SD2_Config"] | components["schemas"]["IPAdapter_Checkpoint_SDXL_Config"] | components["schemas"]["IPAdapter_Checkpoint_FLUX_Config"] | components["schemas"]["T2IAdapter_Diffusers_SD1_Config"] | components["schemas"]["T2IAdapter_Diffusers_SDXL_Config"] | components["schemas"]["Spandrel_Checkpoint_Config"] | components["schemas"]["CLIPEmbed_Diffusers_G_Config"] | components["schemas"]["CLIPEmbed_Diffusers_L_Config"] | components["schemas"]["CLIPVision_Diffusers_Config"] | components["schemas"]["SigLIP_Diffusers_Config"] | components["schemas"]["FLUXRedux_Checkpoint_Config"] | components["schemas"]["LlavaOnevision_Diffusers_Config"] | components["schemas"]["Unknown_Config"];
- /**
- * AppConfig
- * @description App Config Response
- */
- AppConfig: {
- /**
- * Infill Methods
- * @description List of available infill methods
- */
- infill_methods: string[];
- /**
- * Upscaling Methods
- * @description List of upscaling methods
- */
- upscaling_methods: components["schemas"]["Upscaler"][];
- /**
- * Nsfw Methods
- * @description List of NSFW checking methods
- */
- nsfw_methods: string[];
- /**
- * Watermarking Methods
- * @description List of invisible watermark methods
- */
- watermarking_methods: string[];
- };
/**
* AppVersion
* @description App Version Response
@@ -23605,19 +23579,6 @@ export type components = {
*/
unstarred_images: string[];
};
- /** Upscaler */
- Upscaler: {
- /**
- * Upscaling Method
- * @description Name of upscaling method
- */
- upscaling_method: string;
- /**
- * Upscaling Models
- * @description List of upscaling models for this method
- */
- upscaling_models: string[];
- };
/** VAEField */
VAEField: {
/** @description Info to load vae submodel */
@@ -26870,7 +26831,7 @@ export interface operations {
};
};
};
- get_config: {
+ get_patchmatch_status: {
parameters: {
query?: never;
header?: never;
@@ -26885,7 +26846,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["AppConfig"];
+ "application/json": boolean;
};
};
};
diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts
index d5f7eedaf4c..fa4c04a62eb 100644
--- a/invokeai/frontend/web/src/services/api/types.ts
+++ b/invokeai/frontend/web/src/services/api/types.ts
@@ -43,7 +43,6 @@ export type InvocationJSONSchemaExtra = S['UIConfigBase'];
// App Info
export type AppVersion = S['AppVersion'];
-export type AppConfig = S['AppConfig'];
const zResourceOrigin = z.enum(['internal', 'external']);
type ResourceOrigin = z.infer;
From 3539880a6ffd792047066aa0bbc72be8d931cf32 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Tue, 14 Oct 2025 17:11:03 +1100
Subject: [PATCH 16/20] docs(ui): more documentation
---
.../frontend/web/src/features/ui/README.md | 26 +++++++++++++++++++
.../frontend/web/src/services/api/README.md | 20 ++++++++++++++
.../services/events/onInvocationComplete.tsx | 10 +++++++
.../services/events/onModelInstallError.tsx | 3 +++
.../src/services/events/setEventListeners.tsx | 4 +++
5 files changed, 63 insertions(+)
create mode 100644 invokeai/frontend/web/src/features/ui/README.md
create mode 100644 invokeai/frontend/web/src/services/api/README.md
diff --git a/invokeai/frontend/web/src/features/ui/README.md b/invokeai/frontend/web/src/features/ui/README.md
new file mode 100644
index 00000000000..f4c262d425d
--- /dev/null
+++ b/invokeai/frontend/web/src/features/ui/README.md
@@ -0,0 +1,26 @@
+# UI/Layout
+
+We use https://github.com/mathuo/dockview for layout. This library supports resizable and dockable panels. Users can drag and drop panels to rearrange them.
+
+The intention when adopting this library was to allow users to create their own custom layouts and save them. However, this feature is not yet implemented and each tab only has a predefined layout.
+
+This works well, but it _is_ fairly complex. You can see that we've needed to write a fairly involved API to manage the layouts: invokeai/frontend/web/src/features/ui/layouts/navigation-api.ts
+
+And the layouts themselves are awkward to define, especially when compared to plain JSX: invokeai/frontend/web/src/features/ui/layouts/generate-tab-auto-layout.tsx
+
+This complexity may or may not be worth it.
+
+## Previous approach
+
+Previously we used https://github.com/bvaughn/react-resizable-panels and simple JSX components.
+
+This library is great except it doesn't support absolute size constraints, only relative/percentage constraints. We had a brittle abstraction layer on top of it to try to enforce minimum pixel sizes for panels but it was janky and had FP precision issues causing drifting sizes.
+
+It also doesn't support dockable panels.
+
+## Future possibilities
+
+1. Continue with dockview and implement custom layout saving/loading. We experimented with this and it was _really_ nice. We defined a component for each panel type and use react context to manage state. But we thought that it would be confusing for most users, so we flagged it for a future iteration and instead shipped with predefined layouts.
+2. Switch to a simpler layout library or roll our own.
+
+In hindsight, we should have skipped dockview and found something else that was simpler until we were ready to invest in custom layouts.
diff --git a/invokeai/frontend/web/src/services/api/README.md b/invokeai/frontend/web/src/services/api/README.md
new file mode 100644
index 00000000000..2cd21dbd99b
--- /dev/null
+++ b/invokeai/frontend/web/src/services/api/README.md
@@ -0,0 +1,20 @@
+# API
+
+The API client is a fairly standard Redux Toolkit Query (RTK-Query) setup.
+
+It defines a simple base query with special handling for OpenAPI schema queries and endpoints: invokeai/frontend/web/src/services/api/index.ts
+
+## Types
+
+The API provides an OpenAPI schema and we generate TS types from it. They are stored in: invokeai/frontend/web/src/services/api/schema.ts
+
+We use https://github.com/openapi-ts/openapi-typescript/ to generate the types.
+
+- Python script to outut the OpenAPI schema: scripts/generate_openapi_schema.py
+- Node script to call openapi-typescript and generate the TS types: invokeai/frontend/web/scripts/typegen.js
+
+Pipe the output of the python script to the node script to update the types. There is a `make` target that does this in one fell swoop (after activating venv): `make frontend-typegen`
+
+Alternatively, start the ptyhon server and run `pnpm typegen`.
+
+The schema.ts file is pushed to the repo, and a CI check ensures it is up to date.
diff --git a/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
index d076b9a7303..e2ee74dcad1 100644
--- a/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
+++ b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
@@ -26,8 +26,18 @@ import type { JsonObject } from 'type-fest';
const log = logger('events');
+// These nodes are passthrough nodes. They do not add images to the gallery, so we must skip that handling for them.
const nodeTypeDenylist = ['load_image', 'image'];
+/**
+ * Builds the socket event handler for invocation complete events. Adds output images to the gallery and/or updates
+ * node execution states for the workflow editor.
+ *
+ * @param getState The Redux getState function.
+ * @param dispatch The Redux dispatch function.
+ * @param finishedQueueItemIds A cache of finished queue item IDs to prevent duplicate handling and avoid race
+ * conditions that can happen when a graph finishes very quickly.
+ */
export const buildOnInvocationComplete = (
getState: AppGetState,
dispatch: AppDispatch,
diff --git a/invokeai/frontend/web/src/services/events/onModelInstallError.tsx b/invokeai/frontend/web/src/services/events/onModelInstallError.tsx
index ba7a3ed19f3..24c31c4a296 100644
--- a/invokeai/frontend/web/src/services/events/onModelInstallError.tsx
+++ b/invokeai/frontend/web/src/services/events/onModelInstallError.tsx
@@ -39,6 +39,9 @@ const getHFTokenStatus = async (dispatch: AppDispatch): Promise {
return async (data: S['ModelInstallErrorEvent']) => {
log.error({ data }, 'Model install error');
diff --git a/invokeai/frontend/web/src/services/events/setEventListeners.tsx b/invokeai/frontend/web/src/services/events/setEventListeners.tsx
index d9a1d8386c1..f998627d26c 100644
--- a/invokeai/frontend/web/src/services/events/setEventListeners.tsx
+++ b/invokeai/frontend/web/src/services/events/setEventListeners.tsx
@@ -33,6 +33,10 @@ type SetEventListenersArg = {
const selectModelInstalls = modelsApi.endpoints.listModelInstalls.select();
+/**
+ * Sets up event listeners for the socketio client. Some components will set up their own listeners. These are the ones
+ * that have app-wide implications.
+ */
export const setEventListeners = ({ socket, store, setIsConnected }: SetEventListenersArg) => {
const { dispatch, getState } = store;
From 420d8a445eeb5884fa89e4d524288b6482aecac5 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Tue, 14 Oct 2025 19:31:27 +1100
Subject: [PATCH 17/20] docs(ui): add canvas overview and design doc
---
.../web/src/features/controlLayers/README.md | 228 ++++++++++++++++++
1 file changed, 228 insertions(+)
create mode 100644 invokeai/frontend/web/src/features/controlLayers/README.md
diff --git a/invokeai/frontend/web/src/features/controlLayers/README.md b/invokeai/frontend/web/src/features/controlLayers/README.md
new file mode 100644
index 00000000000..de2aafee13a
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/README.md
@@ -0,0 +1,228 @@
+# Canvas
+
+The canvas is a fairly complex feature. It uses "native" KonvaJS (i.e. not the Konva react bindings) to render a drawing canvas.
+
+It supports layers, drawing, erasing, undo/redo, exporting, backend filters (i.e. filters that require sending image data to teh backend to process) and frontend filters.
+
+## Broad Strokes of Design
+
+The canvas is internally is a hierarchy of classes (modules). All canvas modules inherit from invokeai/frontend/web/src/features/controlLayers/konva/CanvasModuleBase.ts
+
+### Modules
+
+The top-level module is the CanvasManager: invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts
+
+All canvas modules have:
+
+- A unique id (per instance)
+- A ref to its parent module and the canvas manager (the top-leve Manager refs itself)
+- A repr() method that returns a plain JS object representing the module instance
+- A destroy() method to clean up resources
+- A log() method that auto-injects context for the module instanc)
+
+Modules can do anything, they are simply plain-JS classes to encapsulate some functionality. Some are singletons. Some examples:
+
+- A singleton module that handles tool-specific interactions: invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasToolModule.ts
+- Singleton models for each tool e.g. the CanvasBrushToolModule: invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasBrushToolModule.ts
+- A singleton module to render the background of the canvas: invokeai/frontend/web/src/features/controlLayers/konva/CanvasBackgroundModule.ts
+- A strictly logical module that manages various caches of image data: invokeai/frontend/web/src/features/controlLayers/konva/CanvasCacheModule.ts
+- A non-singleton module that handles rendering a brush stroke: invokeai/frontend/web/src/features/controlLayers/konva/CanvasObject/CanvasObjectBrushLine.ts
+
+### Layers (Entities) and Adapters modules
+
+Canvas has a number of layer types:
+
+- Raster layers: Traditional raster/pixel layers, much like layers in Photoshop
+- Control layers: Internally a raster layer, but designated to hold control data (e.g. depth maps, segmentation masks, etc.) and have special rendering rules
+- Regional guidance layers: A mask-like layer (i.e. it has arbitrary shapes but they have no color or texture, it's just a mask region) plus conditioning data like prompts or ref images. The conditioning is applied only to the masked regions
+- Inpaint mask layers: Another mask-like layer that indicate regions to inpaint/regenerate
+
+Instances of layers are called "entities" in the codebase. Each entity has a type (one of the above), a number of properties (e.g. visibility, opacity, etc.), objects (e.g. brush strokes, shapes, images) and possibly other data.
+
+Each layer type has a corresponding "adapter" module that handles rendering the layer and its objects, applying filters, etc. The adapter modules are non-singleton modules that are instantiated once per layer entity.
+
+Using the raster layer type as an example, it has a number of sub-modules:
+
+- A top-level module that coordinates everything: invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterRasterLayer.ts
+- An object (e.g. brush strokes, shapes, images) renderer that draws the layer via Konva: invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityObjectRenderer.ts
+- A "buffer" object renderer, which renders in-progress objects (e.g. a brush stroke that is being drawn but not yet committed, important for performance): invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityBufferObjectRenderer.ts
+- A module that handles previewing and applying backend filters: invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityFilterer.ts
+- A module that handles selecting objects from the pixel data of a layer (aka segmentation tasks): invokeai/frontend/web/src/features/controlLayers/konva/CanvasSegmentAnythingModule.ts
+- A module that handles transforming the layer (scale, translate, rotate): invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityTransformer.ts
+
+## State mgmt
+
+This gets a bit hairy. We have a mix of redux, Konva and nanostores.
+
+At a high level, we use observable/listener patterns to react to state changes and propagate them to where they need to go.
+
+### Redux
+
+Redux is the source of truth for _persistent_ canvas state - layers, their order, etc.
+
+The redux API includes:
+
+- getState(): Get the entire redux state
+- subscribe(listener): Subscribe to state changes, listener is called on _every_ state change, no granularity is provided
+- dispatch(action): Dispatch an action to change state
+
+Redux is not suitable for _transient_ state that changes frequently, e.g. the current brush stroke as the user is drawing it. Syncing every change to redux would be too slow and incur a significant performance penalty that would drop FPS too much.
+
+Canvas modules that have persistent state (e.g. layers, their properties, etc.) use redux to store that state and will subscribe to redux to listen for changes and update themselves as needed.
+
+### Konva
+
+Konva's API is imperative (i.e. you call methods on the Konva nodes to change them) but it renders automatically.
+
+There is no simple way to "subscribe" to changes in Konva nodes. You can listen to certain events (e.g. dragmove, transform, etc.) but there is no generic "node changed" event.
+
+So we almost exclusively push data to Konva, we never "read" from it.
+
+### Nanostores
+
+We use https://github.com/nanostores/nanostores as a lightweight observable state management solution. Nanostores has a plain-JS listener API for subscribing to changes, similar to redux's subscribe(). And it has react bindings so we can use it in react components.
+
+Modules often use nanostores to store their internal state, especially when that state needs to be observed by other modules or react components.
+
+For example, the CanvasToolModule uses a nanostore to hold the current tool (brush, eraser, etc.) and its options (brush size, color, etc.). React components can subscribe to that store to update their UI when the tool or its options change.
+
+So this provides a simple two-way binding between canvas modules and react components.
+
+### State -> Canvas
+
+Data may flow from redux state to Canvas. For example, on canvas init we render all layers and their objects from redux state in Konva:
+
+- Create the layer's entity adapter and all sub-modules
+- Iterate over the layer's objects and create a module instance for each object (e.g. brush stroke, shape, image)
+- Each object module creates the necessary Konva nodes to represent itself and adds them to the layer
+
+The entity adapter subscribes to redux to listen for state changes and pass on the updated state to its sub-modules so they can do whatever they need to do w/ the updated state.
+
+Besides the initial render, we might have to update the Konva representation of a layer when:
+
+- The layer's properties are changed (e.g. visibility, opacity, etc.)
+- The layer's order is changed (e.g. move up/down)
+- User does an undo/redo operation that affects the layer
+- The layer is deleted
+
+### Canvas -> State
+
+When the user interacts w/ the canvas (e.g. draws a brush stroke, erases, moves an object, etc.), we create/update/delete objects in Konva. When the user finishes the interaction (e.g. finishes drawing a brush stroke), we serialize the object to a plain JS object and dispatch a redux action to add the object in redux state.
+
+Using drawing a line on a raster layer as an example, the flow is:
+
+- User initiates a brush stroke and draws
+- We create a brush line object module instance in the layer's buffer renderer
+- The brush line object is given a unique ID
+- The brush line mod creates a Konva.Line node to represent the stroke
+- The brush line mod tracks the stroke as the user draws, updating the Konva.Line node as needed, all in the buffer renderer
+- When the user finishes the stroke, the brush line module transfers control of itself from the layer's buffer renderer to its main renderer
+- As the line is marked complete, the line data is serialized to a plain JS object (i.e. array of points and color) and we dispatch a redux action to add the line object to the layer entity in redux state
+
+Besides drawing tasks, we have similar flows for:
+
+- Transforming a layer (scale, translate, rotate)
+- Filtering a layer
+- Selecting objects from a layer (segmentation tasks)
+
+## Erasing is hard
+
+HTML Canvas has a limited set of compositing modes. These apply globally to the whole canvas element. There is no "local" compositing mode that applies only to a specific shape or object. There is no concept of layers.
+
+So to implement erasing (and opacity!), we have to get creative. Konva handles much of this for us. Each layer is represented internally by a Konva.Layer, which in turn is drawn to its own HTML Canvas element.
+
+Erasing is accomplished by using a globalCompositeOperation of "destination-out" on the brush stroke that is doing the erasing. The brush stroke "cuts a hole" in the layer it is drawn on.
+
+There is a complication. The UX for erasing a layer should be:
+
+- User has a layer, let's say it has an image on it
+- The layer's size is exactly the size of the image
+- User erases the right-hand half of the image
+- The layer's size shrinks to fit the remaining content, i.e. the left half of the image
+- If the user transforms the layer (scale, translate, rotate), the transformations apply only to the remaining content
+
+But the "destination-out" compositing mode only makes the erased pixels transparent. It does not actually remove them from the layer. The layer's bounding box includes the eraser strokes - even though they are transparent. The eraser strokes can actually _enlarge_ the layer's bounding box if the user erases outside the original bounds of the layer.
+
+So, we need a way to calculate the _visual_ bounds of the layer, i.e. the bounding box of all non-transparent pixels. We do this by rendering the layer to an offscreen canvas and reading back the pixel data to calculate the bounds. This process is costly, and we offload some of the work to a web worker to avoid blocking the main thread. Nevertheless, just getting that pixel data is expensive, scaling to the size of the layer.
+
+The usage of the buffer renderer module helps a lot here, as we only need to recalc the bounds when the user finishes a drawing action, not while they are drawing it.
+
+You'll see the relevant code for this in the transformer module. It encapsulates the bounds calculation logic and exposes an observable that holds the last-known visual bounds of the layer.
+
+The worker entrypoint is here invokeai/frontend/web/src/features/controlLayers/konva/CanvasWorkerModule.ts
+
+## Rasterizing layers
+
+Layers consist of a mix of vector and pixel data. For example, a brush stroke is a vector (i.e. array of points) and an image is pixel data.
+
+Ideally we could go straight from user input to pixel data, but this is not feasible for performance reasons. We'd need to write the images to an offscreen canvas, read back the pixel data, send it to the backend, get back the processed pixel data, write it to an offscreen canvas, then read back the pixel data again to update the layer. This would be too slow and block the main thread too much.
+
+So we use a hybrid approach. We keep the vector data in memory and render it to pixel data only when needed, e.g. when the user applies a backend filter or does a transformation on the canvas.
+
+This is unfortunately complicated but we couldn't figure out a more performance way to handle this.
+
+## Compositing layers to prepare for generation
+
+The canvas is a means to an end: provide strong user control and agency for image generation.
+
+When generating an image, the raster layers must be composited toegher into a single image that is sent to the backend. All inpaint masks are similarly composited together into a single mask image. Regional guidance and control layers are not composited together, they are sent as individual images.
+
+This is handled in invokeai/frontend/web/src/features/controlLayers/konva/CanvasCompositorModule.ts
+
+For each compositing task, the compositor creates a unique hash of the layer's state (e.g. objects, properties, etc.) and uses that to cache the resulting composited image's name (which ref a unique ref to the image file stored on disk). This avoids re-compositing layers that haven't changed since the last generation.
+
+## The generation bounding box
+
+Image generation models can only generate images up to certain sizes without causing VRAM OOMs. So we need to give the user a way to specify the size of the generation area. This is done via the "generation bounding box" tool, which is a rectangle that the user can resize and move around the canvas.
+
+Here's the module for it invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasBboxToolModule.ts
+
+Models all have width/height constraints - they must be multiples of a certain number (typically 8, 16 or 32). This is related to the internal "latents" representatino of images in diffusion models. So the generation bbox must be constrained to these multiples.
+
+## Staging generations
+
+The typical use pattern for generating images on canvas is to generate a number of variations and pick one or more to keep. This is supported via the "staging area", which is a horizontal strip of image thumbnails below the canvas. These staged images are rendered via React, not Konva.
+
+Once canvas generation starts, much of the canvas is locked down until the user finalizes the staging area, either by accepting a single image, adding one or more images as new layers, or discarding all staged images.
+
+The currently-selected staged image is previewed on the canvas and rendered via invokeai/frontend/web/src/features/controlLayers/konva/CanvasStagingAreaModule.ts
+
+When the user accepts a staged image, it is added as a new raster layer (there are other options for adding as control, saving directly to gallery, etc).
+
+This subsystem tracks generated images by watching the queue of generation tasks. The relevant code for queue tracking is in invokeai/frontend/web/src/features/controlLayers/components/StagingArea/state.ts
+
+## Future enhancements
+
+### Perf: Reduce the number of canvas elements
+
+Each layer has a Konva.Layer which has its own canvas element. Once you get too many of these, the browser starts to struggle.
+
+One idea to improve this would be to have a 3-layer system:
+
+- The active layer is its own Konva.Layer
+- All layers behind it are flattened into a single Konva.Layer
+- All layers in front of it are flattened into a single Konva.Layer
+
+When the user switches the active layer, we re-flatten the layers as needed. This would reduce the number of canvas elements to 3 regardless of how many layers there are. This would greatly improve performance, especially on lower-end devices.
+
+### Perf: Konva in a web worker
+
+All of the heavy konva rendering could be offloaded to a web worker. This would free up the main thread for user interactions and UI updates. The main thread would send user input and state changes to the worker, and the worker would send back rendered images to display.
+
+There used to be a hacky example of this on the Konva docs but I can't find it as of this writing. It requires proxying mouse and keyboard events to the worker, but wasn't too complicated. This could be a _huge_ perf win.
+
+### Abstract state bindings
+
+Currently the state bindings (redux, nanostores) are all over the place. There is a singleton module that handles much of the redux binding, but it's still a bit messy: invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApiModule.ts
+
+Many modules still directly subscribe to redux with their own selectors.
+
+Ideally we could have a more abstracted state binding system that could handle multiple backends (e.g. redux, nanostores, etc.) in a more uniform way. This would make it easier to manage state and reduce boilerplate code.
+
+### Do not lock down canvas as much during staging
+
+Currently, once the user starts generating images, much of the canvas is locked down until the user finalizes the staging area. This can be frustrating if the user wants to make small adjustments to layers or settings while previewing staged images, but it prevents footguns.
+
+For example, if the user changes the generation bbox size while staging, then queues up more generations, the output images may not match the bbox size, leading to confusion.
+
+It's more locked-down than it needs to be. Theoretically, most of the canvas could be interactive while staging. Just needs some careful through to not be too confusing.
From dfc3f4e9ad3593a19a9b065bb0fbf90547194bb5 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Wed, 15 Oct 2025 09:59:52 +1100
Subject: [PATCH 18/20] fix(ui): move logging setup to react code
---
.../frontend/web/src/app/components/InvokeAIUI.tsx | 10 ++++++++++
invokeai/frontend/web/src/app/logging/logger.ts | 9 ---------
2 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx
index 1fa0a5f3cd9..775a4c7a963 100644
--- a/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx
+++ b/invokeai/frontend/web/src/app/components/InvokeAIUI.tsx
@@ -1,5 +1,6 @@
import 'i18n';
+import { configureLogging } from 'app/logging/logger';
import { addStorageListeners } from 'app/store/enhancers/reduxRemember/driver';
import { $store } from 'app/store/nanostores/store';
import { createStore } from 'app/store/store';
@@ -7,6 +8,15 @@ import Loading from 'common/components/Loading/Loading';
import React, { lazy, memo, useEffect, useState } from 'react';
import { Provider } from 'react-redux';
+/*
+ * We need to configure logging before anything else happens - useLayoutEffect ensures we set this at the first
+ * possible opportunity.
+ *
+ * Once redux initializes, we will check the user's settings and update the logging config accordingly. See
+ * `useSyncLoggingConfig`.
+ */
+configureLogging(true, 'debug', '*');
+
const App = lazy(() => import('./App'));
const InvokeAIUI = () => {
diff --git a/invokeai/frontend/web/src/app/logging/logger.ts b/invokeai/frontend/web/src/app/logging/logger.ts
index 7c6b4ecdde0..6c843068df3 100644
--- a/invokeai/frontend/web/src/app/logging/logger.ts
+++ b/invokeai/frontend/web/src/app/logging/logger.ts
@@ -83,12 +83,3 @@ export const configureLogging = (
ROARR.write = createLogWriter({ styleOutput });
};
-
-/*
- * We need to configure logging before anything else happens - useLayoutEffect ensures we set this at the first
- * possible opportunity.
- *
- * Once redux initializes, we will check the user's settings and update the logging config accordingly. See
- * `useSyncLoggingConfig`.
- */
-configureLogging(true, 'debug', '*');
From da0508880e03d5d5ae87f451f3c90965be0f0b0b Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Wed, 15 Oct 2025 10:27:43 +1100
Subject: [PATCH 19/20] chore: ruff
---
invokeai/app/invocations/fields.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/invokeai/app/invocations/fields.py b/invokeai/app/invocations/fields.py
index 5a2d0810356..1bca7ec3f53 100644
--- a/invokeai/app/invocations/fields.py
+++ b/invokeai/app/invocations/fields.py
@@ -235,8 +235,6 @@ class ImageField(BaseModel):
image_name: str = Field(description="The name of the image")
-
-
class BoardField(BaseModel):
"""A board primitive field"""
From 3cf9ff66e064854605d87bf21a301743d902cf7a Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Wed, 15 Oct 2025 10:40:22 +1100
Subject: [PATCH 20/20] chore(ui): fix schema
---
invokeai/frontend/web/src/services/api/schema.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts
index 456d5f3d55e..b52f6eb74c6 100644
--- a/invokeai/frontend/web/src/services/api/schema.ts
+++ b/invokeai/frontend/web/src/services/api/schema.ts
@@ -6827,7 +6827,7 @@ export type components = {
/**
* Use Cache
* @description Whether or not to use the cache
- * @default true
+ * @default false
*/
use_cache?: boolean;
/**
@@ -19475,7 +19475,7 @@ export type components = {
/**
* Use Cache
* @description Whether or not to use the cache
- * @default true
+ * @default false
*/
use_cache?: boolean;
/**
@@ -19522,7 +19522,7 @@ export type components = {
/**
* Use Cache
* @description Whether or not to use the cache
- * @default true
+ * @default false
*/
use_cache?: boolean;
/**
@@ -19563,7 +19563,7 @@ export type components = {
/**
* Use Cache
* @description Whether or not to use the cache
- * @default true
+ * @default false
*/
use_cache?: boolean;
/**
@@ -20641,7 +20641,7 @@ export type components = {
/**
* Use Cache
* @description Whether or not to use the cache
- * @default true
+ * @default false
*/
use_cache?: boolean;
/**