diff --git a/CHANGELOG.md b/CHANGELOG.md index de6e87827..8a516bd83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,23 @@ - extra networks: - support for **tags** - search by tag, list tags, add to prompt, etc. + show tags on hover, search by tag, list tags, add to prompt, etc. + - **styles** are now also listed as part of extra networks + existing `styles.csv` is converted upon startup to individual styles inside `models/style` + this is stage one of new styles functionality + old styles interface is still available, but will be removed in future + - cache file lists for much faster startup - simplified handling of **descriptions** now shows on-mouse-over without the need for user interaction + - **metadata** and **info** buttons only show if there is actual content - diffusers: - ability to interrupt (stop/skip) model generate - add `diffusers_force_zeros` setting create zero-tensor for prompt if prompt is empty (positive or negative) - add `diffusers_aesthetics_score` setting automatically guide unet towards higher pleasing images +- fixes: + - pre-create all system folders on startup ## Update for 2023-08-30 diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 54b703bcb..a06ab27d8 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -37,6 +37,7 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): "hash": lora_on_disk.shorthash, "preview": self.find_preview(path), "description": self.find_description(path), + "info": self.find_info(path), "search_term": self.search_terms_from_path(lora_on_disk.filename) + ' '.join(tags.keys()), "prompt": prompt, "local_preview": f"{path}.{shared.opts.samples_format}", diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index c3b32f254..42bb1b1a6 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit c3b32f254368ffc96b6aae9eab0a8fe7450a359f +Subproject commit 42bb1b1a62181bf9ef5224015b1189a201ae103c diff --git a/installer.py b/installer.py index a5b67f9e4..cd8921748 100644 --- a/installer.py +++ b/installer.py @@ -478,6 +478,10 @@ def check_modified_files(): res = git('status --porcelain') files = [x[2:].strip() for x in res.split('\n')] files = [x for x in files if len(x) > 0 and (not x.startswith('extensions')) and (not x.startswith('wiki')) and (not x.endswith('.json')) and ('.log' not in x)] + deleted = [x for x in files if not os.path.exists(x)] + if len(deleted) > 0: + log.warning(f'Deleted files: {files}') + files = [x for x in files if os.path.exists(x) and not os.path.isdir(x)] if len(files) > 0: log.warning(f'Modified files: {files}') except Exception: diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 9f09fef35..c286c2ce6 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -278,4 +278,21 @@ function extraNetworksSearchButton(event) { updateInput(searchTextarea); } +let desiredStyle = ''; +function selectStyle(name) { + desiredStyle = name; + const tabname = getENActiveTab(); + const button = gradioApp().querySelector(`#${tabname}_styles_select`); + button.click(); +} + +function applyStyles(styles) { + let newStyles = []; + if (styles) newStyles = Array.isArray(styles) ? styles : [styles]; + const index = newStyles.indexOf(desiredStyle); + if (index > -1) newStyles.splice(index, 1); + else newStyles.push(desiredStyle); + return newStyles.join('|'); +} + onUiLoaded(setupExtraNetworks); diff --git a/javascript/style.css b/javascript/style.css index a42aea0e0..c5a72bfdf 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -235,7 +235,7 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt .extra-networks-page { display: flex } .extra-networks .custom-button { width: 120px; width: 100%; background: none; justify-content: left; text-align: left; padding: 2px 8px 2px 16px; text-indent: -8px; box-shadow: none; line-break: auto; } .extra-networks .custom-button:hover { background: var(--button-primary-background-fill) } -.extra-network-cards { display: flex; flex-wrap: wrap; overflow-y: scroll; overflow-x: hidden; width: -webkit-fill-available; } +.extra-network-cards { display: flex; flex-wrap: wrap; overflow-y: auto; overflow-x: hidden; align-content: flex-start; width: -webkit-fill-available; } .extra-network-cards .card { height: fit-content; margin: 0.5em; position: relative; scroll-snap-align: start; scroll-margin-top: 0; } .extra-network-cards .card .overlay { position: absolute; bottom: 0; padding: 0.2em; z-index: 10; width: 100%; background: none; } .extra-network-cards .card:hover .overlay { background: rgba(0, 0, 0, 0.40); } diff --git a/modules/api/models.py b/modules/api/models.py index a14d6d299..142d4db55 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -204,7 +204,7 @@ class PreprocessResponse(BaseModel): fields = {} for key, metadata in shared.opts.data_labels.items(): - value = shared.opts.data.get(key) + value = shared.opts.data.get(key) or shared.opts.data_labels[key].default optType = shared.opts.typemap.get(type(metadata.default), type(value)) if metadata is not None: diff --git a/modules/paths.py b/modules/paths.py index 1943bca63..ab6ef42e4 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -50,29 +50,37 @@ for d, must_exist, what, _options in path_dirs: paths[what] = d -def create_paths(opts): +def create_paths(opts, log=None): def create_path(folder): if folder is None or folder == '': return if not os.path.exists(folder): try: os.makedirs(folder, exist_ok=True) - except Exception: - pass + if log is not None: + log.debug(f'Create path: {folder}') + except Exception as e: + if log is not None: + log.error(f'Failed to create path: {folder} {e}') def fix_path(folder): - if opts.data.get(folder, None) is None or opts.data[folder] is None or opts.data[folder] == '': + tgt = opts.data.get(folder, None) or opts.data_labels[folder].default + if tgt is None or tgt == '': return - if os.path.isabs(opts.data[folder]) or (len(data_path) > 0 and opts.data[folder].startswith(data_path)) and not opts.data[folder].startswith(script_path): + if os.path.isabs(tgt) or (len(data_path) > 0 and tgt.startswith(data_path)) and not tgt.startswith(script_path): return - fullpath = os.path.join(data_path, opts.data[folder]) + fullpath = os.path.join(data_path, tgt) relpath = os.path.relpath(fullpath, script_path) opts.data[folder] = relpath return - create_path(fix_path('temp_dir')) + create_path(data_path) + create_path(script_path) + create_path(models_path) + create_path(sd_configs_path) create_path(extensions_dir) create_path(extensions_builtin_dir) + create_path(fix_path('temp_dir')) create_path(fix_path('hypernetwork_dir')) create_path(fix_path('ckpt_dir')) create_path(fix_path('vae_dir')) @@ -86,6 +94,7 @@ def create_paths(opts): create_path(fix_path('outdir_txt2img_grids')) create_path(fix_path('outdir_img2img_grids')) create_path(fix_path('outdir_save')) + create_path(fix_path('styles_dir')) class Prioritize: diff --git a/modules/processing.py b/modules/processing.py index a2c11040d..0ab9a8c83 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -536,7 +536,7 @@ def process_images(p: StableDiffusionProcessing) -> Processed: return None stored_opts = {} for k in p.override_settings.keys(): - stored_opts[k] = shared.opts.data.get(k, None) + stored_opts[k] = shared.opts.data.get(k, None) or shared.opts.data_labels[k].default try: # if no checkpoint override or the override checkpoint can't be found, remove override entry and load opts checkpoint if p.override_settings.get('sd_model_checkpoint', None) is not None and sd_models.checkpoint_aliases.get(p.override_settings.get('sd_model_checkpoint')) is None: @@ -575,6 +575,8 @@ def process_images(p: StableDiffusionProcessing) -> Processed: setattr(shared.opts, k, v) if k == 'sd_model_checkpoint': sd_models.reload_model_weights() + if k == 'sd_model_refiner': + sd_models.reload_model_weights() if k == 'sd_vae': sd_vae.reload_vae_weights() return res diff --git a/modules/sd_models.py b/modules/sd_models.py index bb15fd35d..743876837 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -329,12 +329,15 @@ def read_metadata_from_safetensors(filename): json_data = json_start + file.read(metadata_len-2) json_obj = json.loads(json_data) for k, v in json_obj.get("__metadata__", {}).items(): - res[k] = v + if k == 'format' and v == 'pt': + continue if isinstance(v, str) and v[0:1] == '{': try: res[k] = json.loads(v) except Exception: pass + else: + res[k] = v sd_metadata[filename] = res global sd_metadata_pending # pylint: disable=global-statement sd_metadata_pending += 1 diff --git a/modules/shared.py b/modules/shared.py index b990ed348..e88b7eaa5 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -631,7 +631,6 @@ options_templates.update(options_section(('interrogate', "Interrogate"), { })) options_templates.update(options_section(('extra_networks', "Extra Networks"), { - "ui_extra_networks_tab_reorder": OptionInfo("Checkpoints, Lora, LyCORIS, Textual Inversion, Hypernetworks", "Extra networks tab order"), "extra_networks_card_cover": OptionInfo("sidebar", "UI position", gr.Radio, lambda: {"choices": ["cover", "inline", "sidebar"]}), "extra_networks_height": OptionInfo(53, "UI height (%)", gr.Slider, {"minimum": 10, "maximum": 100, "step": 1}), "extra_networks_sidebar_width": OptionInfo(35, "UI sidebar width (%)", gr.Slider, {"minimum": 10, "maximum": 80, "step": 1}), @@ -820,7 +819,7 @@ opts.data['uni_pc_lower_order_final'] = opts.schedulers_use_loworder opts.data['uni_pc_order'] = opts.schedulers_solver_order log.info(f'Engine: backend={backend}') -prompt_styles = modules.styles.StyleDatabase(opts.styles_dir) +prompt_styles = modules.styles.StyleDatabase(opts) cmd_opts.disable_extension_access = (cmd_opts.share or cmd_opts.listen or (cmd_opts.server_name or False)) and not cmd_opts.insecure devices.device, devices.device_interrogate, devices.device_gfpgan, devices.device_esrgan, devices.device_codeformer = (devices.cpu if any(y in cmd_opts.use_cpu for y in [x, 'all']) else devices.get_optimal_device() for x in ['sd', 'interrogate', 'gfpgan', 'esrgan', 'codeformer']) device = devices.device diff --git a/modules/styles.py b/modules/styles.py index f611a2c55..a13da7bcd 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -2,11 +2,11 @@ from __future__ import annotations import csv import os -import os.path -import tempfile +import json import shutil import typing from installer import log +from modules import paths if typing.TYPE_CHECKING: @@ -18,6 +18,7 @@ class PromptStyle(typing.NamedTuple): name: str prompt: str negative_prompt: str + extra: str = "" def merge_prompts(style_prompt: str, prompt: str) -> str: @@ -41,26 +42,38 @@ def apply_styles_to_prompt(prompt, styles): class StyleDatabase: - def __init__(self, path: str): + def __init__(self, opts): self.no_style = PromptStyle("None", "", "") self.styles = {} - self.path = path + self.path = opts.styles_dir + if os.path.isfile(opts.styles_dir): + legacy_file = opts.styles_dir + self.load_csv(legacy_file) + opts.styles_dir = os.path.join(paths.models_path, "styles") + self.path = opts.styles_dir + self.mkdir() + self.save_styles(opts.styles_dir, verbose=True) + log.debug(f'Migrated styles: file={legacy_file} folder={self.path}') + self.mkdir() self.reload() + def mkdir(self): + if not os.path.isdir(self.path): + os.makedirs(self.path, exist_ok=True) + log.debug(f'Created styles: folder={self.path}') + def reload(self): self.styles.clear() - if not os.path.exists(self.path): - self.save_styles(self.path) - with open(self.path, "r", encoding="utf-8-sig", newline='') as file: - reader = csv.DictReader(file, skipinitialspace=True) - for row in reader: + for fn in os.listdir(self.path): + if not fn.endswith(".json"): + continue + with open(os.path.join(self.path, fn), 'r', encoding='utf-8') as f: try: - prompt = row["prompt"] if "prompt" in row else row["text"] - negative_prompt = row.get("negative_prompt", "") - self.styles[row["name"]] = PromptStyle(row["name"], prompt, negative_prompt) - except Exception: - log.error(f'Styles error: {self.path} {row}') - log.debug(f'Loaded styles: {self.path} items={len(self.styles.keys())}') + style = json.load(f) + self.styles[style["name"]] = PromptStyle(style["name"], style["prompt"], style["negative"], style["extra"]) + except Exception as e: + log.error(f'Failed to load style: file={fn} error={e}') + log.debug(f'Loaded styles: folder={self.path} items={len(self.styles.keys())}') def get_style_prompts(self, styles): return [self.styles.get(x, self.no_style).prompt for x in styles] @@ -74,7 +87,38 @@ class StyleDatabase: def apply_negative_styles_to_prompt(self, prompt, styles): return apply_styles_to_prompt(prompt, [self.styles.get(x, self.no_style).negative_prompt for x in styles]) - def save_styles(self, path: str) -> None: + def save_styles(self, path, verbose=False): + for name in list(self.styles): + style = { + "name": name, + "prompt": self.styles[name].prompt, + "negative": self.styles[name].negative_prompt, + "extra": "", + } + fn = os.path.join(path, name + ".json") + try: + with open(fn, 'w', encoding='utf-8') as f: + json.dump(style, f, indent=2) + if verbose: + log.debug(f'Saved style: name={name} file={fn}') + except Exception as e: + log.error(f'Failed to save style: name={name} file={path} error={e}') + log.debug(f'Saved styles: {path} {len(self.styles.keys())}') + + def load_csv(self, legacy_file): + with open(legacy_file, "r", encoding="utf-8-sig", newline='') as file: + reader = csv.DictReader(file, skipinitialspace=True) + for row in reader: + try: + prompt = row["prompt"] if "prompt" in row else row["text"] + negative_prompt = row.get("negative_prompt", "") + self.styles[row["name"]] = PromptStyle(row["name"], prompt, negative_prompt) + except Exception: + log.error(f'Styles error: file={legacy_file} row={row}') + log.debug(f'Loaded legacy styles: file={legacy_file} items={len(self.styles.keys())}') + + def save_csv(self, path: str) -> None: + import tempfile basedir = os.path.dirname(path) if basedir is not None and len(basedir) > 0: os.makedirs(basedir, exist_ok=True) @@ -83,5 +127,5 @@ class StyleDatabase: writer = csv.DictWriter(file, fieldnames=PromptStyle._fields) writer.writeheader() writer.writerows(style._asdict() for k, style in self.styles.items()) - log.debug(f'Saved styles: {path} {len(self.styles.keys())}') + log.debug(f'Saved legacy styles: {path} {len(self.styles.keys())}') shutil.move(temp_path, path) diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index b01fe41e0..9715f01e7 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -120,11 +120,11 @@ class EmbeddingDatabase: return embedding def get_expected_shape(self): + if shared.backend == shared.Backend.DIFFUSERS: + return 0 if shared.sd_model is None: shared.log.error('Model not loaded') return 0 - if shared.backend == shared.Backend.DIFFUSERS: - return 0 vec = shared.sd_model.cond_stage_model.encode_embedding_init_text(",", 1) return vec.shape[1] @@ -213,11 +213,14 @@ class EmbeddingDatabase: self.skipped_embeddings[name] = embedding def load_from_dir(self, embdir): + if sd_models.model_data.sd_model is None: + shared.log.info('Skipping embeddings load: model not loaded') + return if not os.path.isdir(embdir.path): return is_ext = extension_filter(['.PNG', '.WEBP', '.JXL', '.AVIF', '.BIN', '.PT', '.SAFETENSORS']) - is_not_preview = lambda fp: not next(iter(os.path.splitext(fp))).upper().endswith('.PREVIEW') + is_not_preview = lambda fp: not next(iter(os.path.splitext(fp))).upper().endswith('.PREVIEW') # pylint: disable=unnecessary-lambda-assignment for file_path in [*filter(lambda fp: is_ext(fp) and is_not_preview(fp), directory_files(embdir.path))]: try: diff --git a/modules/ui.py b/modules/ui.py index af6eb6819..0c914847e 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -117,6 +117,10 @@ def apply_styles(prompt, prompt_neg, styles): return [gr.Textbox.update(value=prompt), gr.Textbox.update(value=prompt_neg), gr.Dropdown.update(value=[])] +def parse_style(styles): + return styles.split('|') + + def process_interrogate(interrogation_function, mode, ii_input_files, ii_input_dir, ii_output_dir, *ii_singles): if mode in {0, 1, 3, 4}: return [interrogation_function(ii_singles[mode]), None] @@ -278,6 +282,8 @@ def create_toprow(is_img2img): with gr.Row(elem_id=f"{id_part}_styles_row"): prompt_styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[k for k, v in modules.shared.prompt_styles.styles.items()], value=[], multiselect=True) create_refresh_button(prompt_styles, modules.shared.prompt_styles.reload, lambda: {"choices": [k for k, v in modules.shared.prompt_styles.styles.items()]}, f"refresh_{id_part}_styles") + prompt_styles_btn = gr.Button('Apply', elem_id=f"{id_part}_styles_select", visible=False) + prompt_styles_btn.click(_js="applyStyles", fn=parse_style, inputs=[prompt_styles], outputs=[prompt_styles]) return prompt, prompt_styles, negative_prompt, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, extra_networks_button, token_counter, token_button, negative_token_counter, negative_token_button diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index cef9aa03d..c38b3abdd 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -91,6 +91,7 @@ class ExtraNetworksPage: self.html = '' self.items = [] self.missing_thumbs = [] + self.refresh_time = None # class additional is to keep old extensions happy self.card = '''
@@ -102,13 +103,12 @@ class ExtraNetworksPage:
âēī¸ 🛅 - 📘 - â„šī¸ + {card_extra}
- ''' # noqa: RUF001 + ''' def refresh(self): pass @@ -168,11 +168,11 @@ class ExtraNetworksPage: created += 1 except Exception as e: shared.log.error(f'Extra network error creating thumbnail: {f} {e}') - if len(self.missing_thumbs) > 0: + if created > 0: shared.log.info(f"Extra network created thumbnails: {self.name} {created}") self.missing_thumbs.clear() - def create_html(self, tabname, skip = False): + def create_page(self, tabname, skip = False): t0 = time.time() self_name_id = self.name.replace(" ", "_") if skip: @@ -197,35 +197,35 @@ class ExtraNetworksPage:
""" for subdir in subdirs]) - try: - if len(self.html) > 0: - res = f"
{subdirs_html}
{self.html}
" - return res - self.html = '' + # try: + if len(self.html) > 0: + res = f"
{subdirs_html}
{self.html}
" + return res + self.html = '' + if self.refresh_time is None or len(self.items) == 0: try: self.items = list(self.list_items()) except Exception as e: self.items = [] shared.log.error(f'Extra networks error listing items: class={self.__class__} tab={tabname} {e}') - self.create_xyz_grid() - htmls = [] - items = self.items - for item in items: - self.metadata[item["name"]] = item.get("metadata", {}) - self.info[item["name"]] = self.find_info(item['filename']) - htmls.append(self.create_html_for_item(item, tabname)) - self.html += ''.join(htmls) - if len(subdirs_html) > 0 or len(self.html) > 0: - res = f"
{subdirs_html}
{self.html}
" - else: - return '' - t1 = time.time() - shared.log.debug(f'Extra networks: {self.name} items={len(self.items)} subdirs={len(subdirs)} time={round(t1-t0, 2)}') - threading.Thread(target=self.create_thumb).start() - return res - except Exception as e: - shared.log.error(f'Extra networks page error: title={self.title} tab={tabname} class={e.__class__.__name__} {e}') - return f"
Extra network error
{e}
" + self.create_xyz_grid() + htmls = [] + for item in self.items: + self.metadata[item["name"]] = item.get("metadata", {}) + self.info[item["name"]] = item.get('info', None) or self.find_info(item['filename']) + htmls.append(self.create_html(item, tabname)) + self.html += ''.join(htmls) + if len(subdirs_html) > 0 or len(self.html) > 0: + res = f"
{subdirs_html}
{self.html}
" + else: + return '' + t1 = time.time() + shared.log.debug(f'Extra networks: {self.name} items={len(self.items)} subdirs={len(subdirs)} time={round(t1-t0, 2)}') + threading.Thread(target=self.create_thumb).start() + return res + # except Exception as e: + # shared.log.error(f'Extra networks page error: title={self.title} tab={tabname} class={e.__class__.__name__} {e}') + # return f"
Extra network error
{e}
" def list_items(self): raise NotImplementedError @@ -233,7 +233,7 @@ class ExtraNetworksPage: def allowed_directories_for_previews(self): return [] - def create_html_for_item(self, item, tabname): + def create_html(self, item, tabname): try: args = { "tabname": json.dumps(tabname), @@ -252,11 +252,19 @@ class ExtraNetworksPage: "card_click": item.get("onclick", '"' + html.escape(f'return cardClicked({item.get("prompt", None)}, {"true" if self.allow_negative_prompt else "false"})') + '"'), "card_save_preview": '"' + html.escape('return saveCardPreview(event)') + '"', "card_save_desc": '"' + html.escape('return saveCardDescription(event)') + '"', - "card_read_meta": '"' + html.escape(f'return readCardMetadata(event, {json.dumps(self.name)}, {json.dumps(item["name"])})') + '"', - "card_read_info": '"' + html.escape(f'return readCardInformation(event, {json.dumps(self.name)}, {json.dumps(item["name"])})') + '"', + "card_extra": "", } - if item.get("alias", None) is not None: - args['title'] += f'\nAlias: {item["alias"]}' + metadata = item.get("metadata", None) + if metadata is not None and len(metadata) > 0: + card_read_meta = '"' + html.escape(f'return readCardMetadata(event, {json.dumps(self.name)}, {json.dumps(item["name"])})') + '"' + args['card_extra'] += f'📘' + info = item.get("info", None) + if info is not None and len(info) > 0: + card_read_info = '"' + html.escape(f'return readCardInformation(event, {json.dumps(self.name)}, {json.dumps(item["name"])})') + '"' + args['card_extra'] += f'â„šī¸' # noqa + alias = item.get("alias", None) + if alias is not None: + args['title'] += f'\nAlias: {alias}' return self.card.format(**args) except Exception as e: shared.log.error(f'Extra networks item error: page={tabname} item={item["name"]} {e}') @@ -308,9 +316,11 @@ def register_default_pages(): from modules.ui_extra_networks_textual_inversion import ExtraNetworksPageTextualInversion from modules.ui_extra_networks_hypernets import ExtraNetworksPageHypernetworks from modules.ui_extra_networks_checkpoints import ExtraNetworksPageCheckpoints + from modules.ui_extra_networks_styles import ExtraNetworksPageStyles + register_page(ExtraNetworksPageCheckpoints()) + register_page(ExtraNetworksPageStyles()) register_page(ExtraNetworksPageTextualInversion()) register_page(ExtraNetworksPageHypernetworks()) - register_page(ExtraNetworksPageCheckpoints()) class ExtraNetworksUi: @@ -328,24 +338,10 @@ class ExtraNetworksUi: self.search = None -def sort_extra_pages(pages): - tab_order = [x.lower().strip() for x in shared.opts.ui_extra_networks_tab_reorder.split(",")] - - def tab_name_score(name): - name = name.lower() - for i, possible_match in enumerate(tab_order): - if possible_match in name: - return i - return len(pages) - - tab_scores = {page.name: (tab_name_score(page.name), original_index) for original_index, page in enumerate(pages)} - return sorted(pages, key=lambda x: tab_scores[x.name]) - - def create_ui(container, button, tabname, skip_indexing = False): ui = ExtraNetworksUi() ui.pages = [] - ui.stored_extra_pages = sort_extra_pages(extra_pages) + ui.stored_extra_pages = extra_pages ui.tabname = tabname with gr.Tabs(elem_id=tabname+"_extra_tabs"): button_refresh = ToolButton(refresh_symbol, elem_id=tabname+"_extra_refresh") @@ -359,9 +355,9 @@ def create_ui(container, button, tabname, skip_indexing = False): for page in ui.stored_extra_pages: shared.log.debug(f"Extra network page: {page.title} tab={tabname}") - page_html = page.create_html(ui.tabname, skip_indexing) + page_html = page.create_page(ui.tabname, skip_indexing) with gr.Tab(page.title, id=page.title.lower().replace(" ", "_"), elem_classes="extra-networks-tab"): - page_elem = gr.HTML(page_html, elem_id=tabname+page.name+"_extra_page", elem_classes="extra-networks-page") + page_elem = gr.HTML(page_html, elem_id=f'{tabname}{page.name}_extra_page', elem_classes="extra-networks-page") page_elem.change(fn=lambda: None, _js=f'() => refreshExtraNetworks("{tabname}")', inputs=[], outputs=[]) ui.pages.append(page_elem) @@ -378,8 +374,9 @@ def create_ui(container, button, tabname, skip_indexing = False): res = [] for pg in ui.stored_extra_pages: pg.html = '' + pg.refresh_time = None pg.refresh() - res.append(pg.create_html(ui.tabname)) + res.append(pg.create_page(ui.tabname)) ui.search.update(value = ui.search.value) return res @@ -396,7 +393,7 @@ def path_is_parent(parent_path, child_path): def setup_ui(ui, gallery): def save_preview(index, images, filename): if len(images) == 0: - return [page.create_html(ui.tabname) for page in ui.stored_extra_pages] + return [page.create_page(ui.tabname) for page in ui.stored_extra_pages] index = int(index) index = 0 if index < 0 else index index = len(images) - 1 if index >= len(images) else index @@ -415,11 +412,11 @@ def setup_ui(ui, gallery): shared.log.debug(f'Extra network delete thumbnail: {thumb}') os.remove(thumb) shared.log.info(f'Extra network save preview: {filename}') - return [page.create_html(ui.tabname) for page in ui.stored_extra_pages] + return [page.create_page(ui.tabname) for page in ui.stored_extra_pages] ui.button_save_preview.click( fn=save_preview, - _js="function(x, y, z){return [selected_gallery_index(), y, z]}", + _js="function(x, y, z) {return [selected_gallery_index(), y, z]}", inputs=[ui.preview_target_filename, gallery, ui.preview_target_filename], outputs=[*ui.pages] ) @@ -435,11 +432,11 @@ def setup_ui(ui, gallery): shared.log.info(f'Extra network save description: {filename} {desc}') except Exception as e: shared.log.error(f'Extra network save description: {filename} {e}') - return [page.create_html(ui.tabname) for page in ui.stored_extra_pages] + return [page.create_page(ui.tabname) for page in ui.stored_extra_pages] ui.button_save_description.click( fn=save_description, - _js="function(x,y) {return [x,y]}", + _js="function(x, y) { return [x, y] }", inputs=[ui.description_target_filename, ui.description], outputs=[*ui.pages] ) diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index 48d4e7bc5..7159445b5 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -23,6 +23,7 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): "hash": checkpoint.shorthash, "preview": self.find_preview(path), "description": self.find_description(path), + "info": self.find_info(path), "search_term": f'{self.search_terms_from_path(checkpoint.filename)} {(checkpoint.sha256 or "")} /{checkpoint.type}/', "onclick": '"' + html.escape(f"""return selectCheckpoint({json.dumps(name)})""") + '"', "local_preview": f"{path}.{shared.opts.samples_format}", diff --git a/modules/ui_extra_networks_hypernets.py b/modules/ui_extra_networks_hypernets.py index 40cdb339a..66ddca527 100644 --- a/modules/ui_extra_networks_hypernets.py +++ b/modules/ui_extra_networks_hypernets.py @@ -18,6 +18,7 @@ class ExtraNetworksPageHypernetworks(ui_extra_networks.ExtraNetworksPage): "filename": path, "preview": self.find_preview(path), "description": self.find_description(path), + "info": self.find_info(path), "search_term": self.search_terms_from_path(path), "prompt": json.dumps(f""), "local_preview": f"{path}.preview.{shared.opts.samples_format}", diff --git a/modules/ui_extra_networks_styles.py b/modules/ui_extra_networks_styles.py new file mode 100644 index 000000000..2ccbed93b --- /dev/null +++ b/modules/ui_extra_networks_styles.py @@ -0,0 +1,34 @@ +import os +import html +import json + +from modules import shared, ui_extra_networks + + +class ExtraNetworksPageStyles(ui_extra_networks.ExtraNetworksPage): + def __init__(self): + super().__init__('Styles') + + def refresh(self): + shared.prompt_styles.reload() + + def list_items(self): + styles = list(shared.prompt_styles.styles) + for style in styles: + path = os.path.join(shared.opts.styles_dir, style) + txt = f'Prompt: {shared.prompt_styles.styles[style].prompt}' + negative = shared.prompt_styles.styles[style].negative_prompt + if negative is not None and len(negative) > 0: + txt += f'\nNegative: {negative}' + yield { + "name": style, + "search_term": path, + "filename": path, + "preview": self.find_preview(path), + "description": txt, + "onclick": '"' + html.escape(f"""return selectStyle({json.dumps(style)})""") + '"', + "local_preview": f"{path}.{shared.opts.samples_format}", + } + + def allowed_directories_for_previews(self): + return [v for v in [shared.opts.styles_dir] if v is not None] diff --git a/modules/ui_extra_networks_textual_inversion.py b/modules/ui_extra_networks_textual_inversion.py index 9e99ba211..9a4d0fd4b 100644 --- a/modules/ui_extra_networks_textual_inversion.py +++ b/modules/ui_extra_networks_textual_inversion.py @@ -29,6 +29,7 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): "filename": embedding.filename, "preview": self.find_preview(path), "description": self.find_description(path), + "info": self.find_info(path), "search_term": self.search_terms_from_path(embedding.filename), "prompt": json.dumps(os.path.splitext(embedding.name)[0]), "local_preview": f"{path}.preview.{shared.opts.samples_format}", diff --git a/requirements.txt b/requirements.txt index 85a95c0e5..4c12440ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -47,7 +47,7 @@ requests==2.31.0 tqdm==4.65.0 accelerate==0.20.3 opencv-python-headless==4.7.0.72 -diffusers==0.20.1 +diffusers==0.20.2 einops==0.4.1 gradio==3.41.2 huggingface_hub==0.16.4 diff --git a/webui.py b/webui.py index f90f18cec..c8d1214ca 100644 --- a/webui.py +++ b/webui.py @@ -242,7 +242,7 @@ def start_common(): log.info(f'Using data path: {shared.cmd_opts.data_dir}') if shared.cmd_opts.models_dir is not None and len(shared.cmd_opts.models_dir) > 0: log.info(f'Using models path: {shared.cmd_opts.data_dir}') - create_paths(opts) + create_paths(opts, log) async_policy() initialize() if shared.opts.clean_temp_dir_at_start: