mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
many TUI improvements:
1. Separated the "starter models" and "more models" sections. This gives us room to list all installed diffuserse models, not just those that are on the starter list. 2. Support mouse-based paste into the textboxes with either middle or right mouse buttons. 3. Support terminal-style cursor movement: ^A to move to beginning of line ^E to move to end of line ^K kill text to right and put in killring ^Y yank text back 4. Internal code cleanup.
This commit is contained in:
parent
713fb061e8
commit
f74f3d6a3a
@ -677,6 +677,12 @@ def run_console_ui(
|
|||||||
invokeai_opts = default_startup_options(initfile)
|
invokeai_opts = default_startup_options(initfile)
|
||||||
|
|
||||||
set_min_terminal_size(MIN_COLS, MIN_LINES)
|
set_min_terminal_size(MIN_COLS, MIN_LINES)
|
||||||
|
|
||||||
|
# the install-models application spawns a subprocess to install
|
||||||
|
# models, and will crash unless this is set before running.
|
||||||
|
import torch
|
||||||
|
torch.multiprocessing.set_start_method("spawn")
|
||||||
|
|
||||||
editApp = EditOptApplication(program_opts, invokeai_opts)
|
editApp = EditOptApplication(program_opts, invokeai_opts)
|
||||||
editApp.run()
|
editApp.run()
|
||||||
if editApp.user_cancelled:
|
if editApp.user_cancelled:
|
||||||
|
@ -6,13 +6,12 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass,field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryFile
|
from tempfile import TemporaryFile
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from dataclasses import dataclass,field
|
|
||||||
from diffusers import AutoencoderKL
|
from diffusers import AutoencoderKL
|
||||||
from huggingface_hub import hf_hub_url, HfFolder
|
from huggingface_hub import hf_hub_url, HfFolder
|
||||||
from omegaconf import OmegaConf
|
from omegaconf import OmegaConf
|
||||||
@ -127,14 +126,17 @@ def install_requested_models(
|
|||||||
|
|
||||||
if diffusers.install_models and len(diffusers.install_models) > 0:
|
if diffusers.install_models and len(diffusers.install_models) > 0:
|
||||||
logger.info("INSTALLING SELECTED STARTER MODELS")
|
logger.info("INSTALLING SELECTED STARTER MODELS")
|
||||||
successfully_downloaded = download_weight_datasets(
|
downloaded_paths = download_weight_datasets(
|
||||||
models=diffusers.install_models,
|
models=diffusers.install_models,
|
||||||
access_token=None,
|
access_token=None,
|
||||||
precision=precision,
|
precision=precision,
|
||||||
) # FIX: for historical reasons, we don't use model manager here
|
) # FIX: for historical reasons, we don't use model manager here
|
||||||
update_config_file(successfully_downloaded, config_file_path)
|
successful = {x:v for x,v in downloaded_paths.items() if v is not None}
|
||||||
if len(successfully_downloaded) < len(diffusers.install_models):
|
if len(successful) > 0:
|
||||||
logger.warning("Some of the model downloads were not successful")
|
update_config_file(successful, config_file_path)
|
||||||
|
if len(successful) < len(diffusers.install_models):
|
||||||
|
unsuccessful = [x for x in downloaded_paths if downloaded_paths[x] is None]
|
||||||
|
logger.warning(f"Some of the model downloads were not successful: {unsuccessful}")
|
||||||
|
|
||||||
# due to above, we have to reload the model manager because conf file
|
# due to above, we have to reload the model manager because conf file
|
||||||
# was changed behind its back
|
# was changed behind its back
|
||||||
@ -254,7 +256,6 @@ def _download_repo_or_file(
|
|||||||
)
|
)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
def _download_ckpt_weights(mconfig: DictConfig, access_token: str) -> Path:
|
def _download_ckpt_weights(mconfig: DictConfig, access_token: str) -> Path:
|
||||||
repo_id = mconfig["repo_id"]
|
repo_id = mconfig["repo_id"]
|
||||||
filename = mconfig["file"]
|
filename = mconfig["file"]
|
||||||
@ -302,10 +303,10 @@ def _download_diffusion_weights(
|
|||||||
**extra_args,
|
**extra_args,
|
||||||
)
|
)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if str(e).startswith("fp16 is not a valid"):
|
if 'Revision Not Found' in str(e):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
logger.error(f"An unexpected error occurred while downloading the model: {e})")
|
logger.error(str(e))
|
||||||
if path:
|
if path:
|
||||||
break
|
break
|
||||||
return path
|
return path
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# This file predefines a few models that the user may want to install.
|
||||||
diffusers:
|
diffusers:
|
||||||
stable-diffusion-1.5:
|
stable-diffusion-1.5:
|
||||||
description: Stable Diffusion version 1.5 diffusers model (4.27 GB)
|
description: Stable Diffusion version 1.5 diffusers model (4.27 GB)
|
||||||
|
@ -13,6 +13,7 @@ import argparse
|
|||||||
import curses
|
import curses
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import textwrap
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from multiprocessing import Process
|
from multiprocessing import Process
|
||||||
from multiprocessing.connection import Connection, Pipe
|
from multiprocessing.connection import Connection, Pipe
|
||||||
@ -75,7 +76,11 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
|
|
||||||
model_manager = ModelManager(config.model_conf_path)
|
model_manager = ModelManager(config.model_conf_path)
|
||||||
|
|
||||||
self.initial_models = OmegaConf.load(Dataset_path)['diffusers']
|
self.starter_models = OmegaConf.load(Dataset_path)['diffusers']
|
||||||
|
self.installed_diffusers_models = self.list_additional_diffusers_models(
|
||||||
|
model_manager,
|
||||||
|
self.starter_models,
|
||||||
|
)
|
||||||
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_lora_models = model_manager.list_lora_models()
|
||||||
self.installed_ti_models = model_manager.list_ti_models()
|
self.installed_ti_models = model_manager.list_ti_models()
|
||||||
@ -85,7 +90,7 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
except:
|
except:
|
||||||
self.existing_models = dict()
|
self.existing_models = dict()
|
||||||
|
|
||||||
self.starter_model_list = list(self.initial_models.keys())
|
self.starter_model_list = list(self.starter_models.keys())
|
||||||
self.installed_models = dict()
|
self.installed_models = dict()
|
||||||
|
|
||||||
window_width, window_height = get_terminal_size()
|
window_width, window_height = get_terminal_size()
|
||||||
@ -107,13 +112,14 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
self.tabs = self.add_widget_intelligent(
|
self.tabs = self.add_widget_intelligent(
|
||||||
SingleSelectColumns,
|
SingleSelectColumns,
|
||||||
values=[
|
values=[
|
||||||
'DIFFUSERS MODELS',
|
'STARTER MODELS',
|
||||||
|
'MORE DIFFUSION MODELS',
|
||||||
'CONTROLNET MODELS',
|
'CONTROLNET MODELS',
|
||||||
'LORA/LYCORIS MODELS',
|
'LORA/LYCORIS MODELS',
|
||||||
'TEXTUAL INVERSION MODELS'
|
'TEXTUAL INVERSION MODELS',
|
||||||
],
|
],
|
||||||
value=[self.current_tab],
|
value=[self.current_tab],
|
||||||
columns = 4,
|
columns = 5,
|
||||||
max_height = 2,
|
max_height = 2,
|
||||||
relx=8,
|
relx=8,
|
||||||
scroll_exit = True,
|
scroll_exit = True,
|
||||||
@ -121,17 +127,40 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
self.tabs.on_changed = self._toggle_tables
|
self.tabs.on_changed = self._toggle_tables
|
||||||
|
|
||||||
top_of_table = self.nextrely
|
top_of_table = self.nextrely
|
||||||
self.diffusers_models = self.add_diffusers()
|
self.starter_diffusers_models = self.add_starter_diffusers()
|
||||||
bottom_of_table = self.nextrely
|
bottom_of_table = self.nextrely
|
||||||
|
|
||||||
self.nextrely = top_of_table
|
self.nextrely = top_of_table
|
||||||
self.controlnet_models = self.add_controlnets()
|
self.diffusers_models = self.add_diffusers_widgets(
|
||||||
|
predefined_models=self.installed_diffusers_models,
|
||||||
|
model_type='Diffusers',
|
||||||
|
window_width=window_width,
|
||||||
|
)
|
||||||
|
bottom_of_table = max(bottom_of_table,self.nextrely)
|
||||||
|
|
||||||
self.nextrely = top_of_table
|
self.nextrely = top_of_table
|
||||||
self.lora_models = self.add_loras()
|
self.controlnet_models = self.add_model_widgets(
|
||||||
|
predefined_models=self.installed_cn_models,
|
||||||
|
model_type='ControlNet',
|
||||||
|
window_width=window_width,
|
||||||
|
)
|
||||||
|
bottom_of_table = max(bottom_of_table,self.nextrely)
|
||||||
|
|
||||||
self.nextrely = top_of_table
|
self.nextrely = top_of_table
|
||||||
self.ti_models = self.add_tis()
|
self.lora_models = self.add_model_widgets(
|
||||||
|
predefined_models=self.installed_lora_models,
|
||||||
|
model_type="LoRA/LyCORIS",
|
||||||
|
window_width=window_width,
|
||||||
|
)
|
||||||
|
bottom_of_table = max(bottom_of_table,self.nextrely)
|
||||||
|
|
||||||
|
self.nextrely = top_of_table
|
||||||
|
self.ti_models = self.add_model_widgets(
|
||||||
|
predefined_models=self.installed_ti_models,
|
||||||
|
model_type="Textual Inversion Embeddings",
|
||||||
|
window_width=window_width,
|
||||||
|
)
|
||||||
|
bottom_of_table = max(bottom_of_table,self.nextrely)
|
||||||
|
|
||||||
self.nextrely = bottom_of_table+1
|
self.nextrely = bottom_of_table+1
|
||||||
|
|
||||||
@ -181,7 +210,7 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
self._toggle_tables([self.current_tab])
|
self._toggle_tables([self.current_tab])
|
||||||
|
|
||||||
############# diffusers tab ##########
|
############# diffusers tab ##########
|
||||||
def add_diffusers(self)->dict[str, npyscreen.widget]:
|
def add_starter_diffusers(self)->dict[str, npyscreen.widget]:
|
||||||
'''Add widgets responsible for selecting diffusers models'''
|
'''Add widgets responsible for selecting diffusers models'''
|
||||||
widgets = dict()
|
widgets = dict()
|
||||||
|
|
||||||
@ -189,10 +218,10 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
recommended_models = [
|
recommended_models = [
|
||||||
x
|
x
|
||||||
for x in self.starter_model_list
|
for x in self.starter_model_list
|
||||||
if self.initial_models[x].get("recommended", False)
|
if self.starter_models[x].get("recommended", False)
|
||||||
]
|
]
|
||||||
self.installed_models = sorted(
|
self.installed_models = sorted(
|
||||||
[x for x in list(self.initial_models.keys()) if x in self.existing_models]
|
[x for x in list(self.starter_models.keys()) if x in self.existing_models]
|
||||||
)
|
)
|
||||||
|
|
||||||
widgets.update(
|
widgets.update(
|
||||||
@ -234,99 +263,58 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
relx=4,
|
relx=4,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
widgets['purge_deleted'].when_value_edited = lambda: self.sync_purge_buttons(widgets['purge_deleted'])
|
||||||
|
|
||||||
self.nextrely += 1
|
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
|
return widgets
|
||||||
|
|
||||||
############# controlnet tab ##########
|
############# Add a set of model install widgets ########
|
||||||
def add_controlnets(self)->dict[str, npyscreen.widget]:
|
def add_model_widgets(self,
|
||||||
|
predefined_models: dict[str,bool],
|
||||||
|
model_type: str,
|
||||||
|
window_width: int=120,
|
||||||
|
install_prompt: str=None,
|
||||||
|
)->dict[str,npyscreen.widget]:
|
||||||
|
'''Generic code to create model selection widgets'''
|
||||||
widgets = dict()
|
widgets = dict()
|
||||||
cn_model_list = sorted(self.installed_cn_models.keys())
|
model_list = sorted(predefined_models.keys())
|
||||||
|
if len(model_list) > 0:
|
||||||
|
max_width = max([len(x) for x in model_list])
|
||||||
|
columns = window_width // (max_width+6) # 6 characters for "[x] " and padding
|
||||||
|
columns = min(len(model_list),columns) or 1
|
||||||
|
prompt = install_prompt or f"Select the desired {model_type} models to install. Unchecked models will be purged from disk."
|
||||||
|
|
||||||
widgets.update(
|
widgets.update(
|
||||||
label1 = self.add_widget_intelligent(
|
label1 = self.add_widget_intelligent(
|
||||||
CenteredTitleText,
|
CenteredTitleText,
|
||||||
name="Select the desired ControlNet models to install. Unchecked models will be purged from disk.",
|
name=prompt,
|
||||||
editable=False,
|
editable=False,
|
||||||
labelColor="CAUTION",
|
labelColor="CAUTION",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
widgets.update(
|
||||||
columns=6
|
models_selected = self.add_widget_intelligent(
|
||||||
widgets.update(
|
MultiSelectColumns,
|
||||||
models_selected = self.add_widget_intelligent(
|
columns=columns,
|
||||||
MultiSelectColumns,
|
name=f"Install {model_type} Models",
|
||||||
columns=columns,
|
values=model_list,
|
||||||
name="Install ControlNet Models",
|
value=[
|
||||||
values=cn_model_list,
|
model_list.index(x)
|
||||||
value=[
|
for x in model_list
|
||||||
cn_model_list.index(x)
|
if predefined_models[x]
|
||||||
for x in cn_model_list
|
],
|
||||||
if self.installed_cn_models[x]
|
max_height=len(model_list)//columns + 1,
|
||||||
],
|
relx=4,
|
||||||
max_height=len(cn_model_list)//columns + 1,
|
scroll_exit=True,
|
||||||
relx=4,
|
)
|
||||||
scroll_exit=True,
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
self.nextrely += 1
|
self.nextrely += 1
|
||||||
widgets.update(
|
widgets.update(
|
||||||
label2 = self.add_widget_intelligent(
|
label2 = self.add_widget_intelligent(
|
||||||
npyscreen.TitleFixedText,
|
npyscreen.TitleFixedText,
|
||||||
name='Additional ControlNet HuggingFace repo_ids to install (Space separated. Use shift-control-V to paste):',
|
name="Additional URLs or HuggingFace repo_ids to install (Space separated. Use shift-control-V to paste):",
|
||||||
relx=4,
|
relx=4,
|
||||||
color='CONTROL',
|
color='CONTROL',
|
||||||
editable=False,
|
editable=False,
|
||||||
@ -346,130 +334,71 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
)
|
)
|
||||||
return widgets
|
return widgets
|
||||||
|
|
||||||
############# LoRA tab ############
|
### Tab for arbitrary diffusers widgets ###
|
||||||
# TO DO - create generic function for loras and textual inversions
|
def add_diffusers_widgets(self,
|
||||||
def add_loras(self)->dict[str,npyscreen.widget]:
|
predefined_models: dict[str,bool],
|
||||||
widgets = dict()
|
model_type: str='Diffusers',
|
||||||
|
window_width: int=120,
|
||||||
model_list = sorted(self.installed_lora_models.keys())
|
)->dict[str,npyscreen.widget]:
|
||||||
widgets.update(
|
'''Similar to add_model_widgets() but adds some additional widgets at the bottom
|
||||||
label1 = self.add_widget_intelligent(
|
to support the autoload directory'''
|
||||||
CenteredTitleText,
|
widgets = self.add_model_widgets(
|
||||||
name="Select the desired LoRA/LyCORIS models to install. Unchecked models will be purged from disk.",
|
predefined_models,
|
||||||
editable=False,
|
'Diffusers',
|
||||||
labelColor="CAUTION",
|
window_width,
|
||||||
)
|
install_prompt="Additional diffusers models already installed. Uncheck to purge from disk.",
|
||||||
)
|
)
|
||||||
|
|
||||||
columns=min(len(model_list),3) or 1
|
self.nextrely += 2
|
||||||
widgets.update(
|
widgets.update(
|
||||||
models_selected = self.add_widget_intelligent(
|
purge_deleted = self.add_widget_intelligent(
|
||||||
MultiSelectColumns,
|
npyscreen.Checkbox,
|
||||||
columns=columns,
|
name="Purge unchecked diffusers models from disk",
|
||||||
name="Install ControlNet Models",
|
value=False,
|
||||||
values=model_list,
|
scroll_exit=True,
|
||||||
value=[
|
relx=4,
|
||||||
model_list.index(x)
|
)
|
||||||
for x in model_list
|
)
|
||||||
if self.installed_lora_models[x]
|
label = "Directory to scan for models to automatically import (<tab> autocompletes):"
|
||||||
],
|
self.nextrely += 2
|
||||||
max_height=len(model_list)//columns + 1,
|
widgets.update(
|
||||||
|
autoload_directory = self.add_widget_intelligent(
|
||||||
|
npyscreen.TitleFilename,
|
||||||
|
name=label,
|
||||||
|
select_dir=True,
|
||||||
|
must_exist=True,
|
||||||
|
use_two_lines=False,
|
||||||
|
labelColor="DANGER",
|
||||||
|
begin_entry_at=len(label)+1,
|
||||||
|
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,
|
relx=4,
|
||||||
scroll_exit=True,
|
scroll_exit=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
widgets['purge_deleted'].when_value_edited = lambda: self.sync_purge_buttons(widgets['purge_deleted'])
|
||||||
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=4,
|
|
||||||
scroll_exit=True,
|
|
||||||
editable=True,
|
|
||||||
relx=4,
|
|
||||||
hidden=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return widgets
|
return widgets
|
||||||
|
|
||||||
############# Textual Inversion tab ############
|
def sync_purge_buttons(self,checkbox):
|
||||||
def add_tis(self)->dict[str, npyscreen.widget]:
|
value = checkbox.value
|
||||||
widgets = dict()
|
self.starter_diffusers_models['purge_deleted'].value = value
|
||||||
model_list = sorted(self.installed_ti_models.keys())
|
self.diffusers_models['purge_deleted'].value = value
|
||||||
|
|
||||||
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=min(len(model_list),6) or 1
|
|
||||||
widgets.update(
|
|
||||||
models_selected = self.add_widget_intelligent(
|
|
||||||
MultiSelectColumns,
|
|
||||||
columns=columns,
|
|
||||||
name="Install Textual Inversion Embeddings",
|
|
||||||
values=model_list,
|
|
||||||
value=[
|
|
||||||
model_list.index(x)
|
|
||||||
for x in model_list
|
|
||||||
if self.installed_ti_models[x]
|
|
||||||
],
|
|
||||||
max_height=len(model_list)//columns + 1,
|
|
||||||
relx=4,
|
|
||||||
scroll_exit=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
widgets.update(
|
|
||||||
label2 = self.add_widget_intelligent(
|
|
||||||
npyscreen.TitleFixedText,
|
|
||||||
name='Textual Inversion models to download, use URLs or HugggingFace repo_ids (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=4,
|
|
||||||
scroll_exit=True,
|
|
||||||
editable=True,
|
|
||||||
relx=4,
|
|
||||||
hidden=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return widgets
|
|
||||||
|
|
||||||
def resize(self):
|
def resize(self):
|
||||||
super().resize()
|
super().resize()
|
||||||
if (s := self.diffusers_models.get("models_selected")):
|
if (s := self.starter_diffusers_models.get("models_selected")):
|
||||||
s.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.starter_diffusers_models,
|
||||||
self.diffusers_models,
|
self.diffusers_models,
|
||||||
self.controlnet_models,
|
self.controlnet_models,
|
||||||
self.lora_models,
|
self.lora_models,
|
||||||
@ -479,8 +408,11 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
for group in widgets:
|
for group in widgets:
|
||||||
for k,v in group.items():
|
for k,v in group.items():
|
||||||
v.hidden = True
|
v.hidden = True
|
||||||
|
v.editable = False
|
||||||
for k,v in widgets[selected_tab].items():
|
for k,v in widgets[selected_tab].items():
|
||||||
v.hidden = False
|
v.hidden = False
|
||||||
|
if not isinstance(v,(npyscreen.FixedText, npyscreen.TitleFixedText, CenteredTitleText)):
|
||||||
|
v.editable = True
|
||||||
self.__class__.current_tab = selected_tab # for persistence
|
self.__class__.current_tab = selected_tab # for persistence
|
||||||
self.display()
|
self.display()
|
||||||
|
|
||||||
@ -490,7 +422,7 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
checkbox_width = 4
|
checkbox_width = 4
|
||||||
spacing_width = 2
|
spacing_width = 2
|
||||||
description_width = window_width - label_width - checkbox_width - spacing_width
|
description_width = window_width - label_width - checkbox_width - spacing_width
|
||||||
im = self.initial_models
|
im = self.starter_models
|
||||||
names = self.starter_model_list
|
names = self.starter_model_list
|
||||||
descriptions = [
|
descriptions = [
|
||||||
im[x].description[0 : description_width - 3] + "..."
|
im[x].description[0 : description_width - 3] + "..."
|
||||||
@ -518,7 +450,7 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
return min(cols, len(self.installed_models))
|
return min(cols, len(self.installed_models))
|
||||||
|
|
||||||
def on_execute(self):
|
def on_execute(self):
|
||||||
self.monitor.entry_widget.buffer(['Installing...'],scroll_end=True)
|
self.monitor.entry_widget.buffer(['Processing...'],scroll_end=True)
|
||||||
self.marshall_arguments()
|
self.marshall_arguments()
|
||||||
app = self.parentApp
|
app = self.parentApp
|
||||||
self.display()
|
self.display()
|
||||||
@ -554,6 +486,8 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
self.editing = False
|
self.editing = False
|
||||||
|
|
||||||
def while_waiting(self):
|
def while_waiting(self):
|
||||||
|
app = self.parentApp
|
||||||
|
monitor_widget = self.monitor.entry_widget
|
||||||
if c := self.subprocess_connection:
|
if c := self.subprocess_connection:
|
||||||
while c.poll():
|
while c.poll():
|
||||||
try:
|
try:
|
||||||
@ -561,21 +495,42 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
data.strip('\n')
|
data.strip('\n')
|
||||||
if data=='*done*':
|
if data=='*done*':
|
||||||
self.subprocess_connection = None
|
self.subprocess_connection = None
|
||||||
self.monitor.entry_widget.buffer(['** Action Complete **'])
|
monitor_widget.buffer(['** Action Complete **'])
|
||||||
self.display()
|
self.display()
|
||||||
# rebuild the form, saving log messages
|
# rebuild the form, saving log messages
|
||||||
saved_messages = self.monitor.entry_widget.values
|
saved_messages = monitor_widget.values
|
||||||
self.parentApp.main_form = self.parentApp.addForm(
|
app.main_form = app.addForm(
|
||||||
"MAIN", addModelsForm, name="Install Stable Diffusion Models"
|
"MAIN", addModelsForm, name="Install Stable Diffusion Models"
|
||||||
)
|
)
|
||||||
self.parentApp.switchForm('MAIN')
|
app.switchForm('MAIN')
|
||||||
self.parentApp.main_form.monitor.entry_widget.values = saved_messages
|
app.main_form.monitor.entry_widget.values = saved_messages
|
||||||
return
|
app.main_form.monitor.entry_widget.buffer([''],scroll_end=True)
|
||||||
self.monitor.entry_widget.buffer([data])
|
break
|
||||||
self.display()
|
else:
|
||||||
|
monitor_widget.buffer(
|
||||||
|
textwrap.wrap(data,
|
||||||
|
width=monitor_widget.width,
|
||||||
|
subsequent_indent=' ',
|
||||||
|
),
|
||||||
|
scroll_end=True
|
||||||
|
)
|
||||||
|
self.display()
|
||||||
except (EOFError,OSError):
|
except (EOFError,OSError):
|
||||||
self.subprocess_connection = None
|
self.subprocess_connection = None
|
||||||
|
|
||||||
|
def list_additional_diffusers_models(self,
|
||||||
|
manager: ModelManager,
|
||||||
|
starters:dict
|
||||||
|
)->dict[str,bool]:
|
||||||
|
'''Return a dict of all the currently installed models that are not on the starter list'''
|
||||||
|
model_info = manager.list_models()
|
||||||
|
additional_models = {
|
||||||
|
x:True for x in model_info \
|
||||||
|
if model_info[x]['format']=='diffusers' \
|
||||||
|
and x not in starters
|
||||||
|
}
|
||||||
|
return additional_models
|
||||||
|
|
||||||
def marshall_arguments(self):
|
def marshall_arguments(self):
|
||||||
"""
|
"""
|
||||||
Assemble arguments and store as attributes of the application:
|
Assemble arguments and store as attributes of the application:
|
||||||
@ -590,60 +545,70 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
# due to some bug in npyscreen that is causing attributes to be lost
|
# due to some bug in npyscreen that is causing attributes to be lost
|
||||||
selections = self.parentApp.user_selections
|
selections = self.parentApp.user_selections
|
||||||
|
|
||||||
# starter models to install/remove
|
# Starter models to install/remove
|
||||||
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.diffusers_models['models_selected'].value,
|
self.starter_diffusers_models['models_selected'].value,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
selections.purge_deleted_models = self.diffusers_models['purge_deleted'].value
|
selections.purge_deleted_models = self.starter_diffusers_models['purge_deleted'].value or \
|
||||||
|
self.diffusers_models['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]
|
||||||
|
|
||||||
|
# "More" models
|
||||||
|
selections.import_model_paths = self.diffusers_models['download_ids'].value.split()
|
||||||
|
if diffusers_selected := self.diffusers_models.get('models_selected'):
|
||||||
|
selections.remove_models.extend([x
|
||||||
|
for x in diffusers_selected.values
|
||||||
|
if self.installed_diffusers_models[x]
|
||||||
|
and diffusers_selected.values.index(x) not in diffusers_selected.value
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: REFACTOR THIS REPETITIVE CODE
|
# TODO: REFACTOR THIS REPETITIVE CODE
|
||||||
cn_models_selected = self.controlnet_models['models_selected']
|
if cn_models_selected := self.controlnet_models.get('models_selected'):
|
||||||
selections.install_cn_models = [cn_models_selected.values[x]
|
selections.install_cn_models = [cn_models_selected.values[x]
|
||||||
for x in cn_models_selected.value
|
for x in cn_models_selected.value
|
||||||
if not self.installed_cn_models[cn_models_selected.values[x]]
|
if not self.installed_cn_models[cn_models_selected.values[x]]
|
||||||
]
|
]
|
||||||
selections.remove_cn_models = [x
|
selections.remove_cn_models = [x
|
||||||
for x in cn_models_selected.values
|
for x in cn_models_selected.values
|
||||||
if self.installed_cn_models[x]
|
if self.installed_cn_models[x]
|
||||||
and cn_models_selected.values.index(x) not in cn_models_selected.value
|
and cn_models_selected.values.index(x) not in cn_models_selected.value
|
||||||
]
|
]
|
||||||
if (additional_cns := self.controlnet_models['download_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
|
# same thing, for LoRAs
|
||||||
loras_selected = self.lora_models['models_selected']
|
if loras_selected := self.lora_models.get('models_selected'):
|
||||||
selections.install_lora_models = [loras_selected.values[x]
|
selections.install_lora_models = [loras_selected.values[x]
|
||||||
for x in loras_selected.value
|
for x in loras_selected.value
|
||||||
if not self.installed_lora_models[loras_selected.values[x]]
|
if not self.installed_lora_models[loras_selected.values[x]]
|
||||||
]
|
]
|
||||||
selections.remove_lora_models = [x
|
selections.remove_lora_models = [x
|
||||||
for x in loras_selected.values
|
for x in loras_selected.values
|
||||||
if self.installed_lora_models[x]
|
if self.installed_lora_models[x]
|
||||||
and loras_selected.values.index(x) not in loras_selected.value
|
and loras_selected.values.index(x) not in loras_selected.value
|
||||||
]
|
]
|
||||||
|
|
||||||
if (additional_loras := self.lora_models['download_ids'].value.split()):
|
if (additional_loras := self.lora_models['download_ids'].value.split()):
|
||||||
selections.install_lora_models.extend(additional_loras)
|
selections.install_lora_models.extend(additional_loras)
|
||||||
|
|
||||||
# same thing, for TIs
|
# same thing, for TIs
|
||||||
# TODO: refactor
|
# TODO: refactor
|
||||||
tis_selected = self.ti_models['models_selected']
|
if tis_selected := self.ti_models.get('models_selected'):
|
||||||
selections.install_ti_models = [tis_selected.values[x]
|
selections.install_ti_models = [tis_selected.values[x]
|
||||||
for x in tis_selected.value
|
for x in tis_selected.value
|
||||||
if not self.installed_ti_models[tis_selected.values[x]]
|
if not self.installed_ti_models[tis_selected.values[x]]
|
||||||
]
|
]
|
||||||
selections.remove_ti_models = [x
|
selections.remove_ti_models = [x
|
||||||
for x in tis_selected.values
|
for x in tis_selected.values
|
||||||
if self.installed_ti_models[x]
|
if self.installed_ti_models[x]
|
||||||
and tis_selected.values.index(x) not in tis_selected.value
|
and tis_selected.values.index(x) not in tis_selected.value
|
||||||
]
|
]
|
||||||
|
|
||||||
if (additional_tis := self.ti_models['download_ids'].value.split()):
|
if (additional_tis := self.ti_models['download_ids'].value.split()):
|
||||||
selections.install_ti_models.extend(additional_tis)
|
selections.install_ti_models.extend(additional_tis)
|
||||||
@ -652,8 +617,6 @@ class addModelsForm(npyscreen.FormMultiPage):
|
|||||||
selections.scan_directory = self.diffusers_models['autoload_directory'].value
|
selections.scan_directory = self.diffusers_models['autoload_directory'].value
|
||||||
selections.autoscan_on_startup = self.diffusers_models['autoscan_on_startup'].value
|
selections.autoscan_on_startup = self.diffusers_models['autoscan_on_startup'].value
|
||||||
|
|
||||||
# URLs and the like
|
|
||||||
selections.import_model_paths = self.diffusers_models['download_ids'].value.split()
|
|
||||||
|
|
||||||
class AddModelApplication(npyscreen.NPSAppManaged):
|
class AddModelApplication(npyscreen.NPSAppManaged):
|
||||||
def __init__(self,opt):
|
def __init__(self,opt):
|
||||||
@ -724,12 +687,12 @@ def select_and_download_models(opt: Namespace):
|
|||||||
)
|
)
|
||||||
if opt.default_only:
|
if opt.default_only:
|
||||||
install_requested_models(
|
install_requested_models(
|
||||||
install_initial_models=default_dataset(),
|
install_starter_models=default_dataset(),
|
||||||
precision=precision,
|
precision=precision,
|
||||||
)
|
)
|
||||||
elif opt.yes_to_all:
|
elif opt.yes_to_all:
|
||||||
install_requested_models(
|
install_requested_models(
|
||||||
install_initial_models=recommended_datasets(),
|
install_starter_models=recommended_datasets(),
|
||||||
precision=precision,
|
precision=precision,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
@ -5,13 +5,13 @@ import curses
|
|||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
import pyperclip
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
from shutil import get_terminal_size
|
from shutil import get_terminal_size
|
||||||
|
from curses import BUTTON2_CLICKED,BUTTON3_CLICKED
|
||||||
import npyscreen
|
import npyscreen
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------
|
# -------------------------------------
|
||||||
def set_terminal_size(columns: int, lines: int):
|
def set_terminal_size(columns: int, lines: int):
|
||||||
OS = platform.uname().system
|
OS = platform.uname().system
|
||||||
@ -175,6 +175,50 @@ class SingleSelectColumns(SelectColumnBase, npyscreen.SelectOne):
|
|||||||
self.h_exit_down('bye bye')
|
self.h_exit_down('bye bye')
|
||||||
|
|
||||||
class TextBox(npyscreen.MultiLineEdit):
|
class TextBox(npyscreen.MultiLineEdit):
|
||||||
|
|
||||||
|
def __init__(self,*args,**kwargs):
|
||||||
|
super().__init__(*args,**kwargs)
|
||||||
|
self.yank = None
|
||||||
|
self.handlers.update({
|
||||||
|
"^A": self.h_cursor_to_start,
|
||||||
|
"^E": self.h_cursor_to_end,
|
||||||
|
"^K": self.h_kill,
|
||||||
|
"^F": self.h_cursor_right,
|
||||||
|
"^B": self.h_cursor_left,
|
||||||
|
"^Y": self.h_yank,
|
||||||
|
"^V": self.h_paste,
|
||||||
|
})
|
||||||
|
|
||||||
|
def h_cursor_to_start(self, input):
|
||||||
|
self.cursor_position = 0
|
||||||
|
|
||||||
|
def h_cursor_to_end(self, input):
|
||||||
|
self.cursor_position = len(self.value)
|
||||||
|
|
||||||
|
def h_kill(self, input):
|
||||||
|
self.yank = self.value[self.cursor_position:]
|
||||||
|
self.value = self.value[:self.cursor_position]
|
||||||
|
|
||||||
|
def h_yank(self, input):
|
||||||
|
if self.yank:
|
||||||
|
self.paste(self.yank)
|
||||||
|
|
||||||
|
def paste(self, text: str):
|
||||||
|
self.value = self.value[:self.cursor_position] + text + self.value[self.cursor_position:]
|
||||||
|
self.cursor_position += len(text)
|
||||||
|
|
||||||
|
def h_paste(self, input: int=0):
|
||||||
|
try:
|
||||||
|
text = pyperclip.paste()
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
text = "To paste with the mouse on Linux, please install the 'xclip' program."
|
||||||
|
self.paste(text)
|
||||||
|
|
||||||
|
def handle_mouse_event(self, mouse_event):
|
||||||
|
mouse_id, rel_x, rel_y, z, bstate = self.interpret_mouse_event(mouse_event)
|
||||||
|
if bstate & (BUTTON2_CLICKED|BUTTON3_CLICKED):
|
||||||
|
self.h_paste()
|
||||||
|
|
||||||
def update(self, clear=True):
|
def update(self, clear=True):
|
||||||
if clear:
|
if clear:
|
||||||
self.clear()
|
self.clear()
|
||||||
@ -226,3 +270,5 @@ class TextBox(npyscreen.MultiLineEdit):
|
|||||||
|
|
||||||
class BufferBox(npyscreen.BoxTitle):
|
class BufferBox(npyscreen.BoxTitle):
|
||||||
_contained_widget = npyscreen.BufferPager
|
_contained_widget = npyscreen.BufferPager
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,6 +65,7 @@ dependencies = [
|
|||||||
"pillow",
|
"pillow",
|
||||||
"prompt-toolkit",
|
"prompt-toolkit",
|
||||||
"pypatchmatch",
|
"pypatchmatch",
|
||||||
|
'pyperclip',
|
||||||
"pyreadline3",
|
"pyreadline3",
|
||||||
"python-multipart==0.0.6",
|
"python-multipart==0.0.6",
|
||||||
"pytorch-lightning==1.7.7",
|
"pytorch-lightning==1.7.7",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user