both forms functional; need integration

This commit is contained in:
Lincoln Stein 2023-02-19 13:12:05 -05:00
parent 587faa3e52
commit e5646d7241
2 changed files with 416 additions and 350 deletions

View File

@ -8,8 +8,11 @@
# #
print("Loading Python libraries...\n") print("Loading Python libraries...\n")
import argparse import argparse
import curses
import npyscreen
import io import io
import os import os
import re
import shutil import shutil
import sys import sys
import traceback import traceback
@ -18,9 +21,10 @@ from pathlib import Path
from urllib import request from urllib import request
import transformers import transformers
from getpass_asterisk import getpass_asterisk from argparse import Namespace
from huggingface_hub import HfFolder from huggingface_hub import HfFolder
from huggingface_hub import login as hf_hub_login from huggingface_hub import login as hf_hub_login
from omegaconf import OmegaConf from omegaconf import OmegaConf
from tqdm import tqdm from tqdm import tqdm
from transformers import ( from transformers import (
@ -31,10 +35,12 @@ from transformers import (
) )
import invokeai.configs as configs import invokeai.configs as configs
from ldm.invoke.config.model_install_backend import download_from_hf from ..args import Args, PRECISION_CHOICES
from ldm.invoke.config.model_install import select_and_download_models from .model_install_backend import download_from_hf
from ldm.invoke.globals import Globals, global_config_dir from .model_install import select_and_download_models
from ldm.invoke.readline import generic_completer from .widgets import IntTitleSlider
from ..globals import Globals, global_config_dir
from ..readline import generic_completer
warnings.filterwarnings("ignore") warnings.filterwarnings("ignore")
@ -53,16 +59,19 @@ SD_Configs = Path(global_config_dir()) / "stable-diffusion"
Datasets = OmegaConf.load(Dataset_path) Datasets = OmegaConf.load(Dataset_path)
completer = generic_completer(["yes", "no"]) completer = generic_completer(["yes", "no"])
Config_preamble = """# This file describes the alternative machine learning models INIT_FILE_PREAMBLE = """# InvokeAI initialization file
# available to InvokeAI script. # This is the InvokeAI initialization file, which contains command-line default values.
# # Feel free to edit. If anything goes wrong, you can re-initialize this file by deleting
# To add a new model, follow the examples below. Each # or renaming it and then running invokeai-configure again.
# model requires a model config file, a weights file, # Place frequently-used startup commands here, one or more per line.
# and the width and height of the images it # Examples:
# was trained on. # --outdir=D:\data\images
# --no-nsfw_checker
# --web --host=0.0.0.0
# --steps=20
# -Ak_euler_a -C10.0
""" """
# -------------------------------------------- # --------------------------------------------
def postscript(errors: None): def postscript(errors: None):
if not any(errors): if not any(errors):
@ -111,7 +120,6 @@ def yes_or_no(prompt: str, default_yes=True):
else: else:
return response[0] in ("y", "Y") return response[0] in ("y", "Y")
# --------------------------------------------- # ---------------------------------------------
def HfLogin(access_token) -> str: def HfLogin(access_token) -> str:
""" """
@ -128,122 +136,23 @@ def HfLogin(access_token) -> str:
sys.stdout = sys.__stdout__ sys.stdout = sys.__stdout__
print(exc) print(exc)
raise exc raise exc
# -------------------------------------
class ProgressBar:
def __init__(self, model_name="file"):
self.pbar = None
self.name = model_name
def __call__(self, block_num, block_size, total_size):
# -------------------------------Authenticate against Hugging Face if not self.pbar:
def save_hf_token(yes_to_all=False): self.pbar = tqdm(
print("** LICENSE AGREEMENT FOR WEIGHT FILES **") desc=self.name,
print("=" * shutil.get_terminal_size()[0]) initial=0,
print( unit="iB",
""" unit_scale=True,
By downloading the Stable Diffusion weight files from the official Hugging Face unit_divisor=1000,
repository, you agree to have read and accepted the CreativeML Responsible AI License. total=total_size,
The license terms are located here: )
self.pbar.update(block_size)
https://huggingface.co/spaces/CompVis/stable-diffusion-license
"""
)
print("=" * shutil.get_terminal_size()[0])
if not yes_to_all:
accepted = False
while not accepted:
accepted = yes_or_no("Accept the above License terms?")
if not accepted:
print("Please accept the License or Ctrl+C to exit.")
else:
print("Thank you!")
else:
print(
"The program was started with a '--yes' flag, which indicates user's acceptance of the above License terms."
)
# Authenticate to Huggingface using environment variables.
# If successful, authentication will persist for either interactive or non-interactive use.
# Default env var expected by HuggingFace is HUGGING_FACE_HUB_TOKEN.
print("=" * shutil.get_terminal_size()[0])
print("Authenticating to Huggingface")
hf_envvars = ["HUGGING_FACE_HUB_TOKEN", "HUGGINGFACE_TOKEN"]
token_found = False
if not (access_token := HfFolder.get_token()):
print("Huggingface token not found in cache.")
for ev in hf_envvars:
if access_token := os.getenv(ev):
print(
f"Token was found in the {ev} environment variable.... Logging in."
)
try:
HfLogin(access_token)
continue
except ValueError:
print(f"Login failed due to invalid token found in {ev}")
else:
print(f"Token was not found in the environment variable {ev}.")
else:
print("Huggingface token found in cache.")
try:
HfLogin(access_token)
token_found = True
except ValueError:
print("Login failed due to invalid token found in cache")
if not (yes_to_all or token_found):
print(
f""" You may optionally enter your Huggingface token now. InvokeAI
*will* work without it but you will not be able to automatically
download some of the Hugging Face style concepts. See
https://invoke-ai.github.io/InvokeAI/features/CONCEPTS/#using-a-hugging-face-concept
for more information.
Visit https://huggingface.co/settings/tokens to generate a token. (Sign up for an account if needed).
Paste the token below using {"Ctrl+Shift+V" if sys.platform == "linux" else "Command+V" if sys.platform == "darwin" else "Ctrl+V, right-click, or Edit>Paste"}.
Alternatively, press 'Enter' to skip this step and continue.
You may re-run the configuration script again in the future if you do not wish to set the token right now.
"""
)
again = True
while again:
try:
access_token = getpass_asterisk.getpass_asterisk(prompt="HF Token ")
if access_token is None or len(access_token) == 0:
raise EOFError
HfLogin(access_token)
access_token = HfFolder.get_token()
again = False
except ValueError:
again = yes_or_no(
"Failed to log in to Huggingface. Would you like to try again?"
)
if not again:
print(
"\nRe-run the configuration script whenever you wish to set the token."
)
print("...Continuing...")
except EOFError:
# this happens if the user pressed Enter on the prompt without any input; assume this means they don't want to input a token
# safety net needed against accidental "Enter"?
print("None provided - continuing")
again = False
elif access_token is None:
print()
print(
"HuggingFace login did not succeed. Some functionality may be limited; see https://invoke-ai.github.io/InvokeAI/features/CONCEPTS/#using-a-hugging-face-concept for more information"
)
print()
print(
f"Re-run the configuration script without '--yes' to set the HuggingFace token interactively, or use one of the environment variables: {', '.join(hf_envvars)}"
)
print("=" * shutil.get_terminal_size()[0])
return access_token
# --------------------------------------------- # ---------------------------------------------
def download_with_progress_bar(model_url: str, model_dest: str, label: str = "the"): def download_with_progress_bar(model_url: str, model_dest: str, label: str = "the"):
@ -381,79 +290,270 @@ def get_root(root: str = None) -> str:
return Globals.root return Globals.root
# ------------------------------------- class editOptsForm(npyscreen.FormMultiPage):
def select_root(root: str, yes_to_all: bool = False):
default = root or os.path.expanduser("~/invokeai") def create(self):
if yes_to_all: old_opts = self.parentApp.old_opts
return default first_time = not (Globals.root / Globals.initfile).exists()
completer.set_default_dir(default) access_token = HfFolder.get_token()
completer.complete_extensions(())
completer.set_line(default) window_height, window_width = curses.initscr().getmaxyx()
directory = input( for i in [
f"Select a directory in which to install InvokeAI's models and configuration files [{default}]: " 'Configure startup settings. You can come back and change these later.',
).strip(" \\") 'Use ctrl-N and ctrl-P to move to the <N>ext and <P>revious fields.',
return directory or default 'Use cursor arrows to make a checkbox selection, and space to toggle.',
]:
self.add_widget_intelligent(
npyscreen.FixedText,
value=i,
editable=False,
color='CONTROL',
)
self.nextrely += 1
self.add_widget_intelligent(
npyscreen.TitleFixedText,
name="== BASIC OPTIONS ==",
begin_entry_at=0,
editable=False,
color="CONTROL",
scroll_exit=True
)
self.nextrely -= 1
self.add_widget_intelligent(
npyscreen.FixedText,
value='Select an output directory for images:',
editable=False,
color='CONTROL',
)
self.outdir = self.add_widget_intelligent(
npyscreen.TitleFilename,
name='(<tab> autocompletes, ctrl-N advances):',
value=old_opts.outdir or str(default_output_dir()),
select_dir=True,
must_exist=False,
use_two_lines=False,
labelColor='GOOD',
begin_entry_at=40,
scroll_exit=True,
)
self.nextrely += 1
self.add_widget_intelligent(
npyscreen.FixedText,
value='Activate the NSFW checker to blur images showing potential sexual imagery:',
editable=False,
color='CONTROL'
)
self.safety_checker = self.add_widget_intelligent(
npyscreen.Checkbox,
name='NSFW checker',
value=old_opts.safety_checker,
relx = 5,
scroll_exit=True,
)
self.nextrely += 1
for i in [
'If you have an account at HuggingFace you may paste your access token here',
'to allow InvokeAI to download styles & subjects from the "Concept Library".',
'See https://huggingface.co/settings/tokens',
]:
self.add_widget_intelligent(
npyscreen.FixedText,
value=i,
editable=False,
color='CONTROL',
)
self.hf_token = self.add_widget_intelligent(
npyscreen.TitlePassword,
name='Access Token (use shift-ctrl-V to paste):',
value=access_token,
begin_entry_at=42,
use_two_lines=False,
scroll_exit=True
)
self.nextrely += 1
self.add_widget_intelligent(
npyscreen.TitleFixedText,
name="== ADVANCED OPTIONS ==",
begin_entry_at=0,
editable=False,
color="CONTROL",
scroll_exit=True
)
self.nextrely -= 1
self.add_widget_intelligent(
npyscreen.TitleFixedText,
name="GPU Management",
begin_entry_at=0,
editable=False,
color="CONTROL",
scroll_exit=True
)
self.nextrely -= 1
self.free_gpu_mem = self.add_widget_intelligent(
npyscreen.Checkbox,
name='Free GPU memory after each generation',
value=old_opts.free_gpu_mem,
relx=5,
scroll_exit=True
)
self.xformers = self.add_widget_intelligent(
npyscreen.Checkbox,
name='Enable xformers support if available',
value=old_opts.xformers,
relx=5,
scroll_exit=True
)
self.always_use_cpu = self.add_widget_intelligent(
npyscreen.Checkbox,
name='Force CPU to be used on GPU systems',
value=old_opts.always_use_cpu,
relx=5,
scroll_exit=True
)
self.precision = self.add_widget_intelligent(
npyscreen.TitleSelectOne,
name='Precision',
values=PRECISION_CHOICES,
value=PRECISION_CHOICES.index(old_opts.precision),
begin_entry_at=3,
max_height=len(PRECISION_CHOICES)+1,
scroll_exit=True,
)
self.max_loaded_models = self.add_widget_intelligent(
IntTitleSlider,
name="Number of models to cache in CPU memory (each will use 2-4 GB!)",
value=old_opts.max_loaded_models,
out_of=10,
lowest=1,
begin_entry_at=4,
scroll_exit=True
)
self.nextrely += 1
self.add_widget_intelligent(
npyscreen.FixedText,
value='Directory containing embedding/textual inversion files:',
editable=False,
color='CONTROL',
)
self.embedding_path = self.add_widget_intelligent(
npyscreen.TitleFilename,
name='(<tab> autocompletes, ctrl-N advances):',
value=str(default_embedding_dir()),
select_dir=True,
must_exist=False,
use_two_lines=False,
labelColor='GOOD',
begin_entry_at=40,
scroll_exit=True,
)
self.nextrely += 1
self.add_widget_intelligent(
npyscreen.TitleFixedText,
name="== LICENSE ==",
begin_entry_at=0,
editable=False,
color="CONTROL",
scroll_exit=True
)
self.nextrely -= 1
for i in [
'BY DOWNLOADING THE STABLE DIFFUSION WEIGHT FILES, YOU AGREE TO HAVE READ',
'AND ACCEPTED THE CREATIVEML RESPONSIBLE AI LICENSE LOCATED AT',
'https://huggingface.co/spaces/CompVis/stable-diffusion-license'
]:
self.add_widget_intelligent(
npyscreen.FixedText,
value=i,
editable=False,
color='CONTROL',
)
self.license_acceptance = self.add_widget_intelligent(
npyscreen.Checkbox,
name='I accept the CreativeML Responsible AI License',
value=not first_time,
relx = 2,
scroll_exit=True
)
self.nextrely += 1
self.ok_button = self.add_widget_intelligent(
npyscreen.ButtonPress,
name='DONE',
relx= (window_width-len('DONE'))//2,
rely= -3,
when_pressed_function=self.on_ok
)
def on_ok(self):
options = self.marshall_arguments()
if self.validate_field_values(options):
self.parentApp.setNextForm(None)
self.editing = False
else:
self.editing = True
def validate_field_values(self, opt: Namespace) -> bool:
bad_fields = []
if not opt.license_acceptance:
bad_fields.append(
'Please accept the license terms before proceeding to model downloads'
)
if not Path(opt.outdir).parent.exists():
bad_fields.append(
f'The output directory does not seem to be valid. Please check that {str(Path(opt.outdir).parent)} is an existing directory.'
)
if not Path(opt.embedding_path).parent.exists():
bad_fields.append(
f'The embedding directory does not seem to be valid. Please check that {str(Path(opt.embedding_path).parent)} is an existing directory.'
)
if len(bad_fields) > 0:
message = "The following problems were detected and must be corrected:\n"
for problem in bad_fields:
message += f"* {problem}\n"
npyscreen.notify_confirm(message)
return False
else:
return True
def marshall_arguments(self):
new_opts = Namespace()
for attr in ['outdir','safety_checker','free_gpu_mem','max_loaded_models',
'xformers','always_use_cpu','embedding_path']:
setattr(new_opts, attr, getattr(self, attr).value)
new_opts.hf_token = self.hf_token.value
new_opts.license_acceptance = self.license_acceptance.value
new_opts.precision = PRECISION_CHOICES[self.precision.value[0]]
return new_opts
# ------------------------------------- class EditOptApplication(npyscreen.NPSAppManaged):
def select_outputs(root: str, yes_to_all: bool = False): def __init__(self, old_opts=argparse.Namespace):
default = os.path.normpath(os.path.join(root, "outputs")) super().__init__()
if yes_to_all: self.old_opts=old_opts
return default
completer.set_default_dir(os.path.expanduser("~"))
completer.complete_extensions(())
completer.set_line(default)
directory = input(
f"Select the default directory for image outputs [{default}]: "
).strip(" \\")
return directory or default
def onStart(self):
npyscreen.setTheme(npyscreen.Themes.DefaultTheme)
self.main = self.addForm(
"MAIN",
editOptsForm,
name='InvokeAI Startup Options',
)
def new_opts(self):
return self.main.marshall_arguments()
def edit_opts(old_opts: argparse.Namespace)->argparse.Namespace:
editApp = EditOptApplication(old_opts)
editApp.run()
return editApp.new_opts()
# ------------------------------------- # -------------------------------------
def initialize_rootdir(root: str, yes_to_all: bool = False): def initialize_rootdir(root: str, yes_to_all: bool = False):
print("** INITIALIZING INVOKEAI RUNTIME DIRECTORY **") print("** INITIALIZING INVOKEAI RUNTIME DIRECTORY **")
root_selected = False
while not root_selected:
outputs = select_outputs(root, yes_to_all)
outputs = (
outputs
if os.path.isabs(outputs)
else os.path.abspath(os.path.join(Globals.root, outputs))
)
print(f'\nInvokeAI image outputs will be placed into "{outputs}".')
if not yes_to_all:
root_selected = yes_or_no("Accept this location?")
else:
root_selected = True
print(
f'\nYou may change the chosen output directory at any time by editing the --outdir options in "{Globals.initfile}",'
)
print(
"You may also change the runtime directory by setting the environment variable INVOKEAI_ROOT.\n"
)
enable_safety_checker = True
if not yes_to_all:
print(
"The NSFW (not safe for work) checker blurs out images that potentially contain sexual imagery."
)
print(
"It can be selectively enabled at run time with --nsfw_checker, and disabled with --no-nsfw_checker."
)
print(
"The following option will set whether the checker is enabled by default. Like other options, you can"
)
print(f"change this setting later by editing the file {Globals.initfile}.")
print(
"This is NOT recommended for systems with less than 6G VRAM because of the checker's memory requirements."
)
enable_safety_checker = yes_or_no(
"Enable the NSFW checker by default?", enable_safety_checker
)
safety_checker = "--nsfw_checker" if enable_safety_checker else "--no-nsfw_checker"
for name in ( for name in (
"models", "models",
@ -469,51 +569,80 @@ def initialize_rootdir(root: str, yes_to_all: bool = False):
if not os.path.samefile(configs_src, configs_dest): if not os.path.samefile(configs_src, configs_dest):
shutil.copytree(configs_src, configs_dest, dirs_exist_ok=True) shutil.copytree(configs_src, configs_dest, dirs_exist_ok=True)
init_file = os.path.join(Globals.root, Globals.initfile) # -------------------------------------
def do_edit_opt_form(old_opts: argparse.Namespace)->argparse.Namespace:
print(f'Creating the initialization file at "{init_file}".\n') editApp = EditOptApplication(old_opts)
with open(init_file, "w") as f: editApp.run()
f.write( return editApp.new_opts()
f"""# InvokeAI initialization file
# This is the InvokeAI initialization file, which contains command-line default values.
# Feel free to edit. If anything goes wrong, you can re-initialize this file by deleting
# or renaming it and then running invokeai-configure again.
# the --outdir option controls the default location of image files.
--outdir="{outputs}"
# generation arguments
{safety_checker}
# You may place other frequently-used startup commands here, one or more per line.
# Examples:
# --web --host=0.0.0.0
# --steps=20
# -Ak_euler_a -C10.0
#
"""
)
# ------------------------------------- # -------------------------------------
class ProgressBar: def edit_options(init_file: Path):
def __init__(self, model_name="file"): # get current settings from initfile
self.pbar = None opt = Args().parse_args()
self.name = model_name new_opt = do_edit_opt_form(opt)
write_opts(new_opt, init_file)
def __call__(self, block_num, block_size, total_size): # -------------------------------------
if not self.pbar: def write_opts(opts: Namespace, init_file: Path):
self.pbar = tqdm( '''
desc=self.name, Update the invokeai.init file with values from opts Namespace
initial=0, '''
unit="iB", # touch file if it doesn't exist
unit_scale=True, if not init_file.exists():
unit_divisor=1000, with open(init_file,'w') as f:
total=total_size, f.write(INIT_FILE_PREAMBLE)
)
self.pbar.update(block_size)
# We want to write in the changed arguments without clobbering
# any other initialization values the user has entered. There is
# no good way to do this because of the one-way nature of
# argparse: i.e. --outdir could be --outdir, --out, or -o
# initfile needs to be replaced with a fully structured format
# such as yaml; this is a hack that will work much of the time
args_to_skip = re.compile('^--?(o|out|no-xformer|xformer|free|no-nsfw|nsfw|prec|max_load|embed)')
new_file = f'{init_file}.new'
try:
lines = open(init_file,'r').readlines()
with open(new_file,'w') as out_file:
for line in lines:
if not args_to_skip.match(line):
out_file.write(line)
out_file.write(f'''
--outdir={opts.outdir}
--embedding_path={opts.embedding_path}
--precision={opts.precision}
--max_loaded_models={int(opts.max_loaded_models)}
--{'no-' if not opts.safety_checker else ''}nsfw_checker
--{'no-' if not opts.xformers else ''}xformers
{'--free_gpu_mem' if opts.free_gpu_mem else ''}
{'--always_use_cpu' if opts.always_use_cpu else ''}
''')
except OSError as e:
print(f'** An error occurred while writing the init file: {str(e)}')
os.replace(new_file, init_file)
if opts.hf_token:
HfLogin(opts.hf_token)
# -------------------------------------
def default_output_dir()->Path:
return Globals.root / 'outputs'
# -------------------------------------
def default_embedding_dir()->Path:
return Globals.root / 'embeddings'
# -------------------------------------
def write_default_options(initfile: Path):
opt = Namespace(
outdir=str(default_output_dir()),
embedding_path=str(default_embedding_dir()),
nsfw_checker=True,
max_loaded_models=2,
free_gpu_mem=True
)
write_opts(opt, initfile)
# ------------------------------------- # -------------------------------------
def main(): def main():
parser = argparse.ArgumentParser(description="InvokeAI model downloader") parser = argparse.ArgumentParser(description="InvokeAI model downloader")
@ -568,11 +697,14 @@ def main():
try: try:
# We check for to see if the runtime directory is correctly initialized. # We check for to see if the runtime directory is correctly initialized.
if Globals.root == "" or not os.path.exists( init_file = Path(Globals.root, Globals.initfile)
os.path.join(Globals.root, "invokeai.init") if not init_file.exists():
):
initialize_rootdir(Globals.root, opt.yes_to_all) initialize_rootdir(Globals.root, opt.yes_to_all)
save_hf_token(opt.yes_to_all)
if opt.yes_to_all:
write_default_options(init_file)
else:
edit_options(init_file)
print("\n** DOWNLOADING SUPPORT MODELS **") print("\n** DOWNLOADING SUPPORT MODELS **")
download_bert() download_bert()

View File

@ -19,6 +19,7 @@ from typing import List
import npyscreen import npyscreen
import torch import torch
from datetime import datetime
from pathlib import Path from pathlib import Path
from npyscreen import widget from npyscreen import widget
from omegaconf import OmegaConf from omegaconf import OmegaConf
@ -104,6 +105,9 @@ class addModelsForm(npyscreen.FormMultiPageAction):
color="CONTROL", color="CONTROL",
) )
self.nextrely -= 1 self.nextrely -= 1
# if user has already installed some initial models, then don't patronize them
# by showing more recommendations
show_recommended = self.installed_models is None or len(self.installed_models)==0
self.models_selected = self.add_widget_intelligent( self.models_selected = self.add_widget_intelligent(
npyscreen.MultiSelect, npyscreen.MultiSelect,
name="Install Starter Models", name="Install Starter Models",
@ -111,7 +115,7 @@ class addModelsForm(npyscreen.FormMultiPageAction):
value=[ value=[
self.starter_model_list.index(x) self.starter_model_list.index(x)
for x in self.starter_model_list for x in self.starter_model_list
if x in recommended_models if show_recommended and x in recommended_models
], ],
max_height=len(starter_model_labels) + 1, max_height=len(starter_model_labels) + 1,
relx = 4, relx = 4,
@ -202,7 +206,7 @@ class addModelsForm(npyscreen.FormMultiPageAction):
def on_cancel(self): def on_cancel(self):
self.parentApp.setNextForm(None) self.parentApp.setNextForm(None)
self.ParentApp.user_cancelled = True self.parentApp.user_cancelled = True
self.editing = False self.editing = False
def marshall_arguments(self): def marshall_arguments(self):
@ -216,9 +220,13 @@ class addModelsForm(npyscreen.FormMultiPageAction):
.import_model_paths: list of URLs, repo_ids and file paths to import .import_model_paths: list of URLs, repo_ids and file paths to import
.convert_to_diffusers: if True, convert legacy checkpoints into diffusers .convert_to_diffusers: if True, convert legacy checkpoints into diffusers
''' '''
# we're using a global here rather than storing the result in the parentapp
# due to some bug in npyscreen that is causing attributes to be lost
selections = self.parentApp.user_selections
# starter models to install/remove # starter models to install/remove
starter_models = dict(map(lambda x: (self.starter_model_list[x], True), self.models_selected.value)) starter_models = dict(map(lambda x: (self.starter_model_list[x], True), self.models_selected.value))
self.parentApp.purge_deleted_models=False selections.purge_deleted_models=False
if hasattr(self,'previously_installed_models'): if hasattr(self,'previously_installed_models'):
unchecked = [ unchecked = [
self.previously_installed_models.values[x] self.previously_installed_models.values[x]
@ -228,127 +236,52 @@ class addModelsForm(npyscreen.FormMultiPageAction):
starter_models.update( starter_models.update(
map(lambda x: (x, False), unchecked) map(lambda x: (x, False), unchecked)
) )
self.parentApp.purge_deleted_models = self.purge_deleted.value selections.purge_deleted_models = self.purge_deleted.value
self.parentApp.starter_models=starter_models selections.starter_models=starter_models
# load directory and whether to scan on startup # load directory and whether to scan on startup
if self.show_directory_fields.value: if self.show_directory_fields.value:
self.parentApp.scan_directory = self.autoload_directory.value selections.scan_directory = self.autoload_directory.value
self.parentApp.autoscan_on_startup = self.autoscan_on_startup.value selections.autoscan_on_startup = self.autoscan_on_startup.value
else: else:
self.parentApp.scan_directory = None selections.scan_directory = None
self.parentApp.autoscan_on_startup = False selections.autoscan_on_startup = False
# URLs and the like # URLs and the like
self.parentApp.import_model_paths = self.import_model_paths.value.split() selections.import_model_paths = self.import_model_paths.value.split()
self.parentApp.convert_to_diffusers = self.convert_models.value[0] == 1 selections.convert_to_diffusers = self.convert_models.value[0] == 1
# big chunk of dead code
# was intended to be a status area in which output of installation steps (including tqdm) was logged in real time.
# Not used at the current time but retained for possible future implementation.
# class Log(object):
# def __init__(self, writable):
# self.writable = writable
# def __enter__(self):
# self._stdout = sys.stdout
# sys.stdout = self.writable
# return self
# def __exit__(self, *args):
# sys.stdout = self._stdout
# class outputForm(npyscreen.ActionForm):
# def create(self):
# self.done = False
# self.buffer = self.add_widget(
# npyscreen.BufferPager,
# editable=False,
# )
# def write(self,string):
# if string != '\n':
# self.buffer.buffer([string])
# def beforeEditing(self):
# if self.done:
# return
# installApp = self.parentApp
# with Log(self):
# models_to_remove = [x for x in installApp.starter_models if not installApp.starter_models[x]]
# models_to_install = [x for x in installApp.starter_models if installApp.starter_models[x]]
# directory_to_scan = installApp.scan_directory
# scan_at_startup = installApp.autoscan_on_startup
# potential_models_to_install = installApp.import_model_paths
# convert_to_diffusers = installApp.convert_to_diffusers
# print(f'these models will be removed: {models_to_remove}')
# print(f'these models will be installed: {models_to_install}')
# print(f'this directory will be scanned: {directory_to_scan}')
# print(f'these things will be downloaded: {potential_models_to_install}')
# print(f'scan at startup time? {scan_at_startup}')
# print(f'convert to diffusers? {convert_to_diffusers}')
# print(f'\nPress OK to proceed or Cancel.')
# def on_cancel(self):
# self.buffer.buffer(['goodbye!'])
# self.parentApp.setNextForm(None)
# self.editing = False
# def on_ok(self):
# if self.done:
# self.on_cancel()
# return
# installApp = self.parentApp
# with Log(self):
# models_to_remove = [x for x in installApp.starter_models if not installApp.starter_models[x]]
# models_to_install = [x for x in installApp.starter_models if installApp.starter_models[x]]
# directory_to_scan = installApp.scan_directory
# scan_at_startup = installApp.autoscan_on_startup
# potential_models_to_install = installApp.import_model_paths
# convert_to_diffusers = installApp.convert_to_diffusers
# install_requested_models(
# install_initial_models = models_to_install,
# remove_models = models_to_remove,
# scan_directory = Path(directory_to_scan) if directory_to_scan else None,
# external_models = potential_models_to_install,
# scan_at_startup = scan_at_startup,
# convert_to_diffusers = convert_to_diffusers,
# precision = 'float32' if installApp.opt.full_precision else choose_precision(torch.device(choose_torch_device())),
# config_file_path = Path(installApp.opt.config_file) if installApp.opt.config_file else None,
# )
# self.done = True
class AddModelApplication(npyscreen.NPSAppManaged): class AddModelApplication(npyscreen.NPSAppManaged):
def __init__(self, saved_args=None): def __init__(self):
super().__init__() super().__init__()
self.user_cancelled = False
self.models_to_install = None self.models_to_install = None
self.user_selections = Namespace(
starter_models = None,
purge_deleted_models = False,
scan_directory = None,
autoscan_on_startup = None,
import_model_paths = None,
convert_to_diffusers = None
)
def onStart(self): def onStart(self):
npyscreen.setTheme(npyscreen.Themes.DefaultTheme) npyscreen.setTheme(npyscreen.Themes.DefaultTheme)
self.main = self.addForm( self.main_form = self.addForm(
"MAIN", "MAIN",
addModelsForm, addModelsForm,
name="Add/Remove Models", name="Add/Remove Models",
) )
# self.output = self.addForm(
# 'MONITOR_OUTPUT',
# outputForm,
# name='Model Install Output'
# )
# -------------------------------------------------------- # --------------------------------------------------------
def process_and_execute(app: npyscreen.NPSAppManaged): def process_and_execute(opt: Namespace, selections: Namespace):
models_to_remove = [x for x in app.starter_models if not app.starter_models[x]] models_to_remove = [x for x in selections.starter_models if not selections.starter_models[x]]
models_to_install = [x for x in app.starter_models if app.starter_models[x]] models_to_install = [x for x in selections.starter_models if selections.starter_models[x]]
directory_to_scan = app.scan_directory directory_to_scan = selections.scan_directory
scan_at_startup = app.autoscan_on_startup scan_at_startup = selections.autoscan_on_startup
potential_models_to_install = app.import_model_paths potential_models_to_install = selections.import_model_paths
convert_to_diffusers = app.convert_to_diffusers convert_to_diffusers = selections.convert_to_diffusers
install_requested_models( install_requested_models(
install_initial_models = models_to_install, install_initial_models = models_to_install,
remove_models = models_to_remove, remove_models = models_to_remove,
@ -356,9 +289,9 @@ def process_and_execute(app: npyscreen.NPSAppManaged):
external_models = potential_models_to_install, external_models = potential_models_to_install,
scan_at_startup = scan_at_startup, scan_at_startup = scan_at_startup,
convert_to_diffusers = convert_to_diffusers, convert_to_diffusers = convert_to_diffusers,
precision = 'float32' if app.opt.full_precision else choose_precision(torch.device(choose_torch_device())), precision = 'float32' if opt.full_precision else choose_precision(torch.device(choose_torch_device())),
purge_deleted = app.purge_deleted_models, purge_deleted = selections.purge_deleted_models,
config_file_path = Path(app.opt.config_file) if app.opt.config_file else None, config_file_path = Path(opt.config_file) if opt.config_file else None,
) )
# -------------------------------------------------------- # --------------------------------------------------------
@ -371,9 +304,10 @@ def select_and_download_models(opt: Namespace):
) )
else: else:
installApp = AddModelApplication() installApp = AddModelApplication()
installApp.opt = opt
installApp.run() installApp.run()
process_and_execute(installApp)
if not installApp.user_cancelled:
process_and_execute(opt, installApp.user_selections)
# ------------------------------------- # -------------------------------------
def main(): def main():