installer enhancements

- Detect when install failed for some reason and print helpful error
  message rather than stack trace.

- Detect window size and resize to minimum acceptable values to provide
  better display of configure and install forms.
This commit is contained in:
Lincoln Stein 2023-02-22 19:18:07 -05:00
parent 8b6196e0a2
commit 3083356cf0
5 changed files with 69 additions and 17 deletions

View File

@ -346,7 +346,21 @@ class InvokeAiInstance:
# NOTE: currently the config script does its own arg parsing! this means the command-line switches # NOTE: currently the config script does its own arg parsing! this means the command-line switches
# from the installer will also automatically propagate down to the config script. # from the installer will also automatically propagate down to the config script.
# this may change in the future with config refactoring! # this may change in the future with config refactoring!
invokeai_configure.main() succeeded = False
try:
invokeai_configure.main()
succeeded = True
except ConnectionError as e:
print(f'A network error was encountered during configuration and download: {str(e)}')
except OSError as e:
print(f'An OS error was encountered during configuration and download: {str(e)}')
except Exception as e:
print(f'A problem was encountered during the configuration and download steps: {str(e)}')
finally:
if not succeeded:
print('You may be able to finish the process by launching "invoke.sh" or "invoke.bat"')
print('from within the "invokeai" directory, and choosing options 5 and/or 6.')
print('Alternatively you can relaunch the installer.')
def install_user_scripts(self): def install_user_scripts(self):
""" """

View File

@ -8,7 +8,6 @@
# #
print("Loading Python libraries...\n") print("Loading Python libraries...\n")
import argparse import argparse
import curses
import io import io
import os import os
import re import re
@ -19,6 +18,7 @@ import warnings
from argparse import Namespace from argparse import Namespace
from pathlib import Path from pathlib import Path
from urllib import request from urllib import request
from shutil import get_terminal_size
import npyscreen import npyscreen
import torch import torch
@ -46,7 +46,8 @@ from .model_install_backend import (
recommended_datasets, recommended_datasets,
hf_download_with_resume, hf_download_with_resume,
) )
from .widgets import IntTitleSlider, CenteredButtonPress from .widgets import IntTitleSlider, CenteredButtonPress, set_min_terminal_size
warnings.filterwarnings("ignore") warnings.filterwarnings("ignore")
@ -64,6 +65,10 @@ SD_Configs = Path(global_config_dir()) / "stable-diffusion"
Datasets = OmegaConf.load(Dataset_path) Datasets = OmegaConf.load(Dataset_path)
# minimum size for the UI
MIN_COLS = 120
MIN_LINES = 45
INIT_FILE_PREAMBLE = """# InvokeAI initialization file INIT_FILE_PREAMBLE = """# InvokeAI initialization file
# This is the InvokeAI initialization file, which contains command-line default values. # 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 # Feel free to edit. If anything goes wrong, you can re-initialize this file by deleting
@ -109,8 +114,6 @@ Add the '--help' argument to see all of the command-line switches available for
# --------------------------------------------- # ---------------------------------------------
def yes_or_no(prompt: str, default_yes=True): def yes_or_no(prompt: str, default_yes=True):
completer.set_options(["yes", "no"])
completer.complete_extensions(None) # turn off path-completion mode
default = "y" if default_yes else "n" default = "y" if default_yes else "n"
response = input(f"{prompt} [{default}] ") or default response = input(f"{prompt} [{default}] ") or default
if default_yes: if default_yes:
@ -332,8 +335,7 @@ class editOptsForm(npyscreen.FormMultiPage):
old_opts = self.parentApp.invokeai_opts old_opts = self.parentApp.invokeai_opts
first_time = not (Globals.root / Globals.initfile).exists() first_time = not (Globals.root / Globals.initfile).exists()
access_token = HfFolder.get_token() access_token = HfFolder.get_token()
window_width,window_height = get_terminal_size()
window_height, window_width = curses.initscr().getmaxyx()
for i in [ for i in [
"Configure startup settings. You can come back and change these later.", "Configure startup settings. You can come back and change these later.",
"Use ctrl-N and ctrl-P to move to the <N>ext and <P>revious fields.", "Use ctrl-N and ctrl-P to move to the <N>ext and <P>revious fields.",
@ -676,6 +678,8 @@ def run_console_ui(
) -> (Namespace, Namespace): ) -> (Namespace, Namespace):
# parse_args() will read from init file if present # parse_args() will read from init file if present
invokeai_opts = default_startup_options(initfile) invokeai_opts = default_startup_options(initfile)
set_min_terminal_size(MIN_COLS, MIN_LINES)
editApp = EditOptApplication(program_opts, invokeai_opts) editApp = EditOptApplication(program_opts, invokeai_opts)
editApp.run() editApp.run()
if editApp.user_cancelled: if editApp.user_cancelled:
@ -683,7 +687,6 @@ def run_console_ui(
else: else:
return (editApp.new_opts, editApp.user_selections) return (editApp.new_opts, editApp.user_selections)
# ------------------------------------- # -------------------------------------
def write_opts(opts: Namespace, init_file: Path): def write_opts(opts: Namespace, init_file: Path):
""" """

View File

@ -10,7 +10,6 @@ The work is actually done in backend code in model_install_backend.py.
""" """
import argparse import argparse
import curses
import os import os
import sys import sys
import traceback import traceback
@ -22,15 +21,23 @@ import npyscreen
import torch import torch
from npyscreen import widget from npyscreen import widget
from omegaconf import OmegaConf from omegaconf import OmegaConf
from shutil import get_terminal_size
from ..devices import choose_precision, choose_torch_device from ..devices import choose_precision, choose_torch_device
from ..globals import Globals, global_config_dir from ..globals import Globals, global_config_dir
from .model_install_backend import (Dataset_path, default_config_file, from .model_install_backend import (Dataset_path, default_config_file,
default_dataset, get_root, default_dataset, get_root,
install_requested_models, install_requested_models,
recommended_datasets) recommended_datasets,
)
from .widgets import (MultiSelectColumns, TextBox, from .widgets import (MultiSelectColumns, TextBox,
OffsetButtonPress, CenteredTitleText) OffsetButtonPress, CenteredTitleText,
set_min_terminal_size,
)
# minimum size for the UI
MIN_COLS = 120
MIN_LINES = 45
class addModelsForm(npyscreen.FormMultiPage): class addModelsForm(npyscreen.FormMultiPage):
# for responsive resizing - disabled # for responsive resizing - disabled
@ -50,7 +57,7 @@ class addModelsForm(npyscreen.FormMultiPage):
super().__init__(parentApp=parentApp, name=name, *args, **keywords) super().__init__(parentApp=parentApp, name=name, *args, **keywords)
def create(self): def create(self):
window_height, window_width = curses.initscr().getmaxyx() window_width, window_height = get_terminal_size()
starter_model_labels = self._get_starter_model_labels() starter_model_labels = self._get_starter_model_labels()
recommended_models = [ recommended_models = [
x x
@ -249,7 +256,7 @@ class addModelsForm(npyscreen.FormMultiPage):
) )
def _get_starter_model_labels(self) -> List[str]: def _get_starter_model_labels(self) -> List[str]:
window_height, window_width = curses.initscr().getmaxyx() window_width, window_height = get_terminal_size()
label_width = 25 label_width = 25
checkbox_width = 4 checkbox_width = 4
spacing_width = 2 spacing_width = 2
@ -268,7 +275,7 @@ class addModelsForm(npyscreen.FormMultiPage):
] ]
def _get_columns(self) -> int: def _get_columns(self) -> int:
window_height, window_width = curses.initscr().getmaxyx() window_width, window_height = get_terminal_size()
cols = ( cols = (
4 4
if window_width > 240 if window_width > 240
@ -362,7 +369,6 @@ class AddModelApplication(npyscreen.NPSAppManaged):
"MAIN", addModelsForm, name="Install Stable Diffusion Models" "MAIN", addModelsForm, name="Install Stable Diffusion Models"
) )
# -------------------------------------------------------- # --------------------------------------------------------
def process_and_execute(opt: Namespace, selections: Namespace): def process_and_execute(opt: Namespace, selections: Namespace):
models_to_remove = [ models_to_remove = [
@ -409,6 +415,7 @@ def select_and_download_models(opt: Namespace):
precision=precision, precision=precision,
) )
else: else:
set_min_terminal_size(MIN_COLS, MIN_LINES)
installApp = AddModelApplication() installApp = AddModelApplication()
installApp.run() installApp.run()

