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:
Lincoln Stein 2023-06-03 16:17:53 -04:00
parent 713fb061e8
commit f74f3d6a3a
6 changed files with 276 additions and 258 deletions

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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",