From e70bedba7d16b4c928220286ec09d38f058d742c Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Fri, 28 Jul 2023 21:03:27 -0700 Subject: [PATCH 01/16] refactor(ModelManager): factor out _get_implementation method --- .../backend/model_management/model_manager.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index 2a82061a97..fbabd2fece 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -258,7 +258,7 @@ from .models import ( ModelConfigBase, ModelNotFoundException, InvalidModelException, - DuplicateModelException, + DuplicateModelException, ModelBase, ) # We are only starting to number the config file with release 3. @@ -361,7 +361,7 @@ class ModelManager(object): if model_key.startswith("_"): continue model_name, base_model, model_type = self.parse_key(model_key) - model_class = MODEL_CLASSES[base_model][model_type] + model_class = self._get_implementation(base_model, model_type) # alias for config file model_config["model_format"] = model_config.pop("format") self.models[model_key] = model_class.create_config(**model_config) @@ -446,7 +446,7 @@ class ModelManager(object): :param submode_typel: an ModelType enum indicating the portion of the model to retrieve (e.g. ModelType.Vae) """ - model_class = MODEL_CLASSES[base_model][model_type] + model_class = self._get_implementation(base_model, model_type) model_key = self.create_key(model_name, base_model, model_type) # if model not found try to find it (maybe file just pasted) @@ -475,7 +475,7 @@ class ModelManager(object): model_path = self.app_config.root_path / override_path model_type = submodel_type submodel_type = None - model_class = MODEL_CLASSES[base_model][model_type] + model_class = self._get_implementation(base_model, model_type) # TODO: path # TODO: is it accurate to use path as id @@ -513,6 +513,10 @@ class ModelManager(object): _cache=self.cache, ) + def _get_implementation(self, base_model: BaseModelType, model_type: ModelType) -> type[ModelBase]: + model_class = MODEL_CLASSES[base_model][model_type] + return model_class + def model_info( self, model_name: str, @@ -659,7 +663,7 @@ class ModelManager(object): if Path(path).is_relative_to(self.app_config.root_path): model_attributes["path"] = str(Path(path).relative_to(self.app_config.root_path)) - model_class = MODEL_CLASSES[base_model][model_type] + model_class = self._get_implementation(base_model, model_type) model_config = model_class.create_config(**model_attributes) model_key = self.create_key(model_name, base_model, model_type) @@ -837,7 +841,7 @@ class ModelManager(object): for model_key, model_config in self.models.items(): model_name, base_model, model_type = self.parse_key(model_key) - model_class = MODEL_CLASSES[base_model][model_type] + model_class = self._get_implementation(base_model, model_type) if model_class.save_to_config: # TODO: or exclude_unset better fits here? data_to_save[model_key] = model_config.dict(exclude_defaults=True, exclude={"error"}) @@ -888,7 +892,7 @@ class ModelManager(object): model_name, cur_base_model, cur_model_type = self.parse_key(model_key) model_path = self.app_config.root_path.absolute() / model_config.path if not model_path.exists(): - model_class = MODEL_CLASSES[cur_base_model][cur_model_type] + model_class = self._get_implementation(cur_base_model, cur_model_type) if model_class.save_to_config: model_config.error = ModelError.NotFound self.models.pop(model_key, None) @@ -904,7 +908,7 @@ class ModelManager(object): for cur_model_type in ModelType: if model_type is not None and cur_model_type != model_type: continue - model_class = MODEL_CLASSES[cur_base_model][cur_model_type] + model_class = self._get_implementation(cur_base_model, cur_model_type) models_dir = self.app_config.models_path / cur_base_model.value / cur_model_type.value if not models_dir.exists(): From dca685ac252055dff6dd38cfe6575994cc7385a0 Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Fri, 28 Jul 2023 21:11:00 -0700 Subject: [PATCH 02/16] refactor(ModelManager): refactor rescan-on-miss to exists() method --- .../backend/model_management/model_manager.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index fbabd2fece..5da4344b89 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -386,13 +386,21 @@ class ModelManager(object): model_name: str, base_model: BaseModelType, model_type: ModelType, + rescan = False ) -> bool: """ Given a model name, returns True if it is a valid identifier. """ model_key = self.create_key(model_name, base_model, model_type) - return model_key in self.models + exists = model_key in self.models + + # if model not found try to find it (maybe file just pasted) + if rescan and not exists: + self.scan_models_directory(base_model=base_model, model_type=model_type) + exists = model_key in self.models + + return exists @classmethod def create_key( @@ -449,11 +457,8 @@ class ModelManager(object): model_class = self._get_implementation(base_model, model_type) model_key = self.create_key(model_name, base_model, model_type) - # if model not found try to find it (maybe file just pasted) - if model_key not in self.models: - self.scan_models_directory(base_model=base_model, model_type=model_type) - if model_key not in self.models: - raise ModelNotFoundException(f"Model not found - {model_key}") + if not self.model_exists(model_name, base_model, model_type, rescan=True): + raise ModelNotFoundException(f"Model not found - {model_key}") model_config = self.models[model_key] model_path = self.app_config.root_path / model_config.path From b163ae6a4dcb9b5024888c33620db8ddcd01b037 Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Fri, 28 Jul 2023 21:30:20 -0700 Subject: [PATCH 03/16] refactor(ModelManager): factor out get_model_config --- invokeai/backend/model_management/model_manager.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index 5da4344b89..51053f92cc 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -460,7 +460,7 @@ class ModelManager(object): if not self.model_exists(model_name, base_model, model_type, rescan=True): raise ModelNotFoundException(f"Model not found - {model_key}") - model_config = self.models[model_key] + model_config = self._get_model_config(base_model, model_name, model_type) model_path = self.app_config.root_path / model_config.path if not model_path.exists(): @@ -518,6 +518,14 @@ class ModelManager(object): _cache=self.cache, ) + def _get_model_config(self, base_model, model_name, model_type) -> ModelConfigBase: + model_key = self.create_key(model_name, base_model, model_type) + try: + model_config = self.models[model_key] + except KeyError: + raise ModelNotFoundException(f"Model not found - {model_key}") + return model_config + def _get_implementation(self, base_model: BaseModelType, model_type: ModelType) -> type[ModelBase]: model_class = MODEL_CLASSES[base_model][model_type] return model_class From bc9a5038fdbb0c1f9bc88c5fa5ea86cdf994024b Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Fri, 28 Jul 2023 22:01:28 -0700 Subject: [PATCH 04/16] refactor(ModelManager): factor out get_model_path --- .../backend/model_management/model_manager.py | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index 51053f92cc..6c79b07959 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -451,37 +451,33 @@ class ModelManager(object): :param model_name: symbolic name of the model in models.yaml :param model_type: ModelType enum indicating the type of model to return :param base_model: BaseModelType enum indicating the base model used by this model - :param submode_typel: an ModelType enum indicating the portion of + :param submodel_type: an ModelType enum indicating the portion of the model to retrieve (e.g. ModelType.Vae) """ - model_class = self._get_implementation(base_model, model_type) model_key = self.create_key(model_name, base_model, model_type) if not self.model_exists(model_name, base_model, model_type, rescan=True): raise ModelNotFoundException(f"Model not found - {model_key}") model_config = self._get_model_config(base_model, model_name, model_type) - model_path = self.app_config.root_path / model_config.path + + model_path, is_submodel_override = self._get_model_path(model_config, submodel_type) + + if is_submodel_override: + model_type = submodel_type + submodel_type = None + + model_class = self._get_implementation(base_model, model_type) if not model_path.exists(): if model_class.save_to_config: self.models[model_key].error = ModelError.NotFound - raise Exception(f'Files for model "{model_key}" not found') + raise Exception(f'Files for model "{model_key}" not found at {model_path}') else: self.models.pop(model_key, None) raise ModelNotFoundException(f"Model not found - {model_key}") - # vae/movq override - # TODO: - if submodel_type is not None and hasattr(model_config, submodel_type): - override_path = getattr(model_config, submodel_type) - if override_path: - model_path = self.app_config.root_path / override_path - model_type = submodel_type - submodel_type = None - model_class = self._get_implementation(base_model, model_type) - # TODO: path # TODO: is it accurate to use path as id dst_convert_path = self._get_model_cache_path(model_path) @@ -518,6 +514,20 @@ class ModelManager(object): _cache=self.cache, ) + def _get_model_path(self, model_config: ModelConfigBase, submodel_type: SubModelType = None) -> (Path, bool): + model_path = model_config.path + is_submodel_override = False + + # Does the config explicitly override the submodel? + if submodel_type is not None and hasattr(model_config, submodel_type): + submodel_path = getattr(model_config, submodel_type) + if submodel_path is not None: + model_path = getattr(model_config, submodel_type) + is_submodel_override = True + + model_path = self.app_config.root_path / model_path + return model_path, is_submodel_override + def _get_model_config(self, base_model, model_name, model_type) -> ModelConfigBase: model_key = self.create_key(model_name, base_model, model_type) try: From 86b8b69e889cf0e972b75dc50ff119e86c315649 Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Fri, 28 Jul 2023 22:30:25 -0700 Subject: [PATCH 05/16] internal(ModelManager): add instantiate method --- invokeai/backend/model_management/model_manager.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index 6c79b07959..954b86283f 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -540,6 +540,15 @@ class ModelManager(object): model_class = MODEL_CLASSES[base_model][model_type] return model_class + def _instantiate(self, model_name: str, base_model: BaseModelType, model_type: ModelType, + submodel_type: SubModelType = None) -> ModelBase: + model_config = self._get_model_config(base_model, model_name, model_type) + model_path, is_submodel_override = self._get_model_path(model_config, submodel_type) + # FIXME: do non-overriden submodels get the right class? + constructor = self._get_implementation(base_model, model_type) + instance = constructor(model_path, base_model, model_type) + return instance + def model_info( self, model_name: str, From ccceb32a859c6a3fa46a4181e0c31555175de4b9 Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Sat, 29 Jul 2023 11:50:04 -0700 Subject: [PATCH 06/16] lint: formatting --- invokeai/backend/model_management/model_manager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index 954b86283f..c8a1405428 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -228,19 +228,19 @@ the root is the InvokeAI ROOTDIR. """ from __future__ import annotations -import os import hashlib +import os import textwrap -import yaml +import types from dataclasses import dataclass from pathlib import Path -from typing import Optional, List, Tuple, Union, Dict, Set, Callable, types from shutil import rmtree, move +from typing import Optional, List, Tuple, Union, Dict, Set, Callable import torch +import yaml from omegaconf import OmegaConf from omegaconf.dictconfig import DictConfig - from pydantic import BaseModel, Field import invokeai.backend.util.logging as logger From ff1c40747e96e01429650c69e3756e2e58a714d8 Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Sat, 29 Jul 2023 20:02:31 -0700 Subject: [PATCH 07/16] lint: formatting --- .../backend/model_management/model_manager.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index c8a1405428..8bade9cb04 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -258,7 +258,8 @@ from .models import ( ModelConfigBase, ModelNotFoundException, InvalidModelException, - DuplicateModelException, ModelBase, + DuplicateModelException, + ModelBase, ) # We are only starting to number the config file with release 3. @@ -381,13 +382,7 @@ class ModelManager(object): # causing otherwise unreferenced models to be removed from memory self._read_models() - def model_exists( - self, - model_name: str, - base_model: BaseModelType, - model_type: ModelType, - rescan = False - ) -> bool: + def model_exists(self, model_name: str, base_model: BaseModelType, model_type: ModelType, rescan=False) -> bool: """ Given a model name, returns True if it is a valid identifier. @@ -540,8 +535,9 @@ class ModelManager(object): model_class = MODEL_CLASSES[base_model][model_type] return model_class - def _instantiate(self, model_name: str, base_model: BaseModelType, model_type: ModelType, - submodel_type: SubModelType = None) -> ModelBase: + def _instantiate( + self, model_name: str, base_model: BaseModelType, model_type: ModelType, submodel_type: SubModelType = None + ) -> ModelBase: model_config = self._get_model_config(base_model, model_name, model_type) model_path, is_submodel_override = self._get_model_path(model_config, submodel_type) # FIXME: do non-overriden submodels get the right class? From adfd1e52f4e99956533f8dbfd6e9fffde4f3c521 Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Sun, 30 Jul 2023 11:53:12 -0700 Subject: [PATCH 08/16] refactor(model_manager): avoid copy/paste logic --- invokeai/backend/model_management/model_manager.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index 930ce119fd..ac71e2d2f8 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -382,10 +382,9 @@ class ModelManager(object): # causing otherwise unreferenced models to be removed from memory self._read_models() - def model_exists(self, model_name: str, base_model: BaseModelType, model_type: ModelType, rescan=False) -> bool: + def model_exists(self, model_name: str, base_model: BaseModelType, model_type: ModelType, *, rescan=False) -> bool: """ - Given a model name, returns True if it is a valid - identifier. + Given a model name, returns True if it is a valid identifier. """ model_key = self.create_key(model_name, base_model, model_type) exists = model_key in self.models @@ -393,7 +392,7 @@ class ModelManager(object): # if model not found try to find it (maybe file just pasted) if rescan and not exists: self.scan_models_directory(base_model=base_model, model_type=model_type) - exists = model_key in self.models + exists = self.model_exists(model_name, base_model, model_type, rescan=False) return exists From bacdf985f1738f52816f88f708c25899279e517f Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Mon, 31 Jul 2023 09:08:46 -0700 Subject: [PATCH 09/16] doc(model_manager): docstrings --- .../backend/model_management/model_manager.py | 24 ++++++++++++++++--- .../backend/model_management/models/vae.py | 16 +++++++------ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index ac71e2d2f8..139e65ac93 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -385,6 +385,11 @@ class ModelManager(object): def model_exists(self, model_name: str, base_model: BaseModelType, model_type: ModelType, *, rescan=False) -> bool: """ Given a model name, returns True if it is a valid identifier. + + :param model_name: symbolic name of the model in models.yaml + :param model_type: ModelType enum indicating the type of model to return + :param base_model: BaseModelType enum indicating the base model used by this model + :param rescan: if True, scan_models_directory """ model_key = self.create_key(model_name, base_model, model_type) exists = model_key in self.models @@ -470,7 +475,7 @@ class ModelManager(object): else: self.models.pop(model_key, None) - raise ModelNotFoundException(f"Model not found - {model_key}") + raise ModelNotFoundException(f'Files for model "{model_key}" not found at {model_path}') # TODO: path # TODO: is it accurate to use path as id @@ -508,7 +513,13 @@ class ModelManager(object): _cache=self.cache, ) - def _get_model_path(self, model_config: ModelConfigBase, submodel_type: SubModelType = None) -> (Path, bool): + def _get_model_path( + self, model_config: ModelConfigBase, submodel_type: Optional[SubModelType] = None + ) -> (Path, bool): + """Extract a model's filesystem path from its config. + + :return: The fully qualified Path of the module (or submodule). + """ model_path = model_config.path is_submodel_override = False @@ -523,6 +534,7 @@ class ModelManager(object): return model_path, is_submodel_override def _get_model_config(self, base_model, model_name, model_type) -> ModelConfigBase: + """Get a model's config object.""" model_key = self.create_key(model_name, base_model, model_type) try: model_config = self.models[model_key] @@ -531,12 +543,18 @@ class ModelManager(object): return model_config def _get_implementation(self, base_model: BaseModelType, model_type: ModelType) -> type[ModelBase]: + """Get the concrete implementation class for a specific model type.""" model_class = MODEL_CLASSES[base_model][model_type] return model_class def _instantiate( - self, model_name: str, base_model: BaseModelType, model_type: ModelType, submodel_type: SubModelType = None + self, + model_name: str, + base_model: BaseModelType, + model_type: ModelType, + submodel_type: Optional[SubModelType] = None, ) -> ModelBase: + """Make a new instance of this model, without loading it.""" model_config = self._get_model_config(base_model, model_name, model_type) model_path, is_submodel_override = self._get_model_path(model_config, submodel_type) # FIXME: do non-overriden submodels get the right class? diff --git a/invokeai/backend/model_management/models/vae.py b/invokeai/backend/model_management/models/vae.py index b15844bcf8..957a102ffb 100644 --- a/invokeai/backend/model_management/models/vae.py +++ b/invokeai/backend/model_management/models/vae.py @@ -1,9 +1,14 @@ import os -import torch -import safetensors from enum import Enum from pathlib import Path -from typing import Optional, Union, Literal +from typing import Optional + +import safetensors +import torch +from diffusers.utils import is_safetensors_available +from omegaconf import OmegaConf + +from invokeai.app.services.config import InvokeAIAppConfig from .base import ( ModelBase, ModelConfigBase, @@ -18,9 +23,6 @@ from .base import ( InvalidModelException, ModelNotFoundException, ) -from invokeai.app.services.config import InvokeAIAppConfig -from diffusers.utils import is_safetensors_available -from omegaconf import OmegaConf class VaeModelFormat(str, Enum): @@ -80,7 +82,7 @@ class VaeModel(ModelBase): @classmethod def detect_format(cls, path: str): if not os.path.exists(path): - raise ModelNotFoundException() + raise ModelNotFoundException(f"Does not exist as local file: {path}") if os.path.isdir(path): if os.path.exists(os.path.join(path, "config.json")): From 44bf308192629a6e91a26d38e6814a2a2f6b068e Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Sat, 5 Aug 2023 15:22:23 -0700 Subject: [PATCH 10/16] test(model_management): add a couple tests for _get_model_path --- .../backend/model_management/model_manager.py | 2 +- pyproject.toml | 2 +- tests/test_model_manager.py | 36 +++++++++++++++++++ .../configs/relative_sub.models.yaml | 15 ++++++++ .../sdxl/main/SDXL base 1_0/model_index.json | 0 .../sdxl/vae/sdxl-vae-fp16-fix/config.json | 0 6 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 tests/test_model_manager.py create mode 100644 tests/test_model_manager/configs/relative_sub.models.yaml create mode 100644 tests/test_model_manager/models/sdxl/main/SDXL base 1_0/model_index.json create mode 100644 tests/test_model_manager/models/sdxl/vae/sdxl-vae-fp16-fix/config.json diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index 3e8888be24..ebe7ffbbd0 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -533,7 +533,7 @@ class ModelManager(object): model_path = self.resolve_model_path(model_path) return model_path, is_submodel_override - def _get_model_config(self, base_model, model_name, model_type) -> ModelConfigBase: + def _get_model_config(self, base_model: BaseModelType, model_name: str, model_type: ModelType) -> ModelConfigBase: """Get a model's config object.""" model_key = self.create_key(model_name, base_model, model_type) try: diff --git a/pyproject.toml b/pyproject.toml index b3f12481a8..2ae297a6da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,7 @@ dependencies = [ "dev" = [ "pudb", ] -"test" = ["pytest>6.0.0", "pytest-cov", "black"] +"test" = ["pytest>6.0.0", "pytest-cov", "pytest-datadir", "black"] "xformers" = [ "xformers~=0.0.19; sys_platform!='darwin'", "triton; sys_platform=='linux'", diff --git a/tests/test_model_manager.py b/tests/test_model_manager.py new file mode 100644 index 0000000000..af0394eac2 --- /dev/null +++ b/tests/test_model_manager.py @@ -0,0 +1,36 @@ +from pathlib import Path + +import pytest + +from invokeai.app.services.config import InvokeAIAppConfig +from invokeai.backend import ModelManager, BaseModelType, ModelType, SubModelType + + +@pytest.fixture +def model_manager(datadir) -> ModelManager: + InvokeAIAppConfig.get_config(root=datadir) + return ModelManager(datadir / "configs" / "relative_sub.models.yaml") + + +def test_get_model_names(model_manager: ModelManager): + names = model_manager.model_names() + assert names[:2] == [ + ("SDXL base", BaseModelType.StableDiffusionXL, ModelType.Main), + ("SDXL with VAE", BaseModelType.StableDiffusionXL, ModelType.Main), + ] + + +def test_get_model_path_for_diffusers(model_manager: ModelManager, datadir: Path): + model_config = model_manager._get_model_config(BaseModelType.StableDiffusionXL, "SDXL base", ModelType.Main) + top_model_path, is_override = model_manager._get_model_path(model_config) + expected_model_path = datadir / "models" / "sdxl" / "main" / "SDXL base 1_0" + assert top_model_path == expected_model_path + assert not is_override + + +def test_get_model_path_for_overridden_vae(model_manager: ModelManager, datadir: Path): + model_config = model_manager._get_model_config(BaseModelType.StableDiffusionXL, "SDXL with VAE", ModelType.Main) + vae_model_path, is_override = model_manager._get_model_path(model_config, SubModelType.Vae) + expected_vae_path = datadir / "models" / "sdxl" / "vae" / "sdxl-vae-fp16-fix" + assert vae_model_path == expected_vae_path + assert is_override diff --git a/tests/test_model_manager/configs/relative_sub.models.yaml b/tests/test_model_manager/configs/relative_sub.models.yaml new file mode 100644 index 0000000000..757c50e3b5 --- /dev/null +++ b/tests/test_model_manager/configs/relative_sub.models.yaml @@ -0,0 +1,15 @@ +__metadata__: + version: 3.0.0 + +sdxl/main/SDXL base: + path: sdxl/main/SDXL base 1_0 + description: SDXL base v1.0 + variant: normal + format: diffusers + +sdxl/main/SDXL with VAE: + path: sdxl/main/SDXL base 1_0 + description: SDXL base v1.0 + vae: sdxl/vae/sdxl-vae-fp16-fix/ + variant: normal + format: diffusers diff --git a/tests/test_model_manager/models/sdxl/main/SDXL base 1_0/model_index.json b/tests/test_model_manager/models/sdxl/main/SDXL base 1_0/model_index.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_model_manager/models/sdxl/vae/sdxl-vae-fp16-fix/config.json b/tests/test_model_manager/models/sdxl/vae/sdxl-vae-fp16-fix/config.json new file mode 100644 index 0000000000..e69de29bb2 From 7f4c3870808541746e4f4bc51fdc601b8e19056f Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Sat, 5 Aug 2023 15:46:46 -0700 Subject: [PATCH 11/16] test(model_management): factor out name strings --- tests/test_model_manager.py | 14 ++++++++------ .../configs/relative_sub.models.yaml | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/test_model_manager.py b/tests/test_model_manager.py index af0394eac2..4314bad595 100644 --- a/tests/test_model_manager.py +++ b/tests/test_model_manager.py @@ -5,6 +5,9 @@ import pytest from invokeai.app.services.config import InvokeAIAppConfig from invokeai.backend import ModelManager, BaseModelType, ModelType, SubModelType +BASIC_MODEL_NAME = ("SDXL base", BaseModelType.StableDiffusionXL, ModelType.Main) +VAE_OVERRIDE_MODEL_NAME = ("SDXL with VAE", BaseModelType.StableDiffusionXL, ModelType.Main) + @pytest.fixture def model_manager(datadir) -> ModelManager: @@ -14,14 +17,11 @@ def model_manager(datadir) -> ModelManager: def test_get_model_names(model_manager: ModelManager): names = model_manager.model_names() - assert names[:2] == [ - ("SDXL base", BaseModelType.StableDiffusionXL, ModelType.Main), - ("SDXL with VAE", BaseModelType.StableDiffusionXL, ModelType.Main), - ] + assert names[:2] == [BASIC_MODEL_NAME, VAE_OVERRIDE_MODEL_NAME] def test_get_model_path_for_diffusers(model_manager: ModelManager, datadir: Path): - model_config = model_manager._get_model_config(BaseModelType.StableDiffusionXL, "SDXL base", ModelType.Main) + model_config = model_manager._get_model_config(BASIC_MODEL_NAME[1], BASIC_MODEL_NAME[0], BASIC_MODEL_NAME[2]) top_model_path, is_override = model_manager._get_model_path(model_config) expected_model_path = datadir / "models" / "sdxl" / "main" / "SDXL base 1_0" assert top_model_path == expected_model_path @@ -29,7 +29,9 @@ def test_get_model_path_for_diffusers(model_manager: ModelManager, datadir: Path def test_get_model_path_for_overridden_vae(model_manager: ModelManager, datadir: Path): - model_config = model_manager._get_model_config(BaseModelType.StableDiffusionXL, "SDXL with VAE", ModelType.Main) + model_config = model_manager._get_model_config( + VAE_OVERRIDE_MODEL_NAME[1], VAE_OVERRIDE_MODEL_NAME[0], VAE_OVERRIDE_MODEL_NAME[2] + ) vae_model_path, is_override = model_manager._get_model_path(model_config, SubModelType.Vae) expected_vae_path = datadir / "models" / "sdxl" / "vae" / "sdxl-vae-fp16-fix" assert vae_model_path == expected_vae_path diff --git a/tests/test_model_manager/configs/relative_sub.models.yaml b/tests/test_model_manager/configs/relative_sub.models.yaml index 757c50e3b5..3ec7a3adff 100644 --- a/tests/test_model_manager/configs/relative_sub.models.yaml +++ b/tests/test_model_manager/configs/relative_sub.models.yaml @@ -9,7 +9,7 @@ sdxl/main/SDXL base: sdxl/main/SDXL with VAE: path: sdxl/main/SDXL base 1_0 - description: SDXL base v1.0 + description: SDXL with customized VAE vae: sdxl/vae/sdxl-vae-fp16-fix/ variant: normal format: diffusers From 809705c30d2e1ef99a66b377aa1462b7791c650e Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:11:47 -0700 Subject: [PATCH 12/16] api(images): allow HEAD request on image/full --- invokeai/app/api/routers/images.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/invokeai/app/api/routers/images.py b/invokeai/app/api/routers/images.py index aff409e9e5..21fd09e051 100644 --- a/invokeai/app/api/routers/images.py +++ b/invokeai/app/api/routers/images.py @@ -1,22 +1,20 @@ import io from typing import Optional +from PIL import Image from fastapi import Body, HTTPException, Path, Query, Request, Response, UploadFile from fastapi.responses import FileResponse from fastapi.routing import APIRouter -from PIL import Image -from pydantic import BaseModel, Field +from pydantic import BaseModel from invokeai.app.invocations.metadata import ImageMetadata from invokeai.app.models.image import ImageCategory, ResourceOrigin from invokeai.app.services.image_record_storage import OffsetPaginatedResults -from invokeai.app.services.item_storage import PaginatedResults from invokeai.app.services.models.image_record import ( ImageDTO, ImageRecordChanges, ImageUrlsDTO, ) - from ..dependencies import ApiDependencies images_router = APIRouter(prefix="/v1/images", tags=["images"]) @@ -152,8 +150,9 @@ async def get_image_metadata( raise HTTPException(status_code=404) -@images_router.get( +@images_router.api_route( "/i/{image_name}/full", + methods=["GET", "HEAD"], operation_id="get_image_full", response_class=Response, responses={ From fe924daee38cfd16d34bf68502235639ebfec2ca Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Mon, 7 Aug 2023 10:22:12 -0400 Subject: [PATCH 13/16] add option to disable multiselect --- invokeai/frontend/web/src/app/types/invokeai.ts | 3 ++- .../src/features/gallery/hooks/useMultiselect.ts.ts | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts index b38790e0c9..827424fa7f 100644 --- a/invokeai/frontend/web/src/app/types/invokeai.ts +++ b/invokeai/frontend/web/src/app/types/invokeai.ts @@ -96,7 +96,8 @@ export type AppFeature = | 'consoleLogging' | 'dynamicPrompting' | 'batches' - | 'syncModels'; + | 'syncModels' + | 'multiselect'; /** * A disable-able Stable Diffusion feature diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useMultiselect.ts.ts b/invokeai/frontend/web/src/features/gallery/hooks/useMultiselect.ts.ts index b59a2f3d6f..a162c6788d 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useMultiselect.ts.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useMultiselect.ts.ts @@ -9,6 +9,7 @@ import { useListImagesQuery } from 'services/api/endpoints/images'; import { ImageDTO } from 'services/api/types'; import { selectionChanged } from '../store/gallerySlice'; import { imagesSelectors } from 'services/api/util'; +import { useFeatureStatus } from '../../system/hooks/useFeatureStatus'; const selector = createSelector( [stateSelector, selectListImagesBaseQueryArgs], @@ -33,11 +34,18 @@ export const useMultiselect = (imageDTO?: ImageDTO) => { }), }); + const isMultiSelectEnabled = useFeatureStatus('multiselect').isFeatureEnabled; + const handleClick = useCallback( (e: MouseEvent) => { if (!imageDTO) { return; } + if (!isMultiSelectEnabled) { + dispatch(selectionChanged([imageDTO])); + return; + } + if (e.shiftKey) { const rangeEndImageName = imageDTO.image_name; const lastSelectedImage = selection[selection.length - 1]?.image_name; @@ -71,7 +79,7 @@ export const useMultiselect = (imageDTO?: ImageDTO) => { dispatch(selectionChanged([imageDTO])); } }, - [dispatch, imageDTO, imageDTOs, selection] + [dispatch, imageDTO, imageDTOs, selection, isMultiSelectEnabled] ); const isSelected = useMemo( From 734a9e42717a91cd0142f31bed4c133254ebc32d Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Mon, 7 Aug 2023 10:23:02 -0400 Subject: [PATCH 14/16] invalidate board total when images deleted, only run date range logic if board has less than 20 images --- .../web/src/services/api/endpoints/images.ts | 101 ++++++++++-------- 1 file changed, 59 insertions(+), 42 deletions(-) diff --git a/invokeai/frontend/web/src/services/api/endpoints/images.ts b/invokeai/frontend/web/src/services/api/endpoints/images.ts index e093c1c33a..26c2268ca5 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/images.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/images.ts @@ -4,6 +4,7 @@ import { ASSETS_CATEGORIES, BoardId, IMAGE_CATEGORIES, + IMAGE_LIMIT, } from 'features/gallery/store/types'; import { keyBy } from 'lodash'; import { ApiFullTagDescription, LIST_TAG, api } from '..'; @@ -167,7 +168,14 @@ export const imagesApi = api.injectEndpoints({ }, }; }, - invalidatesTags: (result, error, imageDTOs) => [], + invalidatesTags: (result, error, { imageDTOs }) => { + // for now, assume bulk delete is all on one board + const boardId = imageDTOs[0]?.board_id + return [ + { type: 'BoardImagesTotal', id: boardId ?? 'none' }, + { type: 'BoardAssetsTotal', id: boardId ?? 'none' }, + ] + }, async onQueryStarted({ imageDTOs }, { dispatch, queryFulfilled }) { /** * Cache changes for `deleteImages`: @@ -288,11 +296,11 @@ export const imagesApi = api.injectEndpoints({ imageDTO.image_category ) ? boardsApi.endpoints.getBoardImagesTotal.select( - imageDTO.board_id ?? 'none' - )(getState()) + imageDTO.board_id ?? 'none' + )(getState()) : boardsApi.endpoints.getBoardAssetsTotal.select( - imageDTO.board_id ?? 'none' - )(getState()); + imageDTO.board_id ?? 'none' + )(getState()); // IF it eligible for insertion into existing $cache // "eligible" means either: @@ -718,11 +726,11 @@ export const imagesApi = api.injectEndpoints({ imageDTO.image_category ) ? boardsApi.endpoints.getBoardImagesTotal.select( - imageDTO.board_id ?? 'none' - )(getState()) + imageDTO.board_id ?? 'none' + )(getState()) : boardsApi.endpoints.getBoardAssetsTotal.select( - imageDTO.board_id ?? 'none' - )(getState()); + imageDTO.board_id ?? 'none' + )(getState()); const isCacheFullyPopulated = currentCache.data && currentCache.data.ids.length >= (total ?? 0); @@ -838,11 +846,11 @@ export const imagesApi = api.injectEndpoints({ imageDTO.image_category ) ? boardsApi.endpoints.getBoardImagesTotal.select( - imageDTO.board_id ?? 'none' - )(getState()) + imageDTO.board_id ?? 'none' + )(getState()) : boardsApi.endpoints.getBoardAssetsTotal.select( - imageDTO.board_id ?? 'none' - )(getState()); + imageDTO.board_id ?? 'none' + )(getState()); const isCacheFullyPopulated = currentCache.data && currentCache.data.ids.length >= (total ?? 0); @@ -889,18 +897,25 @@ export const imagesApi = api.injectEndpoints({ board_id, }, }), - invalidatesTags: (result, error, { board_id }) => [ - // update the destination board - { type: 'Board', id: board_id ?? 'none' }, - // update old board totals - { type: 'BoardImagesTotal', id: board_id ?? 'none' }, - { type: 'BoardAssetsTotal', id: board_id ?? 'none' }, - // update the no_board totals - { type: 'BoardImagesTotal', id: 'none' }, - { type: 'BoardAssetsTotal', id: 'none' }, - ], + invalidatesTags: (result, error, { imageDTOs, board_id }) => { + //assume all images are being moved from one board for now + const oldBoardId = imageDTOs[0]?.board_id; + return [ + // update the destination board + { type: 'Board', id: board_id ?? 'none' }, + // update new board totals + { type: 'BoardImagesTotal', id: board_id ?? 'none' }, + { type: 'BoardAssetsTotal', id: board_id ?? 'none' }, + // update old board totals + { type: 'BoardImagesTotal', id: oldBoardId ?? 'none' }, + { type: 'BoardAssetsTotal', id: oldBoardId ?? 'none' }, + // update the no_board totals + { type: 'BoardImagesTotal', id: 'none' }, + { type: 'BoardAssetsTotal', id: 'none' }, + ] + }, async onQueryStarted( - { board_id, imageDTOs }, + { board_id: new_board_id, imageDTOs }, { dispatch, queryFulfilled, getState } ) { try { @@ -920,7 +935,7 @@ export const imagesApi = api.injectEndpoints({ 'getImageDTO', image_name, (draft) => { - draft.board_id = board_id; + draft.board_id = new_board_id; } ) ); @@ -946,7 +961,7 @@ export const imagesApi = api.injectEndpoints({ ); const queryArgs = { - board_id, + board_id: new_board_id, categories, }; @@ -954,25 +969,27 @@ export const imagesApi = api.injectEndpoints({ queryArgs )(getState()); - const { data: total } = IMAGE_CATEGORIES.includes( + + const { data: previousTotal } = IMAGE_CATEGORIES.includes( imageDTO.image_category ) ? boardsApi.endpoints.getBoardImagesTotal.select( - imageDTO.board_id ?? 'none' - )(getState()) + new_board_id ?? 'none' + )(getState()) : boardsApi.endpoints.getBoardAssetsTotal.select( - imageDTO.board_id ?? 'none' - )(getState()); + new_board_id ?? 'none' + )(getState()); const isCacheFullyPopulated = - currentCache.data && currentCache.data.ids.length >= (total ?? 0); + currentCache.data && currentCache.data.ids.length >= (previousTotal ?? 0); - const isInDateRange = getIsImageInDateRange( + const isInDateRange = (previousTotal || 0) >= IMAGE_LIMIT ? getIsImageInDateRange( currentCache.data, imageDTO - ); + ) : true; if (isCacheFullyPopulated || isInDateRange) { + console.log("upserting") // *upsert* to $cache dispatch( imagesApi.util.updateQueryData( @@ -981,7 +998,7 @@ export const imagesApi = api.injectEndpoints({ (draft) => { imagesAdapter.upsertOne(draft, { ...imageDTO, - board_id, + board_id: new_board_id, }); } ) @@ -1088,19 +1105,19 @@ export const imagesApi = api.injectEndpoints({ imageDTO.image_category ) ? boardsApi.endpoints.getBoardImagesTotal.select( - imageDTO.board_id ?? 'none' - )(getState()) + imageDTO.board_id ?? 'none' + )(getState()) : boardsApi.endpoints.getBoardAssetsTotal.select( - imageDTO.board_id ?? 'none' - )(getState()); + imageDTO.board_id ?? 'none' + )(getState()); const isCacheFullyPopulated = currentCache.data && currentCache.data.ids.length >= (total ?? 0); - const isInDateRange = getIsImageInDateRange( + const isInDateRange = (total || 0) >= IMAGE_LIMIT ? getIsImageInDateRange( currentCache.data, imageDTO - ); + ) : true; if (isCacheFullyPopulated || isInDateRange) { // *upsert* to $cache @@ -1111,7 +1128,7 @@ export const imagesApi = api.injectEndpoints({ (draft) => { imagesAdapter.upsertOne(draft, { ...imageDTO, - board_id: undefined, + board_id: "none", }); } ) From 57e8ec9488361bcce44ff46c06a02476e1782c56 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 8 Aug 2023 12:49:54 +1000 Subject: [PATCH 15/16] chore(ui): lint/format --- .../web/src/services/api/endpoints/images.ts | 69 +++++++++---------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/invokeai/frontend/web/src/services/api/endpoints/images.ts b/invokeai/frontend/web/src/services/api/endpoints/images.ts index 26c2268ca5..0c52258f9d 100644 --- a/invokeai/frontend/web/src/services/api/endpoints/images.ts +++ b/invokeai/frontend/web/src/services/api/endpoints/images.ts @@ -170,11 +170,11 @@ export const imagesApi = api.injectEndpoints({ }, invalidatesTags: (result, error, { imageDTOs }) => { // for now, assume bulk delete is all on one board - const boardId = imageDTOs[0]?.board_id + const boardId = imageDTOs[0]?.board_id; return [ { type: 'BoardImagesTotal', id: boardId ?? 'none' }, { type: 'BoardAssetsTotal', id: boardId ?? 'none' }, - ] + ]; }, async onQueryStarted({ imageDTOs }, { dispatch, queryFulfilled }) { /** @@ -296,11 +296,11 @@ export const imagesApi = api.injectEndpoints({ imageDTO.image_category ) ? boardsApi.endpoints.getBoardImagesTotal.select( - imageDTO.board_id ?? 'none' - )(getState()) + imageDTO.board_id ?? 'none' + )(getState()) : boardsApi.endpoints.getBoardAssetsTotal.select( - imageDTO.board_id ?? 'none' - )(getState()); + imageDTO.board_id ?? 'none' + )(getState()); // IF it eligible for insertion into existing $cache // "eligible" means either: @@ -726,11 +726,11 @@ export const imagesApi = api.injectEndpoints({ imageDTO.image_category ) ? boardsApi.endpoints.getBoardImagesTotal.select( - imageDTO.board_id ?? 'none' - )(getState()) + imageDTO.board_id ?? 'none' + )(getState()) : boardsApi.endpoints.getBoardAssetsTotal.select( - imageDTO.board_id ?? 'none' - )(getState()); + imageDTO.board_id ?? 'none' + )(getState()); const isCacheFullyPopulated = currentCache.data && currentCache.data.ids.length >= (total ?? 0); @@ -846,11 +846,11 @@ export const imagesApi = api.injectEndpoints({ imageDTO.image_category ) ? boardsApi.endpoints.getBoardImagesTotal.select( - imageDTO.board_id ?? 'none' - )(getState()) + imageDTO.board_id ?? 'none' + )(getState()) : boardsApi.endpoints.getBoardAssetsTotal.select( - imageDTO.board_id ?? 'none' - )(getState()); + imageDTO.board_id ?? 'none' + )(getState()); const isCacheFullyPopulated = currentCache.data && currentCache.data.ids.length >= (total ?? 0); @@ -912,7 +912,7 @@ export const imagesApi = api.injectEndpoints({ // update the no_board totals { type: 'BoardImagesTotal', id: 'none' }, { type: 'BoardAssetsTotal', id: 'none' }, - ] + ]; }, async onQueryStarted( { board_id: new_board_id, imageDTOs }, @@ -969,27 +969,26 @@ export const imagesApi = api.injectEndpoints({ queryArgs )(getState()); - const { data: previousTotal } = IMAGE_CATEGORIES.includes( imageDTO.image_category ) ? boardsApi.endpoints.getBoardImagesTotal.select( - new_board_id ?? 'none' - )(getState()) + new_board_id ?? 'none' + )(getState()) : boardsApi.endpoints.getBoardAssetsTotal.select( - new_board_id ?? 'none' - )(getState()); + new_board_id ?? 'none' + )(getState()); const isCacheFullyPopulated = - currentCache.data && currentCache.data.ids.length >= (previousTotal ?? 0); + currentCache.data && + currentCache.data.ids.length >= (previousTotal ?? 0); - const isInDateRange = (previousTotal || 0) >= IMAGE_LIMIT ? getIsImageInDateRange( - currentCache.data, - imageDTO - ) : true; + const isInDateRange = + (previousTotal || 0) >= IMAGE_LIMIT + ? getIsImageInDateRange(currentCache.data, imageDTO) + : true; if (isCacheFullyPopulated || isInDateRange) { - console.log("upserting") // *upsert* to $cache dispatch( imagesApi.util.updateQueryData( @@ -1105,19 +1104,19 @@ export const imagesApi = api.injectEndpoints({ imageDTO.image_category ) ? boardsApi.endpoints.getBoardImagesTotal.select( - imageDTO.board_id ?? 'none' - )(getState()) + imageDTO.board_id ?? 'none' + )(getState()) : boardsApi.endpoints.getBoardAssetsTotal.select( - imageDTO.board_id ?? 'none' - )(getState()); + imageDTO.board_id ?? 'none' + )(getState()); const isCacheFullyPopulated = currentCache.data && currentCache.data.ids.length >= (total ?? 0); - const isInDateRange = (total || 0) >= IMAGE_LIMIT ? getIsImageInDateRange( - currentCache.data, - imageDTO - ) : true; + const isInDateRange = + (total || 0) >= IMAGE_LIMIT + ? getIsImageInDateRange(currentCache.data, imageDTO) + : true; if (isCacheFullyPopulated || isInDateRange) { // *upsert* to $cache @@ -1128,7 +1127,7 @@ export const imagesApi = api.injectEndpoints({ (draft) => { imagesAdapter.upsertOne(draft, { ...imageDTO, - board_id: "none", + board_id: 'none', }); } ) From e20af5aef022b6ec74a8c4d55a6553df26ed83b4 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 8 Aug 2023 12:00:05 +1000 Subject: [PATCH 16/16] feat(ui): add LoRA support to SDXL linear UI new graph modifier `addSDXLLoRasToGraph()` handles adding LoRA to the SDXL t2i and i2i graphs. --- .../lora/components/ParamLoraCollapse.tsx | 2 +- .../lora/components/ParamLoraList.tsx | 14 +- .../util/graphBuilders/addLoRAsToGraph.ts | 9 +- .../util/graphBuilders/addSDXLLoRAstoGraph.ts | 212 +++++++++++++++++ .../buildLinearSDXLImageToImageGraph.ts | 3 + .../buildLinearSDXLTextToImageGraph.ts | 3 + .../SDXLImageToImageTabParameters.tsx | 2 + .../SDXLTextToImageTabParameters.tsx | 2 + .../frontend/web/src/services/api/schema.d.ts | 222 ++++++++++++++++-- .../frontend/web/src/services/api/types.ts | 3 + 10 files changed, 436 insertions(+), 36 deletions(-) create mode 100644 invokeai/frontend/web/src/features/nodes/util/graphBuilders/addSDXLLoRAstoGraph.ts diff --git a/invokeai/frontend/web/src/features/lora/components/ParamLoraCollapse.tsx b/invokeai/frontend/web/src/features/lora/components/ParamLoraCollapse.tsx index e212efbfa2..c2edd94106 100644 --- a/invokeai/frontend/web/src/features/lora/components/ParamLoraCollapse.tsx +++ b/invokeai/frontend/web/src/features/lora/components/ParamLoraCollapse.tsx @@ -31,7 +31,7 @@ const ParamLoraCollapse = () => { } return ( - + diff --git a/invokeai/frontend/web/src/features/lora/components/ParamLoraList.tsx b/invokeai/frontend/web/src/features/lora/components/ParamLoraList.tsx index 835c315e5c..f10084e585 100644 --- a/invokeai/frontend/web/src/features/lora/components/ParamLoraList.tsx +++ b/invokeai/frontend/web/src/features/lora/components/ParamLoraList.tsx @@ -1,3 +1,4 @@ +import { Divider } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; import { stateSelector } from 'app/store/store'; import { useAppSelector } from 'app/store/storeHooks'; @@ -8,20 +9,21 @@ import ParamLora from './ParamLora'; const selector = createSelector( stateSelector, ({ lora }) => { - const { loras } = lora; - - return { loras }; + return { lorasArray: map(lora.loras) }; }, defaultSelectorOptions ); const ParamLoraList = () => { - const { loras } = useAppSelector(selector); + const { lorasArray } = useAppSelector(selector); return ( <> - {map(loras, (lora) => ( - + {lorasArray.map((lora, i) => ( + <> + {i > 0 && } + + ))} ); diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addLoRAsToGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addLoRAsToGraph.ts index bc0bfee8fd..cdd91d6e4f 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addLoRAsToGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addLoRAsToGraph.ts @@ -9,7 +9,6 @@ import { CLIP_SKIP, LORA_LOADER, MAIN_MODEL_LOADER, - ONNX_MODEL_LOADER, METADATA_ACCUMULATOR, NEGATIVE_CONDITIONING, POSITIVE_CONDITIONING, @@ -36,15 +35,11 @@ export const addLoRAsToGraph = ( | undefined; if (loraCount > 0) { - // Remove MAIN_MODEL_LOADER unet connection to feed it to LoRAs + // Remove modelLoaderNodeId unet connection to feed it to LoRAs graph.edges = graph.edges.filter( (e) => !( - e.source.node_id === MAIN_MODEL_LOADER && - ['unet'].includes(e.source.field) - ) && - !( - e.source.node_id === ONNX_MODEL_LOADER && + e.source.node_id === modelLoaderNodeId && ['unet'].includes(e.source.field) ) ); diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addSDXLLoRAstoGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addSDXLLoRAstoGraph.ts new file mode 100644 index 0000000000..c0f7f7ca82 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/addSDXLLoRAstoGraph.ts @@ -0,0 +1,212 @@ +import { RootState } from 'app/store/store'; +import { NonNullableGraph } from 'features/nodes/types/types'; +import { forEach, size } from 'lodash-es'; +import { + MetadataAccumulatorInvocation, + SDXLLoraLoaderInvocation, +} from 'services/api/types'; +import { + LORA_LOADER, + METADATA_ACCUMULATOR, + NEGATIVE_CONDITIONING, + POSITIVE_CONDITIONING, + SDXL_MODEL_LOADER, +} from './constants'; + +export const addSDXLLoRAsToGraph = ( + state: RootState, + graph: NonNullableGraph, + baseNodeId: string, + modelLoaderNodeId: string = SDXL_MODEL_LOADER +): void => { + /** + * LoRA nodes get the UNet and CLIP models from the main model loader and apply the LoRA to them. + * They then output the UNet and CLIP models references on to either the next LoRA in the chain, + * or to the inference/conditioning nodes. + * + * So we need to inject a LoRA chain into the graph. + */ + + const { loras } = state.lora; + const loraCount = size(loras); + const metadataAccumulator = graph.nodes[METADATA_ACCUMULATOR] as + | MetadataAccumulatorInvocation + | undefined; + + if (loraCount > 0) { + // Remove modelLoaderNodeId unet/clip/clip2 connections to feed it to LoRAs + graph.edges = graph.edges.filter( + (e) => + !( + e.source.node_id === modelLoaderNodeId && + ['unet'].includes(e.source.field) + ) && + !( + e.source.node_id === modelLoaderNodeId && + ['clip'].includes(e.source.field) + ) && + !( + e.source.node_id === modelLoaderNodeId && + ['clip2'].includes(e.source.field) + ) + ); + } + + // we need to remember the last lora so we can chain from it + let lastLoraNodeId = ''; + let currentLoraIndex = 0; + + forEach(loras, (lora) => { + const { model_name, base_model, weight } = lora; + const currentLoraNodeId = `${LORA_LOADER}_${model_name.replace('.', '_')}`; + + const loraLoaderNode: SDXLLoraLoaderInvocation = { + type: 'sdxl_lora_loader', + id: currentLoraNodeId, + is_intermediate: true, + lora: { model_name, base_model }, + weight, + }; + + // add the lora to the metadata accumulator + if (metadataAccumulator) { + metadataAccumulator.loras.push({ + lora: { model_name, base_model }, + weight, + }); + } + + // add to graph + graph.nodes[currentLoraNodeId] = loraLoaderNode; + if (currentLoraIndex === 0) { + // first lora = start the lora chain, attach directly to model loader + graph.edges.push({ + source: { + node_id: modelLoaderNodeId, + field: 'unet', + }, + destination: { + node_id: currentLoraNodeId, + field: 'unet', + }, + }); + + graph.edges.push({ + source: { + node_id: modelLoaderNodeId, + field: 'clip', + }, + destination: { + node_id: currentLoraNodeId, + field: 'clip', + }, + }); + + graph.edges.push({ + source: { + node_id: modelLoaderNodeId, + field: 'clip2', + }, + destination: { + node_id: currentLoraNodeId, + field: 'clip2', + }, + }); + } else { + // we are in the middle of the lora chain, instead connect to the previous lora + graph.edges.push({ + source: { + node_id: lastLoraNodeId, + field: 'unet', + }, + destination: { + node_id: currentLoraNodeId, + field: 'unet', + }, + }); + graph.edges.push({ + source: { + node_id: lastLoraNodeId, + field: 'clip', + }, + destination: { + node_id: currentLoraNodeId, + field: 'clip', + }, + }); + + graph.edges.push({ + source: { + node_id: lastLoraNodeId, + field: 'clip2', + }, + destination: { + node_id: currentLoraNodeId, + field: 'clip2', + }, + }); + } + + if (currentLoraIndex === loraCount - 1) { + // final lora, end the lora chain - we need to connect up to inference and conditioning nodes + graph.edges.push({ + source: { + node_id: currentLoraNodeId, + field: 'unet', + }, + destination: { + node_id: baseNodeId, + field: 'unet', + }, + }); + + graph.edges.push({ + source: { + node_id: currentLoraNodeId, + field: 'clip', + }, + destination: { + node_id: POSITIVE_CONDITIONING, + field: 'clip', + }, + }); + + graph.edges.push({ + source: { + node_id: currentLoraNodeId, + field: 'clip', + }, + destination: { + node_id: NEGATIVE_CONDITIONING, + field: 'clip', + }, + }); + + graph.edges.push({ + source: { + node_id: currentLoraNodeId, + field: 'clip2', + }, + destination: { + node_id: POSITIVE_CONDITIONING, + field: 'clip2', + }, + }); + + graph.edges.push({ + source: { + node_id: currentLoraNodeId, + field: 'clip2', + }, + destination: { + node_id: NEGATIVE_CONDITIONING, + field: 'clip2', + }, + }); + } + + // increment the lora for the next one in the chain + lastLoraNodeId = currentLoraNodeId; + currentLoraIndex += 1; + }); +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearSDXLImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearSDXLImageToImageGraph.ts index a260dbc467..0ec4e096d9 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearSDXLImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearSDXLImageToImageGraph.ts @@ -22,6 +22,7 @@ import { SDXL_LATENTS_TO_LATENTS, SDXL_MODEL_LOADER, } from './constants'; +import { addSDXLLoRAsToGraph } from './addSDXLLoRAstoGraph'; /** * Builds the Image to Image tab graph. @@ -364,6 +365,8 @@ export const buildLinearSDXLImageToImageGraph = ( }, }); + addSDXLLoRAsToGraph(state, graph, SDXL_LATENTS_TO_LATENTS, SDXL_MODEL_LOADER); + // Add Refiner if enabled if (shouldUseSDXLRefiner) { addSDXLRefinerToGraph(state, graph, SDXL_LATENTS_TO_LATENTS); diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearSDXLTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearSDXLTextToImageGraph.ts index c10e7831d3..21b7c1e0ac 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearSDXLTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildLinearSDXLTextToImageGraph.ts @@ -4,6 +4,7 @@ import { NonNullableGraph } from 'features/nodes/types/types'; import { initialGenerationState } from 'features/parameters/store/generationSlice'; import { addDynamicPromptsToGraph } from './addDynamicPromptsToGraph'; import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph'; +import { addSDXLLoRAsToGraph } from './addSDXLLoRAstoGraph'; import { addSDXLRefinerToGraph } from './addSDXLRefinerToGraph'; import { addWatermarkerToGraph } from './addWatermarkerToGraph'; import { @@ -246,6 +247,8 @@ export const buildLinearSDXLTextToImageGraph = ( }, }); + addSDXLLoRAsToGraph(state, graph, SDXL_TEXT_TO_LATENTS, SDXL_MODEL_LOADER); + // Add Refiner if enabled if (shouldUseSDXLRefiner) { addSDXLRefinerToGraph(state, graph, SDXL_TEXT_TO_LATENTS); diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLImageToImageTabParameters.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLImageToImageTabParameters.tsx index c0b143a557..edc92a56c8 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLImageToImageTabParameters.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLImageToImageTabParameters.tsx @@ -4,6 +4,7 @@ import ProcessButtons from 'features/parameters/components/ProcessButtons/Proces import ParamSDXLPromptArea from './ParamSDXLPromptArea'; import ParamSDXLRefinerCollapse from './ParamSDXLRefinerCollapse'; import SDXLImageToImageTabCoreParameters from './SDXLImageToImageTabCoreParameters'; +import ParamLoraCollapse from 'features/lora/components/ParamLoraCollapse'; const SDXLImageToImageTabParameters = () => { return ( @@ -12,6 +13,7 @@ const SDXLImageToImageTabParameters = () => { + diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLTextToImageTabParameters.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLTextToImageTabParameters.tsx index 35bc0b4284..325fd7d881 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLTextToImageTabParameters.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLTextToImageTabParameters.tsx @@ -4,6 +4,7 @@ import ProcessButtons from 'features/parameters/components/ProcessButtons/Proces import TextToImageTabCoreParameters from 'features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters'; import ParamSDXLPromptArea from './ParamSDXLPromptArea'; import ParamSDXLRefinerCollapse from './ParamSDXLRefinerCollapse'; +import ParamLoraCollapse from 'features/lora/components/ParamLoraCollapse'; const SDXLTextToImageTabParameters = () => { return ( @@ -12,6 +13,7 @@ const SDXLTextToImageTabParameters = () => { + diff --git a/invokeai/frontend/web/src/services/api/schema.d.ts b/invokeai/frontend/web/src/services/api/schema.d.ts index 6574ec4909..fc3397820e 100644 --- a/invokeai/frontend/web/src/services/api/schema.d.ts +++ b/invokeai/frontend/web/src/services/api/schema.d.ts @@ -1443,7 +1443,7 @@ export type components = { * @description The nodes in this graph */ nodes?: { - [key: string]: (components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRawPromptInvocation"] | components["schemas"]["SDXLRefinerRawPromptInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SDXLTextToLatentsInvocation"] | components["schemas"]["SDXLLatentsToLatentsInvocation"] | components["schemas"]["ONNXPromptInvocation"] | components["schemas"]["ONNXTextToLatentsInvocation"] | components["schemas"]["ONNXLatentsToImageInvocation"] | components["schemas"]["ONNXSD1ModelLoaderInvocation"] | components["schemas"]["OnnxModelLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["ParamStringInvocation"] | components["schemas"]["ParamPromptInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]) | undefined; + [key: string]: (components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["SDXLLoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRawPromptInvocation"] | components["schemas"]["SDXLRefinerRawPromptInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageLuminosityAdjustmentInvocation"] | components["schemas"]["ImageSaturationAdjustmentInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SDXLTextToLatentsInvocation"] | components["schemas"]["SDXLLatentsToLatentsInvocation"] | components["schemas"]["ONNXPromptInvocation"] | components["schemas"]["ONNXTextToLatentsInvocation"] | components["schemas"]["ONNXLatentsToImageInvocation"] | components["schemas"]["ONNXSD1ModelLoaderInvocation"] | components["schemas"]["OnnxModelLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["ParamStringInvocation"] | components["schemas"]["ParamPromptInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]) | undefined; }; /** * Edges @@ -1486,7 +1486,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: (components["schemas"]["ImageOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LoraLoaderOutput"] | components["schemas"]["VaeLoaderOutput"] | components["schemas"]["MetadataAccumulatorOutput"] | components["schemas"]["CompelOutput"] | components["schemas"]["ClipSkipInvocationOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["ONNXModelLoaderOutput"] | components["schemas"]["PromptOutput"] | components["schemas"]["PromptCollectionOutput"] | components["schemas"]["IntOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["IntCollectionOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["GraphInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"]) | undefined; + [key: string]: (components["schemas"]["ImageOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LoraLoaderOutput"] | components["schemas"]["SDXLLoraLoaderOutput"] | components["schemas"]["VaeLoaderOutput"] | components["schemas"]["MetadataAccumulatorOutput"] | components["schemas"]["CompelOutput"] | components["schemas"]["ClipSkipInvocationOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["ONNXModelLoaderOutput"] | components["schemas"]["PromptOutput"] | components["schemas"]["PromptCollectionOutput"] | components["schemas"]["IntOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["IntCollectionOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["GraphInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"]) | undefined; }; /** * Errors @@ -1904,6 +1904,40 @@ export type components = { */ image_name: string; }; + /** + * ImageHueAdjustmentInvocation + * @description Adjusts the Hue of an image. + */ + ImageHueAdjustmentInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default img_hue_adjust + * @enum {string} + */ + type?: "img_hue_adjust"; + /** + * Image + * @description The image to adjust + */ + image?: components["schemas"]["ImageField"]; + /** + * Hue + * @description The degrees by which to rotate the hue, 0-360 + * @default 0 + */ + hue?: number; + }; /** * ImageInverseLerpInvocation * @description Inverse linear interpolation of all pixels of an image @@ -1984,6 +2018,40 @@ export type components = { */ max?: number; }; + /** + * ImageLuminosityAdjustmentInvocation + * @description Adjusts the Luminosity (Value) of an image. + */ + ImageLuminosityAdjustmentInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default img_luminosity_adjust + * @enum {string} + */ + type?: "img_luminosity_adjust"; + /** + * Image + * @description The image to adjust + */ + image?: components["schemas"]["ImageField"]; + /** + * Luminosity + * @description The factor by which to adjust the luminosity (value) + * @default 1 + */ + luminosity?: number; + }; /** * ImageMetadata * @description An image's generation metadata @@ -2239,6 +2307,40 @@ export type components = { */ resample_mode?: "nearest" | "box" | "bilinear" | "hamming" | "bicubic" | "lanczos"; }; + /** + * ImageSaturationAdjustmentInvocation + * @description Adjusts the Saturation of an image. + */ + ImageSaturationAdjustmentInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default img_saturation_adjust + * @enum {string} + */ + type?: "img_saturation_adjust"; + /** + * Image + * @description The image to adjust + */ + image?: components["schemas"]["ImageField"]; + /** + * Saturation + * @description The factor by which to adjust the saturation + * @default 1 + */ + saturation?: number; + }; /** * ImageScaleInvocation * @description Scales an image by a factor @@ -4912,6 +5014,82 @@ export type components = { */ denoising_end?: number; }; + /** + * SDXLLoraLoaderInvocation + * @description Apply selected lora to unet and text_encoder. + */ + SDXLLoraLoaderInvocation: { + /** + * Id + * @description The id of this node. Must be unique among all nodes. + */ + id: string; + /** + * Is Intermediate + * @description Whether or not this node is an intermediate node. + * @default false + */ + is_intermediate?: boolean; + /** + * Type + * @default sdxl_lora_loader + * @enum {string} + */ + type?: "sdxl_lora_loader"; + /** + * Lora + * @description Lora model name + */ + lora?: components["schemas"]["LoRAModelField"]; + /** + * Weight + * @description With what weight to apply lora + * @default 0.75 + */ + weight?: number; + /** + * Unet + * @description UNet model for applying lora + */ + unet?: components["schemas"]["UNetField"]; + /** + * Clip + * @description Clip model for applying lora + */ + clip?: components["schemas"]["ClipField"]; + /** + * Clip2 + * @description Clip2 model for applying lora + */ + clip2?: components["schemas"]["ClipField"]; + }; + /** + * SDXLLoraLoaderOutput + * @description Model loader output + */ + SDXLLoraLoaderOutput: { + /** + * Type + * @default sdxl_lora_loader_output + * @enum {string} + */ + type?: "sdxl_lora_loader_output"; + /** + * Unet + * @description UNet submodel + */ + unet?: components["schemas"]["UNetField"]; + /** + * Clip + * @description Tokenizer and text_encoder submodels + */ + clip?: components["schemas"]["ClipField"]; + /** + * Clip2 + * @description Tokenizer2 and text_encoder2 submodels + */ + clip2?: components["schemas"]["ClipField"]; + }; /** * SDXLModelLoaderInvocation * @description Loads an sdxl base model, outputting its submodels. @@ -5961,6 +6139,24 @@ export type components = { */ image?: components["schemas"]["ImageField"]; }; + /** + * ControlNetModelFormat + * @description An enumeration. + * @enum {string} + */ + ControlNetModelFormat: "checkpoint" | "diffusers"; + /** + * StableDiffusionXLModelFormat + * @description An enumeration. + * @enum {string} + */ + StableDiffusionXLModelFormat: "checkpoint" | "diffusers"; + /** + * StableDiffusion1ModelFormat + * @description An enumeration. + * @enum {string} + */ + StableDiffusion1ModelFormat: "checkpoint" | "diffusers"; /** * StableDiffusionOnnxModelFormat * @description An enumeration. @@ -5973,24 +6169,6 @@ export type components = { * @enum {string} */ StableDiffusion2ModelFormat: "checkpoint" | "diffusers"; - /** - * StableDiffusion1ModelFormat - * @description An enumeration. - * @enum {string} - */ - StableDiffusion1ModelFormat: "checkpoint" | "diffusers"; - /** - * StableDiffusionXLModelFormat - * @description An enumeration. - * @enum {string} - */ - StableDiffusionXLModelFormat: "checkpoint" | "diffusers"; - /** - * ControlNetModelFormat - * @description An enumeration. - * @enum {string} - */ - ControlNetModelFormat: "checkpoint" | "diffusers"; }; responses: never; parameters: never; @@ -6101,7 +6279,7 @@ export type operations = { }; requestBody: { content: { - "application/json": components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRawPromptInvocation"] | components["schemas"]["SDXLRefinerRawPromptInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SDXLTextToLatentsInvocation"] | components["schemas"]["SDXLLatentsToLatentsInvocation"] | components["schemas"]["ONNXPromptInvocation"] | components["schemas"]["ONNXTextToLatentsInvocation"] | components["schemas"]["ONNXLatentsToImageInvocation"] | components["schemas"]["ONNXSD1ModelLoaderInvocation"] | components["schemas"]["OnnxModelLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["ParamStringInvocation"] | components["schemas"]["ParamPromptInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; + "application/json": components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["SDXLLoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRawPromptInvocation"] | components["schemas"]["SDXLRefinerRawPromptInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageLuminosityAdjustmentInvocation"] | components["schemas"]["ImageSaturationAdjustmentInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SDXLTextToLatentsInvocation"] | components["schemas"]["SDXLLatentsToLatentsInvocation"] | components["schemas"]["ONNXPromptInvocation"] | components["schemas"]["ONNXTextToLatentsInvocation"] | components["schemas"]["ONNXLatentsToImageInvocation"] | components["schemas"]["ONNXSD1ModelLoaderInvocation"] | components["schemas"]["OnnxModelLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["ParamStringInvocation"] | components["schemas"]["ParamPromptInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; }; }; responses: { @@ -6138,7 +6316,7 @@ export type operations = { }; requestBody: { content: { - "application/json": components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRawPromptInvocation"] | components["schemas"]["SDXLRefinerRawPromptInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SDXLTextToLatentsInvocation"] | components["schemas"]["SDXLLatentsToLatentsInvocation"] | components["schemas"]["ONNXPromptInvocation"] | components["schemas"]["ONNXTextToLatentsInvocation"] | components["schemas"]["ONNXLatentsToImageInvocation"] | components["schemas"]["ONNXSD1ModelLoaderInvocation"] | components["schemas"]["OnnxModelLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["ParamStringInvocation"] | components["schemas"]["ParamPromptInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; + "application/json": components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["SDXLLoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["MetadataAccumulatorInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRawPromptInvocation"] | components["schemas"]["SDXLRefinerRawPromptInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageLuminosityAdjustmentInvocation"] | components["schemas"]["ImageSaturationAdjustmentInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SDXLTextToLatentsInvocation"] | components["schemas"]["SDXLLatentsToLatentsInvocation"] | components["schemas"]["ONNXPromptInvocation"] | components["schemas"]["ONNXTextToLatentsInvocation"] | components["schemas"]["ONNXLatentsToImageInvocation"] | components["schemas"]["ONNXSD1ModelLoaderInvocation"] | components["schemas"]["OnnxModelLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["ParamStringInvocation"] | components["schemas"]["ParamPromptInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]; }; }; responses: { diff --git a/invokeai/frontend/web/src/services/api/types.ts b/invokeai/frontend/web/src/services/api/types.ts index ca9dbb3aeb..e7e3accdad 100644 --- a/invokeai/frontend/web/src/services/api/types.ts +++ b/invokeai/frontend/web/src/services/api/types.ts @@ -166,6 +166,9 @@ export type OnnxModelLoaderInvocation = TypeReq< export type LoraLoaderInvocation = TypeReq< components['schemas']['LoraLoaderInvocation'] >; +export type SDXLLoraLoaderInvocation = TypeReq< + components['schemas']['SDXLLoraLoaderInvocation'] +>; export type MetadataAccumulatorInvocation = TypeReq< components['schemas']['MetadataAccumulatorInvocation'] >;