diff --git a/invokeai/backend/install/model_install_backend.py b/invokeai/backend/install/model_install_backend.py index daddac029c..8ce5dc5322 100644 --- a/invokeai/backend/install/model_install_backend.py +++ b/invokeai/backend/install/model_install_backend.py @@ -75,14 +75,6 @@ LEGACY_CONFIGS = { } -@dataclass -class ModelInstallList: - """Class for listing models to be installed/removed""" - - install_models: List[str] = field(default_factory=list) - remove_models: List[str] = field(default_factory=list) - - @dataclass class InstallSelections: install_models: List[str] = field(default_factory=list) @@ -100,6 +92,7 @@ class ModelLoadInfo: installed: bool = False recommended: bool = False default: bool = False + requires: Optional[List[str]] = field(default_factory=list) class ModelInstall(object): @@ -137,8 +130,6 @@ class ModelInstall(object): # supplement with entries in models.yaml installed_models = [x for x in self.mgr.list_models()] - # suppresses autoloaded models - # installed_models = [x for x in self.mgr.list_models() if not self._is_autoloaded(x)] for md in installed_models: base = md["base_model"] @@ -170,9 +161,12 @@ class ModelInstall(object): def list_models(self, model_type): installed = self.mgr.list_models(model_type=model_type) + print() print(f"Installed models of type `{model_type}`:") + print(f"{'Model Key':50} Model Path") for i in installed: - print(f"{i['model_name']}\t{i['base_model']}\t{i['path']}") + print(f"{'/'.join([i['base_model'],i['model_type'],i['model_name']]):50} {i['path']}") + print() # logic here a little reversed to maintain backward compatibility def starter_models(self, all_models: bool = False) -> Set[str]: @@ -210,6 +204,8 @@ class ModelInstall(object): job += 1 # add requested models + self._remove_installed(selections.install_models) + self._add_required_models(selections.install_models) for path in selections.install_models: logger.info(f"Installing {path} [{job}/{jobs}]") try: @@ -269,6 +265,26 @@ class ModelInstall(object): return models_installed + def _remove_installed(self, model_list: List[str]): + all_models = self.all_models() + for path in model_list: + key = self.reverse_paths.get(path) + if key and all_models[key].installed: + logger.warning(f"{path} already installed. Skipping.") + model_list.remove(path) + + def _add_required_models(self, model_list: List[str]): + additional_models = [] + all_models = self.all_models() + for path in model_list: + if not (key := self.reverse_paths.get(path)): + continue + for requirement in all_models[key].requires: + requirement_key = self.reverse_paths.get(requirement) + if not all_models[requirement_key].installed: + additional_models.append(requirement) + model_list.extend(additional_models) + # install a model from a local path. The optional info parameter is there to prevent # the model from being probed twice in the event that it has already been probed. def _install_path(self, path: Path, info: ModelProbeInfo = None) -> AddModelResult: diff --git a/invokeai/configs/INITIAL_MODELS.yaml b/invokeai/configs/INITIAL_MODELS.yaml index e250b5efba..f3dbc11c2a 100644 --- a/invokeai/configs/INITIAL_MODELS.yaml +++ b/invokeai/configs/INITIAL_MODELS.yaml @@ -103,3 +103,35 @@ sd-1/lora/LowRA: recommended: True sd-1/lora/Ink scenery: path: https://civitai.com/api/download/models/83390 +sd-1/ip_adapter/ip_adapter_sd15: + repo_id: InvokeAI/ip_adapter_sd15 + recommended: True + requires: + - InvokeAI/ip_adapter_sd_image_encoder + description: IP-Adapter for SD 1.5 models +sd-1/ip_adapter/ip_adapter_plus_sd15: + repo_id: InvokeAI/ip_adapter_plus_sd15 + recommended: False + requires: + - InvokeAI/ip_adapter_sd_image_encoder + description: Refined IP-Adapter for SD 1.5 models +sd-1/ip_adapter/ip_adapter_plus_face_sd15: + repo_id: InvokeAI/ip_adapter_plus_face_sd15 + recommended: False + requires: + - InvokeAI/ip_adapter_sd_image_encoder + description: Refined IP-Adapter for SD 1.5 models, adapted for faces +sdxl/ip_adapter/ip_adapter_sdxl: + repo_id: InvokeAI/ip_adapter_sdxl + recommended: False + requires: + - InvokeAI/ip_adapter_sdxl_image_encoder + description: IP-Adapter for SDXL models +any/clip_vision/ip_adapter_sd_image_encoder: + repo_id: InvokeAI/ip_adapter_sd_image_encoder + recommended: False + description: Required model for using IP-Adapters with SD-1/2 models +any/clip_vision/ip_adapter_sdxl_image_encoder: + repo_id: InvokeAI/ip_adapter_sdxl_image_encoder + recommended: False + description: Required model for using IP-Adapters with SDXL models diff --git a/invokeai/frontend/install/model_install.py b/invokeai/frontend/install/model_install.py index 64ad3a7d77..b8a44ae089 100644 --- a/invokeai/frontend/install/model_install.py +++ b/invokeai/frontend/install/model_install.py @@ -101,11 +101,12 @@ class addModelsForm(CyclingForm, npyscreen.FormMultiPage): "STARTER MODELS", "MAIN MODELS", "CONTROLNETS", + "IP-ADAPTERS", "LORA/LYCORIS", "TEXTUAL INVERSION", ], value=[self.current_tab], - columns=5, + columns=6, max_height=2, relx=8, scroll_exit=True, @@ -130,6 +131,13 @@ class addModelsForm(CyclingForm, npyscreen.FormMultiPage): ) bottom_of_table = max(bottom_of_table, self.nextrely) + self.nextrely = top_of_table + self.ipadapter_models = self.add_model_widgets( + model_type=ModelType.IPAdapter, + window_width=window_width, + ) + bottom_of_table = max(bottom_of_table, self.nextrely) + self.nextrely = top_of_table self.lora_models = self.add_model_widgets( model_type=ModelType.Lora, @@ -343,6 +351,7 @@ class addModelsForm(CyclingForm, npyscreen.FormMultiPage): self.starter_pipelines, self.pipeline_models, self.controlnet_models, + self.ipadapter_models, self.lora_models, self.ti_models, ] @@ -532,6 +541,7 @@ class addModelsForm(CyclingForm, npyscreen.FormMultiPage): self.starter_pipelines, self.pipeline_models, self.controlnet_models, + self.ipadapter_models, self.lora_models, self.ti_models, ] @@ -553,6 +563,25 @@ class addModelsForm(CyclingForm, npyscreen.FormMultiPage): if downloads := section.get("download_ids"): selections.install_models.extend(downloads.value.split()) + # NOT NEEDED - DONE IN BACKEND NOW + # # special case for the ipadapter_models. If any of the adapters are + # # chosen, then we add the corresponding encoder(s) to the install list. + # section = self.ipadapter_models + # if section.get("models_selected"): + # selected_adapters = [ + # self.all_models[section["models"][x]].name for x in section.get("models_selected").value + # ] + # encoders = [] + # if any(["sdxl" in x for x in selected_adapters]): + # encoders.append("ip_adapter_sdxl_image_encoder") + # if any(["sd15" in x for x in selected_adapters]): + # encoders.append("ip_adapter_sd_image_encoder") + # for encoder in encoders: + # key = f"any/clip_vision/{encoder}" + # repo_id = f"InvokeAI/{encoder}" + # if key not in self.all_models: + # selections.install_models.append(repo_id) + class AddModelApplication(npyscreen.NPSAppManaged): def __init__(self, opt):