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 35b6f8dac2..259cd0ed3d 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 @@ -15,7 +15,7 @@ import { refImageModelChanged, selectRefImagesSlice } from 'features/controlLaye import { selectCanvasSlice } from 'features/controlLayers/store/selectors'; import { getEntityIdentifier, isFLUXReduxConfig, isIPAdapterConfig } from 'features/controlLayers/store/types'; import { modelSelected } from 'features/parameters/store/actions'; -import { postProcessingModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice'; +import { postProcessingModelChanged, tileControlnetModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice'; import { zParameterCLIPEmbedModel, zParameterSpandrelImageToImageModel, @@ -28,6 +28,7 @@ import type { AnyModelConfig } from 'services/api/types'; import { isCLIPEmbedModelConfig, isControlLayerModelConfig, + isControlNetModelConfig, isFluxReduxModelConfig, isFluxVAEModelConfig, isIPAdapterModelConfig, @@ -71,6 +72,7 @@ export const addModelsLoadedListener = (startAppListening: AppStartListening) => handleControlAdapterModels(models, state, dispatch, log); handlePostProcessingModel(models, state, dispatch, log); handleUpscaleModel(models, state, dispatch, log); + handleTileControlNetModel(models, state, dispatch, log); handleIPAdapterModels(models, state, dispatch, log); handleT5EncoderModels(models, state, dispatch, log); handleCLIPEmbedModels(models, state, dispatch, log); @@ -345,6 +347,46 @@ const handleUpscaleModel: ModelHandler = (models, state, dispatch, log) => { } }; +const handleTileControlNetModel: ModelHandler = (models, state, dispatch, log) => { + const selectedTileControlNetModel = state.upscale.tileControlnetModel; + const controlNetModels = models.filter(isControlNetModelConfig); + + // If the currently selected model is available, we don't need to do anything + if (selectedTileControlNetModel && controlNetModels.some((m) => m.key === selectedTileControlNetModel.key)) { + return; + } + + // Find a model with "Tile" in the name, case-insensitive + const tileModel = controlNetModels.find((m) => m.name.toLowerCase().includes('tile')); + + // If we have a tile model, select it + if (tileModel) { + log.debug( + { selectedTileControlNetModel, tileModel }, + 'No selected tile ControlNet model or selected model is not available, selecting tile model' + ); + dispatch(tileControlnetModelChanged(tileModel)); + return; + } + + // Otherwise, select the first available ControlNet model + const firstModel = controlNetModels[0] || null; + if (firstModel) { + log.debug( + { selectedTileControlNetModel, firstModel }, + 'No tile ControlNet model found, selecting first available ControlNet model' + ); + dispatch(tileControlnetModelChanged(firstModel)); + return; + } + + // No available models, we should clear the selected model - but only if we have one selected + if (selectedTileControlNetModel) { + log.debug({ selectedTileControlNetModel }, 'Selected tile ControlNet model is not available, clearing'); + dispatch(tileControlnetModelChanged(null)); + } +}; + const handleT5EncoderModels: ModelHandler = (models, state, dispatch, log) => { const selectedT5EncoderModel = state.params.t5EncoderModel; const t5EncoderModels = models.filter((m) => isT5EncoderModelConfig(m)); diff --git a/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamTileControlNetModel.tsx b/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamTileControlNetModel.tsx new file mode 100644 index 0000000000..2806824695 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Upscale/ParamTileControlNetModel.tsx @@ -0,0 +1,38 @@ +import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useModelCombobox } from 'common/hooks/useModelCombobox'; +import { selectTileControlNetModel, tileControlnetModelChanged } from 'features/parameters/store/upscaleSlice'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useControlNetModels } from 'services/api/hooks/modelsByType'; +import type { ControlNetModelConfig } from 'services/api/types'; + +const ParamTileControlNetModel = () => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const tileControlNetModel = useAppSelector(selectTileControlNetModel); + const [modelConfigs, { isLoading }] = useControlNetModels(); + + const _onChange = useCallback( + (controlNetModel: ControlNetModelConfig | null) => { + dispatch(tileControlnetModelChanged(controlNetModel)); + }, + [dispatch] + ); + + const { options, value, onChange, noOptionsMessage } = useModelCombobox({ + modelConfigs, + onChange: _onChange, + selectedModel: tileControlNetModel, + isLoading, + }); + + return ( + + {t('controlLayers.controlNet')} + + + ); +}; + +export default memo(ParamTileControlNetModel); \ No newline at end of file diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/UpscaleSettingsAccordion/UpscaleSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/UpscaleSettingsAccordion/UpscaleSettingsAccordion.tsx index 34b69e931f..2f3b6ebbfb 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/UpscaleSettingsAccordion/UpscaleSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/UpscaleSettingsAccordion/UpscaleSettingsAccordion.tsx @@ -5,6 +5,7 @@ import { roundDownToMultiple } from 'common/util/roundDownToMultiple'; import ParamCreativity from 'features/parameters/components/Upscale/ParamCreativity'; import ParamSpandrelModel from 'features/parameters/components/Upscale/ParamSpandrelModel'; import ParamStructure from 'features/parameters/components/Upscale/ParamStructure'; +import ParamTileControlNetModel from 'features/parameters/components/Upscale/ParamTileControlNetModel'; import { selectUpscaleSlice } from 'features/parameters/store/upscaleSlice'; import { getGridSize } from 'features/parameters/util/optimalDimension'; import { UpscaleScaleSlider } from 'features/settingsAccordions/components/UpscaleSettingsAccordion/UpscaleScaleSlider'; @@ -70,6 +71,7 @@ export const UpscaleSettingsAccordion = memo(() => { +