View File

@ -67,6 +67,9 @@ def install_requested_models(
purge_deleted: bool = False, purge_deleted: bool = False,
config_file_path: Path = None, config_file_path: Path = None,
): ):
'''
Entry point for installing/deleting starter models, or installing external models.
'''
config_file_path=config_file_path or default_config_file() config_file_path=config_file_path or default_config_file()
if not config_file_path.exists(): if not config_file_path.exists():
open(config_file_path,'w') open(config_file_path,'w')

View File

@ -2,8 +2,34 @@
Widget class definitions used by model_select.py, merge_diffusers.py and textual_inversion.py Widget class definitions used by model_select.py, merge_diffusers.py and textual_inversion.py
''' '''
import math import math
import platform
import npyscreen import npyscreen
import sys
import curses import curses
import termios
import struct
import fcntl
from shutil import get_terminal_size
# -------------------------------------
def set_terminal_size(columns: int, lines: int):
os = platform.uname().system
if os=="Windows":
os.system(f'mode con: cols={columns} lines={lines}')
elif os in ['Darwin', 'Linux']:
winsize = struct.pack("HHHH", lines, columns, 0, 0)
fcntl.ioctl(0, termios.TIOCSWINSZ, winsize)
sys.stdout.write("\x1b[8;{rows};{cols}t".format(rows=lines, cols=columns))
sys.stdout.flush()
def set_min_terminal_size(min_cols: int, min_lines: int):
# make sure there's enough room for the ui
term_cols, term_lines = get_terminal_size()
cols = max(term_cols, min_cols)
lines = max(term_lines, min_lines)
set_terminal_size(cols,lines)
class IntSlider(npyscreen.Slider): class IntSlider(npyscreen.Slider):
def translate_value(self): def translate_value(self):
@ -23,7 +49,6 @@ class CenteredTitleText(npyscreen.TitleText):
maxy, maxx = self.parent.curses_pad.getmaxyx() maxy, maxx = self.parent.curses_pad.getmaxyx()
label = self.name label = self.name
self.relx = (maxx - len(label)) // 2 self.relx = (maxx - len(label)) // 2
begin_entry_at = -self.relx + 2
# ------------------------------------- # -------------------------------------
class CenteredButtonPress(npyscreen.ButtonPress): class CenteredButtonPress(npyscreen.ButtonPress):