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(() => {
+