mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
multiple small fixes
1. Contents of autoscan directory field are restored after doing an installation. 2. Activate dialogue to choose V2 parameterization when importing from a directory. 3. Remove autoscan directory from init file when its checkbox is unselected. 4. Add widget cycling behavior to install models form.
This commit is contained in:
@ -14,6 +14,7 @@ import curses
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
import traceback
|
||||
from argparse import Namespace
|
||||
from multiprocessing import Process
|
||||
from multiprocessing.connection import Connection, Pipe
|
||||
@ -128,10 +129,10 @@ class addModelsForm(CyclingForm, npyscreen.FormMultiPage):
|
||||
SingleSelectColumns,
|
||||
values=[
|
||||
'STARTER MODELS',
|
||||
'MORE DIFFUSION MODELS',
|
||||
'CONTROLNET MODELS',
|
||||
'LORA/LYCORIS MODELS',
|
||||
'TEXTUAL INVERSION MODELS',
|
||||
'MORE MODELS',
|
||||
'CONTROLNETS',
|
||||
'LORA/LYCORIS',
|
||||
'TEXTUAL INVERSION',
|
||||
],
|
||||
value=[self.current_tab],
|
||||
columns = 5,
|
||||
@ -183,33 +184,28 @@ class addModelsForm(CyclingForm, npyscreen.FormMultiPage):
|
||||
BufferBox,
|
||||
name='Log Messages',
|
||||
editable=False,
|
||||
max_height = 20,
|
||||
max_height = 16,
|
||||
)
|
||||
|
||||
self.nextrely += 1
|
||||
done_label = "INSTALL/REMOVE NOW"
|
||||
done_label = "APPLY CHANGES"
|
||||
back_label = "BACK"
|
||||
button_length = len(done_label)
|
||||
button_offset = 0
|
||||
if self.multipage:
|
||||
button_length += len(back_label) + 1
|
||||
button_offset += len(back_label) + 1
|
||||
self.back_button = self.add_widget_intelligent(
|
||||
npyscreen.ButtonPress,
|
||||
name=back_label,
|
||||
relx=(window_width - button_length) // 2,
|
||||
rely=-3,
|
||||
when_pressed_function=self.on_back,
|
||||
)
|
||||
self.ok_button = self.add_widget_intelligent(
|
||||
npyscreen.ButtonPress, # OffsetButtonPress,
|
||||
npyscreen.ButtonPress,
|
||||
name=done_label,
|
||||
relx=button_offset + 1 + (window_width - button_length) // 2,
|
||||
relx=(window_width - len(done_label)) // 2,
|
||||
rely=-3,
|
||||
when_pressed_function=self.on_execute
|
||||
)
|
||||
|
||||
label = "INSTALL AND EXIT"
|
||||
label = "APPLY CHANGES & EXIT"
|
||||
self.done = self.add_widget_intelligent(
|
||||
npyscreen.ButtonPress,
|
||||
name=label,
|
||||
@ -289,13 +285,14 @@ class addModelsForm(CyclingForm, npyscreen.FormMultiPage):
|
||||
model_type: str,
|
||||
window_width: int=120,
|
||||
install_prompt: str=None,
|
||||
add_purge_deleted: bool=False,
|
||||
)->dict[str,npyscreen.widget]:
|
||||
'''Generic code to create model selection widgets'''
|
||||
widgets = dict()
|
||||
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 = window_width // (max_width+8) # 8 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."
|
||||
|
||||
@ -324,27 +321,28 @@ class addModelsForm(CyclingForm, npyscreen.FormMultiPage):
|
||||
scroll_exit=True,
|
||||
)
|
||||
)
|
||||
|
||||
self.nextrely += 1
|
||||
widgets.update(
|
||||
label2 = self.add_widget_intelligent(
|
||||
npyscreen.TitleFixedText,
|
||||
name="Additional URLs or HuggingFace repo_ids to install (Space separated. Use shift-control-V to paste):",
|
||||
relx=4,
|
||||
color='CONTROL',
|
||||
editable=False,
|
||||
scroll_exit=True
|
||||
)
|
||||
)
|
||||
|
||||
self.nextrely -= 1
|
||||
if add_purge_deleted:
|
||||
self.nextrely += 1
|
||||
widgets.update(
|
||||
purge_deleted = self.add_widget_intelligent(
|
||||
npyscreen.Checkbox,
|
||||
name="Purge unchecked diffusers models from disk",
|
||||
value=False,
|
||||
scroll_exit=True,
|
||||
relx=4,
|
||||
)
|
||||
)
|
||||
widgets['purge_deleted'].when_value_edited = lambda: self.sync_purge_buttons(widgets['purge_deleted'])
|
||||
|
||||
self.nextrely += 1
|
||||
widgets.update(
|
||||
download_ids = self.add_widget_intelligent(
|
||||
TextBox,
|
||||
name = "Additional URLs, or HuggingFace repo_ids to install (Space separated. Use shift-control-V to paste):",
|
||||
max_height=4,
|
||||
scroll_exit=True,
|
||||
editable=True,
|
||||
relx=4
|
||||
)
|
||||
)
|
||||
return widgets
|
||||
@ -361,27 +359,18 @@ class addModelsForm(CyclingForm, npyscreen.FormMultiPage):
|
||||
predefined_models,
|
||||
'Diffusers',
|
||||
window_width,
|
||||
install_prompt="Additional diffusers models already installed. Uncheck to purge from disk.",
|
||||
install_prompt="Additional diffusers models already installed.",
|
||||
add_purge_deleted=True
|
||||
)
|
||||
|
||||
self.nextrely += 2
|
||||
widgets.update(
|
||||
purge_deleted = self.add_widget_intelligent(
|
||||
npyscreen.Checkbox,
|
||||
name="Purge unchecked diffusers models from disk",
|
||||
value=False,
|
||||
scroll_exit=True,
|
||||
relx=4,
|
||||
)
|
||||
)
|
||||
label = "Directory to scan for models to automatically import (<tab> autocompletes):"
|
||||
self.nextrely += 2
|
||||
self.nextrely += 1
|
||||
widgets.update(
|
||||
autoload_directory = self.add_widget_intelligent(
|
||||
# npyscreen.TitleFilename,
|
||||
FileBox,
|
||||
max_height=3,
|
||||
name=label,
|
||||
value=str(config.autoconvert_dir) if config.autoconvert_dir else None,
|
||||
select_dir=True,
|
||||
must_exist=True,
|
||||
use_two_lines=False,
|
||||
@ -394,12 +383,11 @@ class addModelsForm(CyclingForm, npyscreen.FormMultiPage):
|
||||
autoscan_on_startup = self.add_widget_intelligent(
|
||||
npyscreen.Checkbox,
|
||||
name="Scan and import from this directory each time InvokeAI starts",
|
||||
value=False,
|
||||
value=config.autoconvert_dir is not None,
|
||||
relx=4,
|
||||
scroll_exit=True,
|
||||
)
|
||||
)
|
||||
widgets['purge_deleted'].when_value_edited = lambda: self.sync_purge_buttons(widgets['purge_deleted'])
|
||||
return widgets
|
||||
|
||||
def sync_purge_buttons(self,checkbox):
|
||||
@ -557,16 +545,22 @@ class addModelsForm(CyclingForm, npyscreen.FormMultiPage):
|
||||
self.subprocess_connection = None
|
||||
self.monitor.entry_widget.buffer(['** Action Complete **'])
|
||||
self.display()
|
||||
# rebuild the form, saving log messages
|
||||
|
||||
# rebuild the form, saving and restoring some of the fields that need to be preserved.
|
||||
saved_messages = self.monitor.entry_widget.values
|
||||
multipage = self.multipage
|
||||
autoload_dir = self.diffusers_models['autoload_directory'].value
|
||||
autoscan = self.diffusers_models['autoscan_on_startup'].value
|
||||
|
||||
app.main_form = app.addForm(
|
||||
"MAIN", addModelsForm, name="Install Stable Diffusion Models", multipage=multipage,
|
||||
"MAIN", addModelsForm, name="Install Stable Diffusion Models", multipage=self.multipage,
|
||||
)
|
||||
app.switchForm("MAIN")
|
||||
|
||||
app.main_form.monitor.entry_widget.values = saved_messages
|
||||
app.main_form.monitor.entry_widget.buffer([''],scroll_end=True)
|
||||
|
||||
app.main_form.diffusers_models['autoload_directory'].value = autoload_dir
|
||||
app.main_form.diffusers_models['autoscan_on_startup'].value = autoscan
|
||||
|
||||
###############################################################
|
||||
|
||||
def list_additional_diffusers_models(self,
|
||||
@ -822,6 +816,7 @@ def select_and_download_models(opt: Namespace):
|
||||
|
||||
if do_listings(opt):
|
||||
pass
|
||||
# this processes command line additions/removals
|
||||
elif opt.diffusers or opt.controlnets or opt.textual_inversions or opt.loras:
|
||||
action = 'remove_models' if opt.delete else 'install_models'
|
||||
diffusers_args = {'diffusers':ModelInstallList(remove_models=opt.diffusers or [])} \
|
||||
@ -846,6 +841,8 @@ def select_and_download_models(opt: Namespace):
|
||||
diffusers=ModelInstallList(install_models=recommended_datasets()),
|
||||
precision=precision,
|
||||
)
|
||||
|
||||
# this is where the TUI is called
|
||||
else:
|
||||
# needed because the torch library is loaded, even though we don't use it
|
||||
torch.multiprocessing.set_start_method("spawn")
|
||||
@ -856,12 +853,14 @@ def select_and_download_models(opt: Namespace):
|
||||
installApp = AddModelApplication(opt)
|
||||
try:
|
||||
installApp.run()
|
||||
except KeyboardInterrupt:
|
||||
form = installApp.main_form
|
||||
if form.subprocess and form.subprocess.is_alive():
|
||||
logger.info('Terminating subprocesses')
|
||||
form.subprocess.terminate()
|
||||
form.subprocess = None
|
||||
except KeyboardInterrupt as e:
|
||||
if hasattr(installApp,'main_form'):
|
||||
if installApp.main_form.subprocess \
|
||||
and installApp.main_form.subprocess.is_alive():
|
||||
logger.info('Terminating subprocesses')
|
||||
installApp.main_form.subprocess.terminate()
|
||||
installApp.main_form.subprocess = None
|
||||
raise e
|
||||
process_and_execute(opt, installApp.user_selections)
|
||||
|
||||
# -------------------------------------
|
||||
@ -970,7 +969,8 @@ def main():
|
||||
"Insufficient horizontal space for the interface. Please make your window wider and try again."
|
||||
)
|
||||
except Exception as e:
|
||||
print(f'An exception has occurred: {str(e)}')
|
||||
print(f'An exception has occurred: {str(e)} Details:')
|
||||
print(traceback.format_exc(), file=sys.stderr)
|
||||
input('Press any key to continue...')
|
||||
|
||||
|
||||
|
@ -17,7 +17,7 @@ from shutil import get_terminal_size
|
||||
from curses import BUTTON2_CLICKED,BUTTON3_CLICKED
|
||||
|
||||
# minimum size for UIs
|
||||
MIN_COLS = 140
|
||||
MIN_COLS = 120
|
||||
MIN_LINES = 50
|
||||
|
||||
# -------------------------------------
|
||||
@ -247,7 +247,7 @@ class SingleSelectColumns(SelectColumnBase, SingleSelectWithChanged):
|
||||
def h_cursor_line_right(self,ch):
|
||||
self.h_exit_down('bye bye')
|
||||
|
||||
class TextBox(npyscreen.MultiLineEdit):
|
||||
class TextBoxInner(npyscreen.MultiLineEdit):
|
||||
|
||||
def __init__(self,*args,**kwargs):
|
||||
super().__init__(*args,**kwargs)
|
||||
@ -292,54 +292,57 @@ class TextBox(npyscreen.MultiLineEdit):
|
||||
if bstate & (BUTTON2_CLICKED|BUTTON3_CLICKED):
|
||||
self.h_paste()
|
||||
|
||||
def update(self, clear=True):
|
||||
if clear:
|
||||
self.clear()
|
||||
# def update(self, clear=True):
|
||||
# if clear:
|
||||
# self.clear()
|
||||
|
||||
HEIGHT = self.height
|
||||
WIDTH = self.width
|
||||
# draw box.
|
||||
self.parent.curses_pad.hline(self.rely, self.relx, curses.ACS_HLINE, WIDTH)
|
||||
self.parent.curses_pad.hline(
|
||||
self.rely + HEIGHT, self.relx, curses.ACS_HLINE, WIDTH
|
||||
)
|
||||
self.parent.curses_pad.vline(
|
||||
self.rely, self.relx, curses.ACS_VLINE, self.height
|
||||
)
|
||||
self.parent.curses_pad.vline(
|
||||
self.rely, self.relx + WIDTH, curses.ACS_VLINE, HEIGHT
|
||||
)
|
||||
# HEIGHT = self.height
|
||||
# WIDTH = self.width
|
||||
# # draw box.
|
||||
# self.parent.curses_pad.hline(self.rely, self.relx, curses.ACS_HLINE, WIDTH)
|
||||
# self.parent.curses_pad.hline(
|
||||
# self.rely + HEIGHT, self.relx, curses.ACS_HLINE, WIDTH
|
||||
# )
|
||||
# self.parent.curses_pad.vline(
|
||||
# self.rely, self.relx, curses.ACS_VLINE, self.height
|
||||
# )
|
||||
# self.parent.curses_pad.vline(
|
||||
# self.rely, self.relx + WIDTH, curses.ACS_VLINE, HEIGHT
|
||||
# )
|
||||
|
||||
# draw corners
|
||||
self.parent.curses_pad.addch(
|
||||
self.rely,
|
||||
self.relx,
|
||||
curses.ACS_ULCORNER,
|
||||
)
|
||||
self.parent.curses_pad.addch(
|
||||
self.rely,
|
||||
self.relx + WIDTH,
|
||||
curses.ACS_URCORNER,
|
||||
)
|
||||
self.parent.curses_pad.addch(
|
||||
self.rely + HEIGHT,
|
||||
self.relx,
|
||||
curses.ACS_LLCORNER,
|
||||
)
|
||||
self.parent.curses_pad.addch(
|
||||
self.rely + HEIGHT,
|
||||
self.relx + WIDTH,
|
||||
curses.ACS_LRCORNER,
|
||||
)
|
||||
# # draw corners
|
||||
# self.parent.curses_pad.addch(
|
||||
# self.rely,
|
||||
# self.relx,
|
||||
# curses.ACS_ULCORNER,
|
||||
# )
|
||||
# self.parent.curses_pad.addch(
|
||||
# self.rely,
|
||||
# self.relx + WIDTH,
|
||||
# curses.ACS_URCORNER,
|
||||
# )
|
||||
# self.parent.curses_pad.addch(
|
||||
# self.rely + HEIGHT,
|
||||
# self.relx,
|
||||
# curses.ACS_LLCORNER,
|
||||
# )
|
||||
# self.parent.curses_pad.addch(
|
||||
# self.rely + HEIGHT,
|
||||
# self.relx + WIDTH,
|
||||
# curses.ACS_LRCORNER,
|
||||
# )
|
||||
|
||||
# fool our superclass into thinking drawing area is smaller - this is really hacky but it seems to work
|
||||
(relx, rely, height, width) = (self.relx, self.rely, self.height, self.width)
|
||||
self.relx += 1
|
||||
self.rely += 1
|
||||
self.height -= 1
|
||||
self.width -= 1
|
||||
super().update(clear=False)
|
||||
(self.relx, self.rely, self.height, self.width) = (relx, rely, height, width)
|
||||
# # fool our superclass into thinking drawing area is smaller - this is really hacky but it seems to work
|
||||
# (relx, rely, height, width) = (self.relx, self.rely, self.height, self.width)
|
||||
# self.relx += 1
|
||||
# self.rely += 1
|
||||
# self.height -= 1
|
||||
# self.width -= 1
|
||||
# super().update(clear=False)
|
||||
# (self.relx, self.rely, self.height, self.width) = (relx, rely, height, width)
|
||||
|
||||
class TextBox(npyscreen.BoxTitle):
|
||||
_contained_widget = TextBoxInner
|
||||
|
||||
class BufferBox(npyscreen.BoxTitle):
|
||||
_contained_widget = npyscreen.BufferPager
|
||||
@ -354,6 +357,9 @@ class ConfirmCancelPopup(fmPopup.ActionPopup):
|
||||
class FileBox(npyscreen.BoxTitle):
|
||||
_contained_widget = npyscreen.Filename
|
||||
|
||||
class PrettyTextBox(npyscreen.BoxTitle):
|
||||
_contained_widget = TextBox
|
||||
|
||||
def _wrap_message_lines(message, line_length):
|
||||
lines = []
|
||||
for line in message.split('\n'):
|
||||
|
Reference in New Issue
Block a user