mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
listing, downloading and deleting LoRAs working; TI support pending
This commit is contained in:
parent
ff9240b51d
commit
41f7758977
@ -95,6 +95,9 @@ def install_requested_models(
|
|||||||
model_manager.install_controlnet_models(controlnet.install_models, access_token=access_token)
|
model_manager.install_controlnet_models(controlnet.install_models, access_token=access_token)
|
||||||
model_manager.delete_controlnet_models(controlnet.remove_models)
|
model_manager.delete_controlnet_models(controlnet.remove_models)
|
||||||
|
|
||||||
|
model_manager.install_lora_models(lora.install_models)
|
||||||
|
model_manager.delete_lora_models(lora.remove_models)
|
||||||
|
|
||||||
# TODO: Replace next three paragraphs with calls into new model manager
|
# TODO: Replace next three paragraphs with calls into new model manager
|
||||||
if diffusers.remove_models and len(diffusers.remove_models) > 0:
|
if diffusers.remove_models and len(diffusers.remove_models) > 0:
|
||||||
logger.info("DELETING UNCHECKED STARTER MODELS")
|
logger.info("DELETING UNCHECKED STARTER MODELS")
|
||||||
|
@ -20,7 +20,7 @@ import warnings
|
|||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from shutil import move, rmtree
|
from shutil import move, rmtree
|
||||||
from typing import Any, Optional, Union, Callable, types
|
from typing import Any, Optional, Union, Callable, Dict, List, types
|
||||||
|
|
||||||
import safetensors
|
import safetensors
|
||||||
import safetensors.torch
|
import safetensors.torch
|
||||||
@ -1322,15 +1322,69 @@ class ModelManager(object):
|
|||||||
os.getenv("HF_HOME") is not None or os.getenv("XDG_CACHE_HOME") is not None
|
os.getenv("HF_HOME") is not None or os.getenv("XDG_CACHE_HOME") is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def list_lora_models(self)->Dict[str,bool]:
|
||||||
|
'''Return a dict of installed lora models; key is either the shortname
|
||||||
|
defined in INITIAL_MODELS, or the basename of the file in the LoRA
|
||||||
|
directory. Value is True if installed'''
|
||||||
|
|
||||||
|
models = OmegaConf.load(Dataset_path).get('lora') or {}
|
||||||
|
installed_models = {x: False for x in models.keys()}
|
||||||
|
|
||||||
|
dir = self.globals.lora_path
|
||||||
|
installed_models = dict()
|
||||||
|
for root, dirs, files in os.walk(dir):
|
||||||
|
for name in files:
|
||||||
|
if Path(name).suffix in ['.safetensors','.ckpt','.pt']:
|
||||||
|
installed_models.update({name: True})
|
||||||
|
return installed_models
|
||||||
|
|
||||||
|
def install_lora_models(self, model_names: list[str]):
|
||||||
|
'''Download list of LoRA/LyCORIS models'''
|
||||||
|
short_names = OmegaConf.load(Dataset_path).get('lora') or {}
|
||||||
|
for name in model_names:
|
||||||
|
url = short_names.get(name) or name
|
||||||
|
download_with_resume(url, self.globals.lora_path)
|
||||||
|
|
||||||
|
def delete_lora_models(self, model_names: List[str]):
|
||||||
|
'''Remove the list of lora models'''
|
||||||
|
for name in model_names:
|
||||||
|
path = self.globals.lora_path / name
|
||||||
|
if path.exists():
|
||||||
|
self.logger.info(f'Purging lora model {name}')
|
||||||
|
path.unlink()
|
||||||
|
|
||||||
|
def list_ti_models(self)->Dict[str,bool]:
|
||||||
|
'''Return a dict of installed textual models; key is either the shortname
|
||||||
|
defined in INITIAL_MODELS, or the basename of the file in the LoRA
|
||||||
|
directory. Value is True if installed'''
|
||||||
|
|
||||||
|
models = OmegaConf.load(Dataset_path).get('textual_inversion') or {}
|
||||||
|
installed_models = {x: False for x in models.keys()}
|
||||||
|
|
||||||
|
dir = self.globals.embedding_path
|
||||||
|
installed_models = dict()
|
||||||
|
for root, dirs, files in os.walk(dir):
|
||||||
|
for name in files:
|
||||||
|
if name == 'learned_embeds.bin':
|
||||||
|
name = str(Path(root,name).parent)
|
||||||
|
installed_models.update({name: True})
|
||||||
|
return installed_models
|
||||||
|
|
||||||
|
def install_ti_models(self, model_names: list[str]):
|
||||||
|
'''Download list of textual inversion embeddings'''
|
||||||
|
short_names = OmegaConf.load(Dataset_path).get('textual_inversion') or {}
|
||||||
|
for name in model_names:
|
||||||
|
url = short_names.get(name) or name
|
||||||
|
download_with_resume(url, self.globals.embedding_path)
|
||||||
|
|
||||||
def list_controlnet_models(self)->Dict[str,bool]:
|
def list_controlnet_models(self)->Dict[str,bool]:
|
||||||
'''Return a dict of installed controlnet models; key is repo_id or short name
|
'''Return a dict of installed controlnet models; key is repo_id or short name
|
||||||
of model (defined in INITIAL_MODELS), and valule is True if installed'''
|
of model (defined in INITIAL_MODELS), and value is True if installed'''
|
||||||
|
|
||||||
cn_models = OmegaConf.load(Dataset_path).get('controlnet') or {}
|
cn_models = OmegaConf.load(Dataset_path).get('controlnet') or {}
|
||||||
installed_models = {x: False for x in cn_models.keys()}
|
installed_models = {x: False for x in cn_models.keys()}
|
||||||
|
|
||||||
cn_dir = self.globals.controlnet_path
|
cn_dir = self.globals.controlnet_path
|
||||||
installed_cn_models = dict()
|
|
||||||
for root, dirs, files in os.walk(cn_dir):
|
for root, dirs, files in os.walk(cn_dir):
|
||||||
for name in dirs:
|
for name in dirs:
|
||||||
if Path(root, name, '.download_complete').exists():
|
if Path(root, name, '.download_complete').exists():
|
||||||
|
@ -96,3 +96,8 @@ controlnet:
|
|||||||
shuffle: lllyasviel/control_v11e_sd15_shuffle
|
shuffle: lllyasviel/control_v11e_sd15_shuffle
|
||||||
tile: lllyasviel/control_v11f1e_sd15_tile
|
tile: lllyasviel/control_v11f1e_sd15_tile
|
||||||
ip2p: lllyasviel/control_v11e_sd15_ip2p
|
ip2p: lllyasviel/control_v11e_sd15_ip2p
|
||||||
|
textual_inversion:
|
||||||
|
'EasyNegative.safetensors': https://huggingface.co/embed/EasyNegative/blob/main/EasyNegative.safetensors
|
||||||
|
lora:
|
||||||
|
'LowRA.safetensors': https://civitai.com/api/download/models/63006
|
||||||
|
'Ink scenery.safetensors': https://civitai.com/api/download/models/83390
|
||||||
|
@ -62,6 +62,8 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
|
|
||||||
self.initial_models = OmegaConf.load(Dataset_path)['diffusers']
|
self.initial_models = OmegaConf.load(Dataset_path)['diffusers']
|
||||||
self.installed_cn_models = model_manager.list_controlnet_models()
|
self.installed_cn_models = model_manager.list_controlnet_models()
|
||||||
|
self.installed_lora_models = model_manager.list_lora_models()
|
||||||
|
self.installed_ti_models = model_manager.list_ti_models()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.existing_models = OmegaConf.load(default_config_file())
|
self.existing_models = OmegaConf.load(default_config_file())
|
||||||
@ -73,17 +75,6 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
window_width, window_height = get_terminal_size()
|
window_width, window_height = get_terminal_size()
|
||||||
starter_model_labels = self._get_starter_model_labels()
|
|
||||||
recommended_models = [
|
|
||||||
x
|
|
||||||
for x in self.starter_model_list
|
|
||||||
if self.initial_models[x].get("recommended", False)
|
|
||||||
]
|
|
||||||
self.installed_models = sorted(
|
|
||||||
[x for x in list(self.initial_models.keys()) if x in self.existing_models]
|
|
||||||
)
|
|
||||||
|
|
||||||
cn_model_list = sorted(self.installed_cn_models.keys())
|
|
||||||
|
|
||||||
self.nextrely -= 1
|
self.nextrely -= 1
|
||||||
self.add_widget_intelligent(
|
self.add_widget_intelligent(
|
||||||
@ -98,206 +89,47 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
editable=False,
|
editable=False,
|
||||||
color="CAUTION",
|
color="CAUTION",
|
||||||
)
|
)
|
||||||
self.nextrely += 1
|
|
||||||
if len(self.starter_model_list) > 0:
|
|
||||||
self.add_widget_intelligent(
|
|
||||||
CenteredTitleText,
|
|
||||||
name="== DIFFUSERS MODEL STARTER PACK ==",
|
|
||||||
editable=False,
|
|
||||||
color="CONTROL",
|
|
||||||
)
|
|
||||||
self.nextrely -= 1
|
|
||||||
self.add_widget_intelligent(
|
|
||||||
CenteredTitleText,
|
|
||||||
name="Select from a starter set of Stable Diffusion models from HuggingFace.",
|
|
||||||
editable=False,
|
|
||||||
labelColor="CAUTION",
|
|
||||||
)
|
|
||||||
self.nextrely -= 1
|
|
||||||
# if user has already installed some initial models, then don't patronize them
|
|
||||||
# by showing more recommendations
|
|
||||||
show_recommended = not self.existing_models
|
|
||||||
self.models_selected = self.add_widget_intelligent(
|
|
||||||
npyscreen.MultiSelect,
|
|
||||||
name="Install Starter Models",
|
|
||||||
values=starter_model_labels,
|
|
||||||
value=[
|
|
||||||
self.starter_model_list.index(x)
|
|
||||||
for x in self.starter_model_list
|
|
||||||
if (show_recommended and x in recommended_models)\
|
|
||||||
or (x in self.existing_models)
|
|
||||||
],
|
|
||||||
max_height=len(starter_model_labels) + 1,
|
|
||||||
relx=4,
|
|
||||||
scroll_exit=True,
|
|
||||||
)
|
|
||||||
self.purge_deleted = self.add_widget_intelligent(
|
|
||||||
npyscreen.Checkbox,
|
|
||||||
name="Purge unchecked diffusers models from disk",
|
|
||||||
value=False,
|
|
||||||
scroll_exit=True,
|
|
||||||
relx=4,
|
|
||||||
)
|
|
||||||
self.nextrely += 1
|
|
||||||
self.add_widget_intelligent(
|
|
||||||
CenteredTitleText,
|
|
||||||
name="== IMPORT MORE DIFFUSERS MODELS FROM YOUR LOCAL DISK OR THE INTERNET ==",
|
|
||||||
editable=False,
|
|
||||||
color="CONTROL",
|
|
||||||
)
|
|
||||||
self.nextrely -= 1
|
|
||||||
|
|
||||||
for line in [
|
|
||||||
"Enter URLs, file paths, or HuggingFace repository IDs, separated by spaces. Use shift-control-V to paste:",
|
|
||||||
]:
|
|
||||||
self.add_widget_intelligent(
|
|
||||||
CenteredTitleText,
|
|
||||||
name=line,
|
|
||||||
editable=False,
|
|
||||||
labelColor="CONTROL",
|
|
||||||
relx=4,
|
|
||||||
)
|
|
||||||
self.nextrely -= 1
|
|
||||||
self.import_model_paths = self.add_widget_intelligent(
|
|
||||||
TextBox, max_height=4, scroll_exit=True, editable=True, relx=4
|
|
||||||
)
|
|
||||||
self.nextrely += 1
|
|
||||||
self.show_directory_fields = self.add_widget_intelligent(
|
|
||||||
npyscreen.FormControlCheckbox,
|
|
||||||
name="Select a directory for models to import automatically at startup",
|
|
||||||
value=False,
|
|
||||||
)
|
|
||||||
self.autoload_directory = self.add_widget_intelligent(
|
|
||||||
npyscreen.TitleFilename,
|
|
||||||
name="Directory (<tab> autocompletes):",
|
|
||||||
select_dir=True,
|
|
||||||
must_exist=True,
|
|
||||||
use_two_lines=False,
|
|
||||||
labelColor="DANGER",
|
|
||||||
begin_entry_at=34,
|
|
||||||
scroll_exit=True,
|
|
||||||
)
|
|
||||||
self.autoscan_on_startup = self.add_widget_intelligent(
|
|
||||||
npyscreen.Checkbox,
|
|
||||||
name="Scan this directory each time InvokeAI starts for new models to import",
|
|
||||||
value=False,
|
|
||||||
relx=4,
|
|
||||||
scroll_exit=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.add_widget_intelligent(
|
|
||||||
CenteredTitleText,
|
|
||||||
name='_' * (window_width-5),
|
|
||||||
editable=False,
|
|
||||||
labelColor='CAUTION'
|
|
||||||
)
|
|
||||||
|
|
||||||
self.nextrely += 1
|
self.nextrely += 1
|
||||||
self.tabs = self.add_widget_intelligent(
|
self.tabs = self.add_widget_intelligent(
|
||||||
SingleSelectColumns,
|
SingleSelectColumns,
|
||||||
values=['ADD CONTROLNET MODELS','ADD LORA/LYCORIS MODELS', 'ADD TEXTUAL INVERSION MODELS'],
|
values=[
|
||||||
|
'DIFFUSERS MODELS',
|
||||||
|
'CONTROLNET MODELS',
|
||||||
|
'LORA/LYCORIS MODELS',
|
||||||
|
'TEXTUAL INVERSION MODELS'
|
||||||
|
],
|
||||||
value=0,
|
value=0,
|
||||||
columns = 4,
|
columns = 4,
|
||||||
max_height = 2,
|
max_height = 2,
|
||||||
relx=8,
|
relx=8,
|
||||||
scroll_exit = True,
|
scroll_exit = True,
|
||||||
)
|
)
|
||||||
# self.add_widget_intelligent(
|
self.tabs.on_changed = self._toggle_tables
|
||||||
# CenteredTitleText,
|
|
||||||
# name="== CONTROLNET MODELS ==",
|
|
||||||
# editable=False,
|
|
||||||
# color="CONTROL",
|
|
||||||
# )
|
|
||||||
top_of_table = self.nextrely
|
top_of_table = self.nextrely
|
||||||
self.cn_label_1 = self.add_widget_intelligent(
|
self.diffusers_models = self.add_diffusers()
|
||||||
CenteredTitleText,
|
|
||||||
name="Select the desired models to install. Unchecked models will be purged from disk.",
|
|
||||||
editable=False,
|
|
||||||
labelColor="CAUTION",
|
|
||||||
)
|
|
||||||
columns=6
|
|
||||||
self.cn_models_selected = self.add_widget_intelligent(
|
|
||||||
MultiSelectColumns,
|
|
||||||
columns=columns,
|
|
||||||
name="Install ControlNet Models",
|
|
||||||
values=cn_model_list,
|
|
||||||
value=[
|
|
||||||
cn_model_list.index(x)
|
|
||||||
for x in cn_model_list
|
|
||||||
if self.installed_cn_models[x]
|
|
||||||
],
|
|
||||||
max_height=len(cn_model_list)//columns + 1,
|
|
||||||
relx=4,
|
|
||||||
scroll_exit=True,
|
|
||||||
)
|
|
||||||
self.nextrely += 1
|
|
||||||
self.cn_label_2 = self.add_widget_intelligent(
|
|
||||||
npyscreen.TitleFixedText,
|
|
||||||
name='Additional ControlNet HuggingFace repo_ids to install (Space separated. Use shift-control-V to paste):',
|
|
||||||
relx=4,
|
|
||||||
color='CONTROL',
|
|
||||||
editable=False,
|
|
||||||
scroll_exit=True
|
|
||||||
)
|
|
||||||
self.nextrely -= 1
|
|
||||||
self.additional_controlnet_ids = self.add_widget_intelligent(
|
|
||||||
TextBox, max_height=2, scroll_exit=True, editable=True, relx=4
|
|
||||||
)
|
|
||||||
|
|
||||||
bottom_of_table = self.nextrely
|
bottom_of_table = self.nextrely
|
||||||
self.nextrely = top_of_table
|
|
||||||
self.lora_label_1 = self.add_widget_intelligent(
|
|
||||||
npyscreen.TitleFixedText,
|
|
||||||
name='LoRA/LYCORIS models to download and install (Space separated. Use shift-control-V to paste):',
|
|
||||||
relx=4,
|
|
||||||
color='CONTROL',
|
|
||||||
editable=False,
|
|
||||||
hidden=True,
|
|
||||||
scroll_exit=True
|
|
||||||
)
|
|
||||||
self.nextrely -= 1
|
|
||||||
self.loras = self.add_widget_intelligent(
|
|
||||||
TextBox,
|
|
||||||
max_height=2,
|
|
||||||
scroll_exit=True,
|
|
||||||
editable=True,
|
|
||||||
relx=4,
|
|
||||||
hidden=True,
|
|
||||||
)
|
|
||||||
self.nextrely = top_of_table
|
|
||||||
self.ti_label_1 = self.add_widget_intelligent(
|
|
||||||
npyscreen.TitleFixedText,
|
|
||||||
name='Textual Inversion models to download and install (Space separated. Use shift-control-V to paste):',
|
|
||||||
relx=4,
|
|
||||||
color='CONTROL',
|
|
||||||
editable=False,
|
|
||||||
hidden=True,
|
|
||||||
scroll_exit=True
|
|
||||||
)
|
|
||||||
self.nextrely -= 1
|
|
||||||
self.tis = self.add_widget_intelligent(
|
|
||||||
TextBox,
|
|
||||||
max_height=2,
|
|
||||||
scroll_exit=True,
|
|
||||||
editable=True,
|
|
||||||
relx=4,
|
|
||||||
hidden=True,
|
|
||||||
)
|
|
||||||
self.nextrely = bottom_of_table
|
|
||||||
self.nextrely += 1
|
|
||||||
self.add_widget_intelligent(
|
|
||||||
CenteredTitleText,
|
|
||||||
name='_' * (window_width-5),
|
|
||||||
editable=False,
|
|
||||||
labelColor='CAUTION'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
self.nextrely = top_of_table
|
||||||
|
self.controlnet_models = self.add_controlnets()
|
||||||
|
|
||||||
|
self.nextrely = top_of_table
|
||||||
|
self.lora_models = self.add_loras()
|
||||||
|
|
||||||
|
self.nextrely = top_of_table
|
||||||
|
self.ti_models = self.add_tis()
|
||||||
|
|
||||||
|
self.nextrely = bottom_of_table
|
||||||
|
|
||||||
|
self.nextrely += 1
|
||||||
self.cancel = self.add_widget_intelligent(
|
self.cancel = self.add_widget_intelligent(
|
||||||
npyscreen.ButtonPress,
|
npyscreen.ButtonPress,
|
||||||
name="CANCEL",
|
name="CANCEL",
|
||||||
rely=-3,
|
rely=-3,
|
||||||
when_pressed_function=self.on_cancel,
|
when_pressed_function=self.on_cancel,
|
||||||
)
|
)
|
||||||
|
|
||||||
done_label = "DONE"
|
done_label = "DONE"
|
||||||
back_label = "BACK"
|
back_label = "BACK"
|
||||||
button_length = len(done_label)
|
button_length = len(done_label)
|
||||||
@ -322,37 +154,285 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
when_pressed_function=self.on_ok,
|
when_pressed_function=self.on_ok,
|
||||||
)
|
)
|
||||||
|
|
||||||
for i in [self.autoload_directory, self.autoscan_on_startup]:
|
self._toggle_tables([0])
|
||||||
self.show_directory_fields.addVisibleWhenSelected(i)
|
|
||||||
|
|
||||||
self.tabs.on_changed = self._toggle_tables
|
def add_diffusers(self)->dict[str, npyscreen.widget]:
|
||||||
|
'''Add widgets responsible for selecting diffusers models'''
|
||||||
|
widgets = dict()
|
||||||
|
|
||||||
self.show_directory_fields.when_value_edited = self._clear_scan_directory
|
starter_model_labels = self._get_starter_model_labels()
|
||||||
|
recommended_models = [
|
||||||
|
x
|
||||||
|
for x in self.starter_model_list
|
||||||
|
if self.initial_models[x].get("recommended", False)
|
||||||
|
]
|
||||||
|
self.installed_models = sorted(
|
||||||
|
[x for x in list(self.initial_models.keys()) if x in self.existing_models]
|
||||||
|
)
|
||||||
|
|
||||||
|
widgets.update(
|
||||||
|
label1 = self.add_widget_intelligent(
|
||||||
|
CenteredTitleText,
|
||||||
|
name="== DIFFUSERS MODEL STARTER PACK ==",
|
||||||
|
editable=False,
|
||||||
|
color="CONTROL",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.nextrely -= 1
|
||||||
|
widgets.update(
|
||||||
|
label2 = self.add_widget_intelligent(
|
||||||
|
CenteredTitleText,
|
||||||
|
name="Select from a starter set of Stable Diffusion models from HuggingFace.",
|
||||||
|
editable=False,
|
||||||
|
labelColor="CAUTION",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.nextrely -= 1
|
||||||
|
# if user has already installed some initial models, then don't patronize them
|
||||||
|
# by showing more recommendations
|
||||||
|
show_recommended = not self.existing_models
|
||||||
|
widgets.update(
|
||||||
|
models_selected = self.add_widget_intelligent(
|
||||||
|
npyscreen.MultiSelect,
|
||||||
|
name="Install Starter Models",
|
||||||
|
values=starter_model_labels,
|
||||||
|
value=[
|
||||||
|
self.starter_model_list.index(x)
|
||||||
|
for x in self.starter_model_list
|
||||||
|
if (show_recommended and x in recommended_models)\
|
||||||
|
or (x in self.existing_models)
|
||||||
|
],
|
||||||
|
max_height=len(starter_model_labels) + 1,
|
||||||
|
relx=4,
|
||||||
|
scroll_exit=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
widgets.update(
|
||||||
|
purge_deleted = self.add_widget_intelligent(
|
||||||
|
npyscreen.Checkbox,
|
||||||
|
name="Purge unchecked diffusers models from disk",
|
||||||
|
value=False,
|
||||||
|
scroll_exit=True,
|
||||||
|
relx=4,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.nextrely += 1
|
||||||
|
widgets.update(
|
||||||
|
label3 = self.add_widget_intelligent(
|
||||||
|
CenteredTitleText,
|
||||||
|
name="== IMPORT MORE DIFFUSERS MODELS FROM YOUR LOCAL DISK OR THE INTERNET ==",
|
||||||
|
editable=False,
|
||||||
|
color="CONTROL",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.nextrely -= 1
|
||||||
|
widgets.update(
|
||||||
|
label4 = self.add_widget_intelligent(
|
||||||
|
CenteredTitleText,
|
||||||
|
name="Enter URLs, file paths, or HuggingFace repository IDs, separated by spaces. Use shift-control-V to paste:",
|
||||||
|
editable=False,
|
||||||
|
labelColor="CONTROL",
|
||||||
|
relx=4,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.nextrely -= 1
|
||||||
|
widgets.update(
|
||||||
|
download_ids = self.add_widget_intelligent(
|
||||||
|
TextBox, max_height=4, scroll_exit=True, editable=True, relx=4
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.nextrely += 1
|
||||||
|
|
||||||
|
widgets.update(
|
||||||
|
autoload_directory = self.add_widget_intelligent(
|
||||||
|
npyscreen.TitleFilename,
|
||||||
|
name="Directory to scan for models to import (<tab> autocompletes):",
|
||||||
|
select_dir=True,
|
||||||
|
must_exist=True,
|
||||||
|
use_two_lines=False,
|
||||||
|
labelColor="DANGER",
|
||||||
|
begin_entry_at=65,
|
||||||
|
scroll_exit=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
widgets.update(
|
||||||
|
autoscan_on_startup = self.add_widget_intelligent(
|
||||||
|
npyscreen.Checkbox,
|
||||||
|
name="Scan and import from this directory each time InvokeAI starts",
|
||||||
|
value=False,
|
||||||
|
relx=4,
|
||||||
|
scroll_exit=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return widgets
|
||||||
|
|
||||||
|
|
||||||
|
def add_controlnets(self)->dict[str, npyscreen.widget]:
|
||||||
|
widgets = dict()
|
||||||
|
cn_model_list = sorted(self.installed_cn_models.keys())
|
||||||
|
|
||||||
|
widgets.update(
|
||||||
|
label1 = self.add_widget_intelligent(
|
||||||
|
CenteredTitleText,
|
||||||
|
name="Select the desired models to install. Unchecked models will be purged from disk.",
|
||||||
|
editable=False,
|
||||||
|
labelColor="CAUTION",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
columns=6
|
||||||
|
widgets.update(
|
||||||
|
models_selected = self.add_widget_intelligent(
|
||||||
|
MultiSelectColumns,
|
||||||
|
columns=columns,
|
||||||
|
name="Install ControlNet Models",
|
||||||
|
values=cn_model_list,
|
||||||
|
value=[
|
||||||
|
cn_model_list.index(x)
|
||||||
|
for x in cn_model_list
|
||||||
|
if self.installed_cn_models[x]
|
||||||
|
],
|
||||||
|
max_height=len(cn_model_list)//columns + 1,
|
||||||
|
relx=4,
|
||||||
|
scroll_exit=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.nextrely += 1
|
||||||
|
widgets.update(
|
||||||
|
label2 = self.add_widget_intelligent(
|
||||||
|
npyscreen.TitleFixedText,
|
||||||
|
name='Additional ControlNet HuggingFace repo_ids to install (Space separated. Use shift-control-V to paste):',
|
||||||
|
relx=4,
|
||||||
|
color='CONTROL',
|
||||||
|
editable=False,
|
||||||
|
scroll_exit=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.nextrely -= 1
|
||||||
|
widgets.update(
|
||||||
|
download_ids = self.add_widget_intelligent(
|
||||||
|
TextBox, max_height=2, scroll_exit=True, editable=True, relx=4
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return widgets
|
||||||
|
|
||||||
|
# TO DO - create generic function for loras and textual inversions
|
||||||
|
def add_loras(self)->dict[str,npyscreen.widget]:
|
||||||
|
widgets = dict()
|
||||||
|
|
||||||
|
model_list = sorted(self.installed_lora_models.keys())
|
||||||
|
widgets.update(
|
||||||
|
label1 = self.add_widget_intelligent(
|
||||||
|
CenteredTitleText,
|
||||||
|
name="Select the desired LoRA/LyCORIS models to install. Unchecked models will be purged from disk.",
|
||||||
|
editable=False,
|
||||||
|
labelColor="CAUTION",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
columns=min(len(model_list),3)
|
||||||
|
widgets.update(
|
||||||
|
models_selected = self.add_widget_intelligent(
|
||||||
|
MultiSelectColumns,
|
||||||
|
columns=columns,
|
||||||
|
name="Install ControlNet Models",
|
||||||
|
values=model_list,
|
||||||
|
value=[
|
||||||
|
model_list.index(x)
|
||||||
|
for x in model_list
|
||||||
|
if self.installed_lora_models[x]
|
||||||
|
],
|
||||||
|
max_height=len(model_list)//columns + 1,
|
||||||
|
relx=4,
|
||||||
|
scroll_exit=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.nextrely += 1
|
||||||
|
widgets.update(
|
||||||
|
label2 = self.add_widget_intelligent(
|
||||||
|
npyscreen.TitleFixedText,
|
||||||
|
name='URLs for new LoRA/LYCORIS models to download and install (Space separated. Use shift-control-V to paste):',
|
||||||
|
relx=4,
|
||||||
|
color='CONTROL',
|
||||||
|
editable=False,
|
||||||
|
hidden=True,
|
||||||
|
scroll_exit=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.nextrely -= 1
|
||||||
|
widgets.update(
|
||||||
|
download_ids = self.add_widget_intelligent(
|
||||||
|
TextBox,
|
||||||
|
max_height=2,
|
||||||
|
scroll_exit=True,
|
||||||
|
editable=True,
|
||||||
|
relx=4,
|
||||||
|
hidden=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return widgets
|
||||||
|
|
||||||
|
def add_tis(self)->dict[str, npyscreen.widget]:
|
||||||
|
widgets = dict()
|
||||||
|
widgets.update(
|
||||||
|
label1 = self.add_widget_intelligent(
|
||||||
|
npyscreen.TitleFixedText,
|
||||||
|
name='Textual Inversion models to download and install (Space separated. Use shift-control-V to paste):',
|
||||||
|
relx=4,
|
||||||
|
color='CONTROL',
|
||||||
|
editable=False,
|
||||||
|
hidden=True,
|
||||||
|
scroll_exit=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.nextrely -= 1
|
||||||
|
widgets.update(
|
||||||
|
download_ids = self.add_widget_intelligent(
|
||||||
|
TextBox,
|
||||||
|
max_height=2,
|
||||||
|
scroll_exit=True,
|
||||||
|
editable=True,
|
||||||
|
relx=4,
|
||||||
|
hidden=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return widgets
|
||||||
|
|
||||||
def resize(self):
|
def resize(self):
|
||||||
super().resize()
|
super().resize()
|
||||||
if hasattr(self, "models_selected"):
|
if (s := self.diffusers_models.get("models_selected")):
|
||||||
self.models_selected.values = self._get_starter_model_labels()
|
s.values = self._get_starter_model_labels()
|
||||||
|
|
||||||
def _toggle_tables(self, value=None):
|
def _toggle_tables(self, value=None):
|
||||||
selected_tab = value[0]
|
selected_tab = value[0]
|
||||||
widgets = [
|
widgets = [
|
||||||
[self.cn_label_1, self.cn_models_selected, self.cn_label_2, self.additional_controlnet_ids],
|
self.diffusers_models,
|
||||||
[self.lora_label_1,self.loras],
|
self.controlnet_models,
|
||||||
[self.ti_label_1,self.tis],
|
self.lora_models,
|
||||||
|
self.ti_models,
|
||||||
]
|
]
|
||||||
|
|
||||||
for group in widgets:
|
for group in widgets:
|
||||||
for w in group:
|
for k,v in group.items():
|
||||||
w.hidden = True
|
v.hidden = True
|
||||||
for w in widgets[selected_tab]:
|
for k,v in widgets[selected_tab].items():
|
||||||
w.hidden = False
|
v.hidden = False
|
||||||
self.display()
|
self.display()
|
||||||
|
|
||||||
def _clear_scan_directory(self):
|
|
||||||
if not self.show_directory_fields.value:
|
|
||||||
self.autoload_directory.value = ""
|
|
||||||
|
|
||||||
def _get_starter_model_labels(self) -> List[str]:
|
def _get_starter_model_labels(self) -> List[str]:
|
||||||
window_width, window_height = get_terminal_size()
|
window_width, window_height = get_terminal_size()
|
||||||
label_width = 25
|
label_width = 25
|
||||||
@ -372,22 +452,6 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
for x in range(0, len(names))
|
for x in range(0, len(names))
|
||||||
]
|
]
|
||||||
|
|
||||||
def _get_installed_cn_models(self)->list[str]:
|
|
||||||
cn_dir = config.controlnet_path
|
|
||||||
installed_cn_models = set()
|
|
||||||
for root, dirs, files in os.walk(cn_dir):
|
|
||||||
for name in dirs:
|
|
||||||
if Path(root, name, '.download_complete').exists():
|
|
||||||
installed_cn_models.add(name.replace('--','/'))
|
|
||||||
return installed_cn_models
|
|
||||||
|
|
||||||
def _add_additional_cn_models(self, known_models: dict, installed_models: set):
|
|
||||||
for i in installed_models:
|
|
||||||
if i in known_models:
|
|
||||||
continue
|
|
||||||
# translate from name to repo_id
|
|
||||||
repo_id = i.replace('--','/')
|
|
||||||
known_models.update({i: repo_id})
|
|
||||||
|
|
||||||
def _get_columns(self) -> int:
|
def _get_columns(self) -> int:
|
||||||
window_width, window_height = get_terminal_size()
|
window_width, window_height = get_terminal_size()
|
||||||
@ -435,43 +499,54 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
selections = self.parentApp.user_selections
|
selections = self.parentApp.user_selections
|
||||||
|
|
||||||
# starter models to install/remove
|
# starter models to install/remove
|
||||||
if hasattr(self, "models_selected"):
|
|
||||||
starter_models = dict(
|
starter_models = dict(
|
||||||
map(
|
map(
|
||||||
lambda x: (self.starter_model_list[x], True),
|
lambda x: (self.starter_model_list[x], True),
|
||||||
self.models_selected.value,
|
self.diffusers_models['models_selected'].value,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
selections.purge_deleted_models = self.diffusers_models['purge_deleted'].value
|
||||||
starter_models = dict()
|
|
||||||
selections.purge_deleted_models = self.purge_deleted.value
|
|
||||||
|
|
||||||
selections.install_models = [x for x in starter_models if x not in self.existing_models]
|
selections.install_models = [x for x in starter_models if x not in self.existing_models]
|
||||||
selections.remove_models = [x for x in self.starter_model_list if x in self.existing_models and x not in starter_models]
|
selections.remove_models = [x for x in self.starter_model_list if x in self.existing_models and x not in starter_models]
|
||||||
|
|
||||||
selections.install_cn_models = [self.cn_models_selected.values[x]
|
# TODO: REFACTOR CUT AND PASTE CODE
|
||||||
for x in self.cn_models_selected.value
|
cn_models_selected = self.controlnet_models['models_selected']
|
||||||
if not self.installed_cn_models[self.cn_models_selected.values[x]]
|
selections.install_cn_models = [cn_models_selected.values[x]
|
||||||
|
for x in cn_models_selected.value
|
||||||
|
if not self.installed_cn_models[cn_models_selected.values[x]]
|
||||||
]
|
]
|
||||||
selections.remove_cn_models = [x
|
selections.remove_cn_models = [x
|
||||||
for x in self.cn_models_selected.values
|
for x in cn_models_selected.values
|
||||||
if self.installed_cn_models[x]
|
if self.installed_cn_models[x]
|
||||||
and self.cn_models_selected.values.index(x) not in self.cn_models_selected.value
|
and cn_models_selected.values.index(x) not in cn_models_selected.value
|
||||||
]
|
]
|
||||||
if (additional_cns := self.additional_controlnet_ids.value.split()):
|
if (additional_cns := self.controlnet_models['download_ids'].value.split()):
|
||||||
valid_cns = [x for x in additional_cns if '/' in x]
|
valid_cns = [x for x in additional_cns if '/' in x]
|
||||||
selections.install_cn_models.extend(valid_cns)
|
selections.install_cn_models.extend(valid_cns)
|
||||||
|
|
||||||
|
# same thing, for LoRAs
|
||||||
|
loras_selected = self.lora_models['models_selected']
|
||||||
|
selections.install_lora_models = [loras_selected.values[x]
|
||||||
|
for x in loras_selected.value
|
||||||
|
if not self.installed_lora_models[loras_selected.values[x]]
|
||||||
|
]
|
||||||
|
selections.remove_lora_models = [x
|
||||||
|
for x in loras_selected.values
|
||||||
|
if self.installed_lora_models[x]
|
||||||
|
and loras_selected.values.index(x) not in loras_selected.value
|
||||||
|
]
|
||||||
|
|
||||||
|
if (additional_loras := self.lora_models['download_ids'].value.split()):
|
||||||
|
valid_loras = [x for x in additional_loras if x.startswith(('http:','https:','ftp:'))]
|
||||||
|
selections.install_lora_models.extend(valid_loras)
|
||||||
|
|
||||||
# load directory and whether to scan on startup
|
# load directory and whether to scan on startup
|
||||||
if self.show_directory_fields.value:
|
selections.scan_directory = self.diffusers_models['autoload_directory'].value
|
||||||
selections.scan_directory = self.autoload_directory.value
|
selections.autoscan_on_startup = self.diffusers_models['autoscan_on_startup'].value
|
||||||
selections.autoscan_on_startup = self.autoscan_on_startup.value
|
|
||||||
else:
|
|
||||||
selections.scan_directory = None
|
|
||||||
selections.autoscan_on_startup = False
|
|
||||||
|
|
||||||
# URLs and the like
|
# URLs and the like
|
||||||
selections.import_model_paths = self.import_model_paths.value.split()
|
selections.import_model_paths = self.diffusers_models['download_ids'].value.split()
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class UserSelections():
|
class UserSelections():
|
||||||
@ -507,6 +582,7 @@ def process_and_execute(opt: Namespace, selections: Namespace):
|
|||||||
install_requested_models(
|
install_requested_models(
|
||||||
diffusers = ModelInstallList(models_to_install, models_to_remove),
|
diffusers = ModelInstallList(models_to_install, models_to_remove),
|
||||||
controlnet = ModelInstallList(selections.install_cn_models, selections.remove_cn_models),
|
controlnet = ModelInstallList(selections.install_cn_models, selections.remove_cn_models),
|
||||||
|
lora = ModelInstallList(selections.install_lora_models, selections.remove_lora_models),
|
||||||
scan_directory=Path(directory_to_scan) if directory_to_scan else None,
|
scan_directory=Path(directory_to_scan) if directory_to_scan else None,
|
||||||
external_models=potential_models_to_install,
|
external_models=potential_models_to_install,
|
||||||
scan_at_startup=scan_at_startup,
|
scan_at_startup=scan_at_startup,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user