Commit 110485d5 authored by Kohaku-Blueleaf's avatar Kohaku-Blueleaf

Merge branch 'dev' into test-fp8

parents 3d341ebc 0bb6e00b
...@@ -64,11 +64,14 @@ class ExtraOptionsSection(scripts.Script): ...@@ -64,11 +64,14 @@ class ExtraOptionsSection(scripts.Script):
p.override_settings[name] = value p.override_settings[name] = value
shared.options_templates.update(shared.options_section(('ui', "User interface"), { shared.options_templates.update(shared.options_section(('settings_in_ui', "Settings in UI", "ui"), {
"extra_options_txt2img": shared.OptionInfo([], "Options in main UI - txt2img", ui_components.DropdownMulti, lambda: {"choices": list(shared.opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings").info("setting entries that also appear in txt2img interfaces").needs_reload_ui(), "settings_in_ui": shared.OptionHTML("""
"extra_options_img2img": shared.OptionInfo([], "Options in main UI - img2img", ui_components.DropdownMulti, lambda: {"choices": list(shared.opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings").info("setting entries that also appear in img2img interfaces").needs_reload_ui(), This page allows you to add some settings to the main interface of txt2img and img2img tabs.
"extra_options_cols": shared.OptionInfo(1, "Options in main UI - number of columns", gr.Number, {"precision": 0}).needs_reload_ui(), """),
"extra_options_accordion": shared.OptionInfo(False, "Options in main UI - place into an accordion").needs_reload_ui() "extra_options_txt2img": shared.OptionInfo([], "Settings for txt2img", ui_components.DropdownMulti, lambda: {"choices": list(shared.opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings").info("setting entries that also appear in txt2img interfaces").needs_reload_ui(),
"extra_options_img2img": shared.OptionInfo([], "Settings for img2img", ui_components.DropdownMulti, lambda: {"choices": list(shared.opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings").info("setting entries that also appear in img2img interfaces").needs_reload_ui(),
"extra_options_cols": shared.OptionInfo(1, "Number of columns for added settings", gr.Number, {"precision": 0}).needs_reload_ui(),
"extra_options_accordion": shared.OptionInfo(False, "Place added settings into an accordion").needs_reload_ui()
})) }))
...@@ -6,7 +6,6 @@ Original author: @tfernd Github: https://github.com/tfernd/HyperTile ...@@ -6,7 +6,6 @@ Original author: @tfernd Github: https://github.com/tfernd/HyperTile
from __future__ import annotations from __future__ import annotations
import functools
from dataclasses import dataclass from dataclasses import dataclass
from typing import Callable from typing import Callable
...@@ -189,20 +188,27 @@ DEPTH_LAYERS_XL = { ...@@ -189,20 +188,27 @@ DEPTH_LAYERS_XL = {
RNG_INSTANCE = random.Random() RNG_INSTANCE = random.Random()
@cache
def random_divisor(value: int, min_value: int, /, max_options: int = 1) -> int: def get_divisors(value: int, min_value: int, /, max_options: int = 1) -> list[int]:
""" """
Returns a random divisor of value that Returns divisors of value that
x * min_value <= value x * min_value <= value
if max_options is 1, the behavior is deterministic in big -> small order, amount of divisors is limited by max_options
""" """
max_options = max(1, max_options) # at least 1 option should be returned
min_value = min(min_value, value) min_value = min(min_value, value)
# All big divisors of value (inclusive)
divisors = [i for i in range(min_value, value + 1) if value % i == 0] # divisors in small -> big order divisors = [i for i in range(min_value, value + 1) if value % i == 0] # divisors in small -> big order
ns = [value // i for i in divisors[:max_options]] # has at least 1 element # big -> small order ns = [value // i for i in divisors[:max_options]] # has at least 1 element # big -> small order
return ns
def random_divisor(value: int, min_value: int, /, max_options: int = 1) -> int:
"""
Returns a random divisor of value that
x * min_value <= value
if max_options is 1, the behavior is deterministic
"""
ns = get_divisors(value, min_value, max_options=max_options) # get cached divisors
idx = RNG_INSTANCE.randint(0, len(ns) - 1) idx = RNG_INSTANCE.randint(0, len(ns) - 1)
return ns[idx] return ns[idx]
...@@ -212,7 +218,7 @@ def set_hypertile_seed(seed: int) -> None: ...@@ -212,7 +218,7 @@ def set_hypertile_seed(seed: int) -> None:
RNG_INSTANCE.seed(seed) RNG_INSTANCE.seed(seed)
@functools.cache @cache
def largest_tile_size_available(width: int, height: int) -> int: def largest_tile_size_available(width: int, height: int) -> int:
""" """
Calculates the largest tile size available for a given width and height Calculates the largest tile size available for a given width and height
......
import hypertile import hypertile
from modules import scripts, script_callbacks, shared from modules import scripts, script_callbacks, shared
from scripts.hypertile_xyz import add_axis_options
class ScriptHypertile(scripts.Script): class ScriptHypertile(scripts.Script):
...@@ -17,7 +18,10 @@ class ScriptHypertile(scripts.Script): ...@@ -17,7 +18,10 @@ class ScriptHypertile(scripts.Script):
configure_hypertile(p.width, p.height, enable_unet=shared.opts.hypertile_enable_unet) configure_hypertile(p.width, p.height, enable_unet=shared.opts.hypertile_enable_unet)
def before_hr(self, p, *args): def before_hr(self, p, *args):
configure_hypertile(p.hr_upscale_to_x, p.hr_upscale_to_y, enable_unet=shared.opts.hypertile_enable_unet_secondpass or shared.opts.hypertile_enable_unet) # exclusive hypertile seed for the second pass
if not shared.opts.hypertile_enable_unet:
hypertile.set_hypertile_seed(p.all_seeds[0])
configure_hypertile(p.hr_upscale_to_x, p.hr_upscale_to_y, enable_unet=shared.opts.hypertile_enable_unet_secondpass)
def configure_hypertile(width, height, enable_unet=True): def configure_hypertile(width, height, enable_unet=True):
...@@ -57,12 +61,12 @@ def on_ui_settings(): ...@@ -57,12 +61,12 @@ def on_ui_settings():
"hypertile_enable_unet_secondpass": shared.OptionInfo(False, "Enable Hypertile U-Net for hires fix second pass"), "hypertile_enable_unet_secondpass": shared.OptionInfo(False, "Enable Hypertile U-Net for hires fix second pass"),
"hypertile_max_depth_unet": shared.OptionInfo(3, "Hypertile U-Net max depth", gr.Slider, {"minimum": 0, "maximum": 3, "step": 1}), "hypertile_max_depth_unet": shared.OptionInfo(3, "Hypertile U-Net max depth", gr.Slider, {"minimum": 0, "maximum": 3, "step": 1}),
"hypertile_max_tile_unet": shared.OptionInfo(256, "Hypertile U-net max tile size", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), "hypertile_max_tile_unet": shared.OptionInfo(256, "Hypertile U-net max tile size", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}),
"hypertile_swap_size_unet": shared.OptionInfo(3, "Hypertile U-net swap size", gr.Slider, {"minimum": 0, "maximum": 6, "step": 1}), "hypertile_swap_size_unet": shared.OptionInfo(3, "Hypertile U-net swap size", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}),
"hypertile_enable_vae": shared.OptionInfo(False, "Enable Hypertile VAE").info("minimal change in the generated picture"), "hypertile_enable_vae": shared.OptionInfo(False, "Enable Hypertile VAE").info("minimal change in the generated picture"),
"hypertile_max_depth_vae": shared.OptionInfo(3, "Hypertile VAE max depth", gr.Slider, {"minimum": 0, "maximum": 3, "step": 1}), "hypertile_max_depth_vae": shared.OptionInfo(3, "Hypertile VAE max depth", gr.Slider, {"minimum": 0, "maximum": 3, "step": 1}),
"hypertile_max_tile_vae": shared.OptionInfo(128, "Hypertile VAE max tile size", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), "hypertile_max_tile_vae": shared.OptionInfo(128, "Hypertile VAE max tile size", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}),
"hypertile_swap_size_vae": shared.OptionInfo(3, "Hypertile VAE swap size ", gr.Slider, {"minimum": 0, "maximum": 6, "step": 1}), "hypertile_swap_size_vae": shared.OptionInfo(3, "Hypertile VAE swap size ", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}),
} }
for name, opt in options.items(): for name, opt in options.items():
...@@ -71,3 +75,4 @@ def on_ui_settings(): ...@@ -71,3 +75,4 @@ def on_ui_settings():
script_callbacks.on_ui_settings(on_ui_settings) script_callbacks.on_ui_settings(on_ui_settings)
script_callbacks.on_before_ui(add_axis_options)
from modules import scripts
from modules.shared import opts
xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ == "xyz_grid.py"][0].module
def int_applier(value_name:str, min_range:int = -1, max_range:int = -1):
"""
Returns a function that applies the given value to the given value_name in opts.data.
"""
def validate(value_name:str, value:str):
value = int(value)
# validate value
if not min_range == -1:
assert value >= min_range, f"Value {value} for {value_name} must be greater than or equal to {min_range}"
if not max_range == -1:
assert value <= max_range, f"Value {value} for {value_name} must be less than or equal to {max_range}"
def apply_int(p, x, xs):
validate(value_name, x)
opts.data[value_name] = int(x)
return apply_int
def bool_applier(value_name:str):
"""
Returns a function that applies the given value to the given value_name in opts.data.
"""
def validate(value_name:str, value:str):
assert value.lower() in ["true", "false"], f"Value {value} for {value_name} must be either true or false"
def apply_bool(p, x, xs):
validate(value_name, x)
value_boolean = x.lower() == "true"
opts.data[value_name] = value_boolean
return apply_bool
def add_axis_options():
extra_axis_options = [
xyz_grid.AxisOption("[Hypertile] Unet First pass Enabled", str, bool_applier("hypertile_enable_unet"), choices=xyz_grid.boolean_choice(reverse=True)),
xyz_grid.AxisOption("[Hypertile] Unet Second pass Enabled", str, bool_applier("hypertile_enable_unet_secondpass"), choices=xyz_grid.boolean_choice(reverse=True)),
xyz_grid.AxisOption("[Hypertile] Unet Max Depth", int, int_applier("hypertile_max_depth_unet", 0, 3), choices=lambda: [str(x) for x in range(4)]),
xyz_grid.AxisOption("[Hypertile] Unet Max Tile Size", int, int_applier("hypertile_max_tile_unet", 0, 512)),
xyz_grid.AxisOption("[Hypertile] Unet Swap Size", int, int_applier("hypertile_swap_size_unet", 0, 64)),
xyz_grid.AxisOption("[Hypertile] VAE Enabled", str, bool_applier("hypertile_enable_vae"), choices=xyz_grid.boolean_choice(reverse=True)),
xyz_grid.AxisOption("[Hypertile] VAE Max Depth", int, int_applier("hypertile_max_depth_vae", 0, 3), choices=lambda: [str(x) for x in range(4)]),
xyz_grid.AxisOption("[Hypertile] VAE Max Tile Size", int, int_applier("hypertile_max_tile_vae", 0, 512)),
xyz_grid.AxisOption("[Hypertile] VAE Swap Size", int, int_applier("hypertile_swap_size_vae", 0, 64)),
]
set_a = {opt.label for opt in xyz_grid.axis_options}
set_b = {opt.label for opt in extra_axis_options}
if set_a.intersection(set_b):
return
xyz_grid.axis_options.extend(extra_axis_options)
...@@ -130,6 +130,10 @@ function extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePromp ...@@ -130,6 +130,10 @@ function extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePromp
} else { } else {
promptContainer.insertBefore(prompt, promptContainer.firstChild); promptContainer.insertBefore(prompt, promptContainer.firstChild);
} }
if (elem) {
elem.classList.toggle('extra-page-prompts-active', showNegativePrompt || showPrompt);
}
} }
...@@ -388,3 +392,9 @@ function extraNetworksRefreshSingleCard(page, tabname, name) { ...@@ -388,3 +392,9 @@ function extraNetworksRefreshSingleCard(page, tabname, name) {
} }
}); });
} }
window.addEventListener("keydown", function(event) {
if (event.key == "Escape") {
closePopup();
}
});
...@@ -44,3 +44,28 @@ onUiLoaded(function() { ...@@ -44,3 +44,28 @@ onUiLoaded(function() {
buttonShowAllPages.addEventListener("click", settingsShowAllTabs); buttonShowAllPages.addEventListener("click", settingsShowAllTabs);
}); });
onOptionsChanged(function() {
if (gradioApp().querySelector('#settings .settings-category')) return;
var sectionMap = {};
gradioApp().querySelectorAll('#settings > div > button').forEach(function(x) {
sectionMap[x.textContent.trim()] = x;
});
opts._categories.forEach(function(x) {
var section = x[0];
var category = x[1];
var span = document.createElement('SPAN');
span.textContent = category;
span.className = 'settings-category';
var sectionElem = sectionMap[section];
if (!sectionElem) return;
sectionElem.parentElement.insertBefore(span, sectionElem);
});
});
...@@ -32,7 +32,7 @@ def dump_cache(): ...@@ -32,7 +32,7 @@ def dump_cache():
with cache_lock: with cache_lock:
cache_filename_tmp = cache_filename + "-" cache_filename_tmp = cache_filename + "-"
with open(cache_filename_tmp, "w", encoding="utf8") as file: with open(cache_filename_tmp, "w", encoding="utf8") as file:
json.dump(cache_data, file, indent=4) json.dump(cache_data, file, indent=4, ensure_ascii=False)
os.replace(cache_filename_tmp, cache_filename) os.replace(cache_filename_tmp, cache_filename)
......
...@@ -55,7 +55,7 @@ def get_optimal_device(): ...@@ -55,7 +55,7 @@ def get_optimal_device():
def get_device_for(task): def get_device_for(task):
if task in shared.cmd_opts.use_cpu: if task in shared.cmd_opts.use_cpu or "all" in shared.cmd_opts.use_cpu:
return cpu return cpu
return get_optimal_device() return get_optimal_device()
......
from __future__ import annotations
import base64 import base64
import io import io
import json import json
...@@ -15,9 +16,6 @@ re_imagesize = re.compile(r"^(\d+)x(\d+)$") ...@@ -15,9 +16,6 @@ re_imagesize = re.compile(r"^(\d+)x(\d+)$")
re_hypernet_hash = re.compile("\(([0-9a-f]+)\)$") re_hypernet_hash = re.compile("\(([0-9a-f]+)\)$")
type_of_gr_update = type(gr.update()) type_of_gr_update = type(gr.update())
paste_fields = {}
registered_param_bindings = []
class ParamBinding: class ParamBinding:
def __init__(self, paste_button, tabname, source_text_component=None, source_image_component=None, source_tabname=None, override_settings_component=None, paste_field_names=None): def __init__(self, paste_button, tabname, source_text_component=None, source_image_component=None, source_tabname=None, override_settings_component=None, paste_field_names=None):
...@@ -30,6 +28,10 @@ class ParamBinding: ...@@ -30,6 +28,10 @@ class ParamBinding:
self.paste_field_names = paste_field_names or [] self.paste_field_names = paste_field_names or []
paste_fields: dict[str, dict] = {}
registered_param_bindings: list[ParamBinding] = []
def reset(): def reset():
paste_fields.clear() paste_fields.clear()
registered_param_bindings.clear() registered_param_bindings.clear()
...@@ -113,7 +115,6 @@ def register_paste_params_button(binding: ParamBinding): ...@@ -113,7 +115,6 @@ def register_paste_params_button(binding: ParamBinding):
def connect_paste_params_buttons(): def connect_paste_params_buttons():
binding: ParamBinding
for binding in registered_param_bindings: for binding in registered_param_bindings:
destination_image_component = paste_fields[binding.tabname]["init_img"] destination_image_component = paste_fields[binding.tabname]["init_img"]
fields = paste_fields[binding.tabname]["fields"] fields = paste_fields[binding.tabname]["fields"]
...@@ -313,6 +314,9 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model ...@@ -313,6 +314,9 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model
if "VAE Decoder" not in res: if "VAE Decoder" not in res:
res["VAE Decoder"] = "Full" res["VAE Decoder"] = "Full"
skip = set(shared.opts.infotext_skip_pasting)
res = {k: v for k, v in res.items() if k not in skip}
return res return res
...@@ -443,3 +447,4 @@ def connect_paste(button, paste_fields, input_comp, override_settings_component, ...@@ -443,3 +447,4 @@ def connect_paste(button, paste_fields, input_comp, override_settings_component,
outputs=[], outputs=[],
show_progress=False, show_progress=False,
) )
...@@ -47,10 +47,20 @@ def Block_get_config(self): ...@@ -47,10 +47,20 @@ def Block_get_config(self):
def BlockContext_init(self, *args, **kwargs): def BlockContext_init(self, *args, **kwargs):
if scripts.scripts_current is not None:
scripts.scripts_current.before_component(self, **kwargs)
scripts.script_callbacks.before_component_callback(self, **kwargs)
res = original_BlockContext_init(self, *args, **kwargs) res = original_BlockContext_init(self, *args, **kwargs)
add_classes_to_gradio_component(self) add_classes_to_gradio_component(self)
scripts.script_callbacks.after_component_callback(self, **kwargs)
if scripts.scripts_current is not None:
scripts.scripts_current.after_component(self, **kwargs)
return res return res
......
import logging import logging
import torch import torch
from torch import Tensor
import platform import platform
from modules.sd_hijack_utils import CondFunc from modules.sd_hijack_utils import CondFunc
from packaging import version from packaging import version
...@@ -51,6 +52,17 @@ def cumsum_fix(input, cumsum_func, *args, **kwargs): ...@@ -51,6 +52,17 @@ def cumsum_fix(input, cumsum_func, *args, **kwargs):
return cumsum_func(input, *args, **kwargs) return cumsum_func(input, *args, **kwargs)
# MPS workaround for https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14046
def interpolate_with_fp32_fallback(orig_func, *args, **kwargs) -> Tensor:
try:
return orig_func(*args, **kwargs)
except RuntimeError as e:
if "not implemented for" in str(e) and "Half" in str(e):
input_tensor = args[0]
return orig_func(input_tensor.to(torch.float32), *args[1:], **kwargs).to(input_tensor.dtype)
else:
print(f"An unexpected RuntimeError occurred: {str(e)}")
if has_mps: if has_mps:
if platform.mac_ver()[0].startswith("13.2."): if platform.mac_ver()[0].startswith("13.2."):
# MPS workaround for https://github.com/pytorch/pytorch/issues/95188, thanks to danieldk (https://github.com/explosion/curated-transformers/pull/124) # MPS workaround for https://github.com/pytorch/pytorch/issues/95188, thanks to danieldk (https://github.com/explosion/curated-transformers/pull/124)
...@@ -77,6 +89,9 @@ if has_mps: ...@@ -77,6 +89,9 @@ if has_mps:
# MPS workaround for https://github.com/pytorch/pytorch/issues/96113 # MPS workaround for https://github.com/pytorch/pytorch/issues/96113
CondFunc('torch.nn.functional.layer_norm', lambda orig_func, x, normalized_shape, weight, bias, eps, **kwargs: orig_func(x.float(), normalized_shape, weight.float() if weight is not None else None, bias.float() if bias is not None else bias, eps).to(x.dtype), lambda _, input, *args, **kwargs: len(args) == 4 and input.device.type == 'mps') CondFunc('torch.nn.functional.layer_norm', lambda orig_func, x, normalized_shape, weight, bias, eps, **kwargs: orig_func(x.float(), normalized_shape, weight.float() if weight is not None else None, bias.float() if bias is not None else bias, eps).to(x.dtype), lambda _, input, *args, **kwargs: len(args) == 4 and input.device.type == 'mps')
# MPS workaround for https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14046
CondFunc('torch.nn.functional.interpolate', interpolate_with_fp32_fallback, None)
# MPS workaround for https://github.com/pytorch/pytorch/issues/92311 # MPS workaround for https://github.com/pytorch/pytorch/issues/92311
if platform.processor() == 'i386': if platform.processor() == 'i386':
for funcName in ['torch.argmax', 'torch.Tensor.argmax']: for funcName in ['torch.argmax', 'torch.Tensor.argmax']:
......
import json import json
import sys import sys
from dataclasses import dataclass
import gradio as gr import gradio as gr
...@@ -8,13 +9,14 @@ from modules.shared_cmd_options import cmd_opts ...@@ -8,13 +9,14 @@ from modules.shared_cmd_options import cmd_opts
class OptionInfo: class OptionInfo:
def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, comment_before='', comment_after='', infotext=None, restrict_api=False): def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, comment_before='', comment_after='', infotext=None, restrict_api=False, category_id=None):
self.default = default self.default = default
self.label = label self.label = label
self.component = component self.component = component
self.component_args = component_args self.component_args = component_args
self.onchange = onchange self.onchange = onchange
self.section = section self.section = section
self.category_id = category_id
self.refresh = refresh self.refresh = refresh
self.do_not_save = False self.do_not_save = False
...@@ -63,7 +65,11 @@ class OptionHTML(OptionInfo): ...@@ -63,7 +65,11 @@ class OptionHTML(OptionInfo):
def options_section(section_identifier, options_dict): def options_section(section_identifier, options_dict):
for v in options_dict.values(): for v in options_dict.values():
if len(section_identifier) == 2:
v.section = section_identifier v.section = section_identifier
elif len(section_identifier) == 3:
v.section = section_identifier[0:2]
v.category_id = section_identifier[2]
return options_dict return options_dict
...@@ -158,7 +164,7 @@ class Options: ...@@ -158,7 +164,7 @@ class Options:
assert not cmd_opts.freeze_settings, "saving settings is disabled" assert not cmd_opts.freeze_settings, "saving settings is disabled"
with open(filename, "w", encoding="utf8") as file: with open(filename, "w", encoding="utf8") as file:
json.dump(self.data, file, indent=4) json.dump(self.data, file, indent=4, ensure_ascii=False)
def same_type(self, x, y): def same_type(self, x, y):
if x is None or y is None: if x is None or y is None:
...@@ -206,6 +212,17 @@ class Options: ...@@ -206,6 +212,17 @@ class Options:
d = {k: self.data.get(k, v.default) for k, v in self.data_labels.items()} d = {k: self.data.get(k, v.default) for k, v in self.data_labels.items()}
d["_comments_before"] = {k: v.comment_before for k, v in self.data_labels.items() if v.comment_before is not None} d["_comments_before"] = {k: v.comment_before for k, v in self.data_labels.items() if v.comment_before is not None}
d["_comments_after"] = {k: v.comment_after for k, v in self.data_labels.items() if v.comment_after is not None} d["_comments_after"] = {k: v.comment_after for k, v in self.data_labels.items() if v.comment_after is not None}
item_categories = {}
for item in self.data_labels.values():
category = categories.mapping.get(item.category_id)
category = "Uncategorized" if category is None else category.label
if category not in item_categories:
item_categories[category] = item.section[1]
# _categories is a list of pairs: [section, category]. Each section (a setting page) will get a special heading above it with the category as text.
d["_categories"] = [[v, k] for k, v in item_categories.items()] + [["Defaults", "Other"]]
return json.dumps(d) return json.dumps(d)
def add_option(self, key, info): def add_option(self, key, info):
...@@ -214,15 +231,40 @@ class Options: ...@@ -214,15 +231,40 @@ class Options:
self.data[key] = info.default self.data[key] = info.default
def reorder(self): def reorder(self):
"""reorder settings so that all items related to section always go together""" """Reorder settings so that:
- all items related to section always go together
- all sections belonging to a category go together
- sections inside a category are ordered alphabetically
- categories are ordered by creation order
Category is a superset of sections: for category "postprocessing" there could be multiple sections: "face restoration", "upscaling".
This function also changes items' category_id so that all items belonging to a section have the same category_id.
"""
category_ids = {}
section_categories = {}
section_ids = {}
settings_items = self.data_labels.items() settings_items = self.data_labels.items()
for _, item in settings_items: for _, item in settings_items:
if item.section not in section_ids: if item.section not in section_categories:
section_ids[item.section] = len(section_ids) section_categories[item.section] = item.category_id
for _, item in settings_items:
item.category_id = section_categories.get(item.section)
for category_id in categories.mapping:
if category_id not in category_ids:
category_ids[category_id] = len(category_ids)
self.data_labels = dict(sorted(settings_items, key=lambda x: section_ids[x[1].section])) def sort_key(x):
item: OptionInfo = x[1]
category_order = category_ids.get(item.category_id, len(category_ids))
section_order = item.section[1]
return category_order, section_order
self.data_labels = dict(sorted(settings_items, key=sort_key))
def cast_value(self, key, value): def cast_value(self, key, value):
"""casts an arbitrary to the same type as this setting's value with key """casts an arbitrary to the same type as this setting's value with key
...@@ -245,3 +287,22 @@ class Options: ...@@ -245,3 +287,22 @@ class Options:
value = expected_type(value) value = expected_type(value)
return value return value
@dataclass
class OptionsCategory:
id: str
label: str
class OptionsCategories:
def __init__(self):
self.mapping = {}
def register_category(self, category_id, label):
if category_id in self.mapping:
return category_id
self.mapping[category_id] = OptionsCategory(category_id, label)
categories = OptionsCategories()
...@@ -679,8 +679,8 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter ...@@ -679,8 +679,8 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter
"Size": f"{p.width}x{p.height}", "Size": f"{p.width}x{p.height}",
"Model hash": p.sd_model_hash if opts.add_model_hash_to_info else None, "Model hash": p.sd_model_hash if opts.add_model_hash_to_info else None,
"Model": p.sd_model_name if opts.add_model_name_to_info else None, "Model": p.sd_model_name if opts.add_model_name_to_info else None,
"VAE hash": p.sd_vae_hash if opts.add_model_hash_to_info else None, "VAE hash": p.sd_vae_hash if opts.add_vae_hash_to_info else None,
"VAE": p.sd_vae_name if opts.add_model_name_to_info else None, "VAE": p.sd_vae_name if opts.add_vae_name_to_info else None,
"Variation seed": (None if p.subseed_strength == 0 else (p.all_subseeds[0] if use_main_prompt else all_subseeds[index])), "Variation seed": (None if p.subseed_strength == 0 else (p.all_subseeds[0] if use_main_prompt else all_subseeds[index])),
"Variation seed strength": (None if p.subseed_strength == 0 else p.subseed_strength), "Variation seed strength": (None if p.subseed_strength == 0 else p.subseed_strength),
"Seed resize from": (None if p.seed_resize_from_w <= 0 or p.seed_resize_from_h <= 0 else f"{p.seed_resize_from_w}x{p.seed_resize_from_h}"), "Seed resize from": (None if p.seed_resize_from_w <= 0 or p.seed_resize_from_h <= 0 else f"{p.seed_resize_from_w}x{p.seed_resize_from_h}"),
......
...@@ -560,17 +560,25 @@ class ScriptRunner: ...@@ -560,17 +560,25 @@ class ScriptRunner:
on_after.clear() on_after.clear()
def create_script_ui(self, script): def create_script_ui(self, script):
import modules.api.models as api_models
script.args_from = len(self.inputs) script.args_from = len(self.inputs)
script.args_to = len(self.inputs) script.args_to = len(self.inputs)
try:
self.create_script_ui_inner(script)
except Exception:
errors.report(f"Error creating UI for {script.name}: ", exc_info=True)
def create_script_ui_inner(self, script):
import modules.api.models as api_models
controls = wrap_call(script.ui, script.filename, "ui", script.is_img2img) controls = wrap_call(script.ui, script.filename, "ui", script.is_img2img)
if controls is None: if controls is None:
return return
script.name = wrap_call(script.title, script.filename, "title", default=script.filename).lower() script.name = wrap_call(script.title, script.filename, "title", default=script.filename).lower()
api_args = [] api_args = []
for control in controls: for control in controls:
......
...@@ -38,9 +38,6 @@ ldm.models.diffusion.ddpm.print = shared.ldm_print ...@@ -38,9 +38,6 @@ ldm.models.diffusion.ddpm.print = shared.ldm_print
optimizers = [] optimizers = []
current_optimizer: sd_hijack_optimizations.SdOptimization = None current_optimizer: sd_hijack_optimizations.SdOptimization = None
ldm_original_forward = patches.patch(__file__, ldm.modules.diffusionmodules.openaimodel.UNetModel, "forward", sd_unet.UNetModel_forward)
sgm_original_forward = patches.patch(__file__, sgm.modules.diffusionmodules.openaimodel.UNetModel, "forward", sd_unet.UNetModel_forward)
def list_optimizers(): def list_optimizers():
new_optimizers = script_callbacks.list_optimizers_callback() new_optimizers = script_callbacks.list_optimizers_callback()
...@@ -258,6 +255,9 @@ class StableDiffusionModelHijack: ...@@ -258,6 +255,9 @@ class StableDiffusionModelHijack:
import modules.models.diffusion.ddpm_edit import modules.models.diffusion.ddpm_edit
ldm_original_forward = patches.patch(__file__, ldm.modules.diffusionmodules.openaimodel.UNetModel, "forward", sd_unet.UNetModel_forward)
sgm_original_forward = patches.patch(__file__, sgm.modules.diffusionmodules.openaimodel.UNetModel, "forward", sd_unet.UNetModel_forward)
if isinstance(m, ldm.models.diffusion.ddpm.LatentDiffusion): if isinstance(m, ldm.models.diffusion.ddpm.LatentDiffusion):
sd_unet.original_forward = ldm_original_forward sd_unet.original_forward = ldm_original_forward
elif isinstance(m, modules.models.diffusion.ddpm_edit.LatentDiffusion): elif isinstance(m, modules.models.diffusion.ddpm_edit.LatentDiffusion):
...@@ -303,6 +303,9 @@ class StableDiffusionModelHijack: ...@@ -303,6 +303,9 @@ class StableDiffusionModelHijack:
self.layers = None self.layers = None
self.clip = None self.clip = None
patches.undo(__file__, ldm.modules.diffusionmodules.openaimodel.UNetModel, "forward")
patches.undo(__file__, sgm.modules.diffusionmodules.openaimodel.UNetModel, "forward")
sd_unet.original_forward = None sd_unet.original_forward = None
......
...@@ -230,15 +230,19 @@ def select_checkpoint(): ...@@ -230,15 +230,19 @@ def select_checkpoint():
return checkpoint_info return checkpoint_info
checkpoint_dict_replacements = { checkpoint_dict_replacements_sd1 = {
'cond_stage_model.transformer.embeddings.': 'cond_stage_model.transformer.text_model.embeddings.', 'cond_stage_model.transformer.embeddings.': 'cond_stage_model.transformer.text_model.embeddings.',
'cond_stage_model.transformer.encoder.': 'cond_stage_model.transformer.text_model.encoder.', 'cond_stage_model.transformer.encoder.': 'cond_stage_model.transformer.text_model.encoder.',
'cond_stage_model.transformer.final_layer_norm.': 'cond_stage_model.transformer.text_model.final_layer_norm.', 'cond_stage_model.transformer.final_layer_norm.': 'cond_stage_model.transformer.text_model.final_layer_norm.',
} }
checkpoint_dict_replacements_sd2_turbo = { # Converts SD 2.1 Turbo from SGM to LDM format.
'conditioner.embedders.0.': 'cond_stage_model.',
}
def transform_checkpoint_dict_key(k): def transform_checkpoint_dict_key(k, replacements):
for text, replacement in checkpoint_dict_replacements.items(): for text, replacement in replacements.items():
if k.startswith(text): if k.startswith(text):
k = replacement + k[len(text):] k = replacement + k[len(text):]
...@@ -249,9 +253,14 @@ def get_state_dict_from_checkpoint(pl_sd): ...@@ -249,9 +253,14 @@ def get_state_dict_from_checkpoint(pl_sd):
pl_sd = pl_sd.pop("state_dict", pl_sd) pl_sd = pl_sd.pop("state_dict", pl_sd)
pl_sd.pop("state_dict", None) pl_sd.pop("state_dict", None)
is_sd2_turbo = 'conditioner.embedders.0.model.ln_final.weight' in pl_sd and pl_sd['conditioner.embedders.0.model.ln_final.weight'].size()[0] == 1024
sd = {} sd = {}
for k, v in pl_sd.items(): for k, v in pl_sd.items():
new_key = transform_checkpoint_dict_key(k) if is_sd2_turbo:
new_key = transform_checkpoint_dict_key(k, checkpoint_dict_replacements_sd2_turbo)
else:
new_key = transform_checkpoint_dict_key(k, checkpoint_dict_replacements_sd1)
if new_key is not None: if new_key is not None:
sd[new_key] = v sd[new_key] = v
......
...@@ -66,6 +66,22 @@ def reload_hypernetworks(): ...@@ -66,6 +66,22 @@ def reload_hypernetworks():
shared.hypernetworks = hypernetwork.list_hypernetworks(cmd_opts.hypernetwork_dir) shared.hypernetworks = hypernetwork.list_hypernetworks(cmd_opts.hypernetwork_dir)
def get_infotext_names():
from modules import generation_parameters_copypaste, shared
res = {}
for info in shared.opts.data_labels.values():
if info.infotext:
res[info.infotext] = 1
for tab_data in generation_parameters_copypaste.paste_fields.values():
for _, name in tab_data.get("fields") or []:
if isinstance(name, str):
res[name] = 1
return list(res)
ui_reorder_categories_builtin_items = [ ui_reorder_categories_builtin_items = [
"prompt", "prompt",
"image", "image",
......
This diff is collapsed.
This diff is collapsed.
...@@ -3,6 +3,8 @@ import requests ...@@ -3,6 +3,8 @@ import requests
import os import os
import numpy as np import numpy as np
from PIL import ImageDraw from PIL import ImageDraw
from modules import paths_internal
from pkg_resources import parse_version
GREEN = "#0F0" GREEN = "#0F0"
BLUE = "#00F" BLUE = "#00F"
...@@ -25,7 +27,6 @@ def crop_image(im, settings): ...@@ -25,7 +27,6 @@ def crop_image(im, settings):
elif is_portrait(settings.crop_width, settings.crop_height): elif is_portrait(settings.crop_width, settings.crop_height):
scale_by = settings.crop_height / im.height scale_by = settings.crop_height / im.height
im = im.resize((int(im.width * scale_by), int(im.height * scale_by))) im = im.resize((int(im.width * scale_by), int(im.height * scale_by)))
im_debug = im.copy() im_debug = im.copy()
...@@ -69,6 +70,7 @@ def crop_image(im, settings): ...@@ -69,6 +70,7 @@ def crop_image(im, settings):
return results return results
def focal_point(im, settings): def focal_point(im, settings):
corner_points = image_corner_points(im, settings) if settings.corner_points_weight > 0 else [] corner_points = image_corner_points(im, settings) if settings.corner_points_weight > 0 else []
entropy_points = image_entropy_points(im, settings) if settings.entropy_points_weight > 0 else [] entropy_points = image_entropy_points(im, settings) if settings.entropy_points_weight > 0 else []
...@@ -110,7 +112,7 @@ def focal_point(im, settings): ...@@ -110,7 +112,7 @@ def focal_point(im, settings):
if corner_centroid is not None: if corner_centroid is not None:
color = BLUE color = BLUE
box = corner_centroid.bounding(max_size * corner_centroid.weight) box = corner_centroid.bounding(max_size * corner_centroid.weight)
d.text((box[0], box[1]-15), f"Edge: {corner_centroid.weight:.02f}", fill=color) d.text((box[0], box[1] - 15), f"Edge: {corner_centroid.weight:.02f}", fill=color)
d.ellipse(box, outline=color) d.ellipse(box, outline=color)
if len(corner_points) > 1: if len(corner_points) > 1:
for f in corner_points: for f in corner_points:
...@@ -118,7 +120,7 @@ def focal_point(im, settings): ...@@ -118,7 +120,7 @@ def focal_point(im, settings):
if entropy_centroid is not None: if entropy_centroid is not None:
color = "#ff0" color = "#ff0"
box = entropy_centroid.bounding(max_size * entropy_centroid.weight) box = entropy_centroid.bounding(max_size * entropy_centroid.weight)
d.text((box[0], box[1]-15), f"Entropy: {entropy_centroid.weight:.02f}", fill=color) d.text((box[0], box[1] - 15), f"Entropy: {entropy_centroid.weight:.02f}", fill=color)
d.ellipse(box, outline=color) d.ellipse(box, outline=color)
if len(entropy_points) > 1: if len(entropy_points) > 1:
for f in entropy_points: for f in entropy_points:
...@@ -126,7 +128,7 @@ def focal_point(im, settings): ...@@ -126,7 +128,7 @@ def focal_point(im, settings):
if face_centroid is not None: if face_centroid is not None:
color = RED color = RED
box = face_centroid.bounding(max_size * face_centroid.weight) box = face_centroid.bounding(max_size * face_centroid.weight)
d.text((box[0], box[1]-15), f"Face: {face_centroid.weight:.02f}", fill=color) d.text((box[0], box[1] - 15), f"Face: {face_centroid.weight:.02f}", fill=color)
d.ellipse(box, outline=color) d.ellipse(box, outline=color)
if len(face_points) > 1: if len(face_points) > 1:
for f in face_points: for f in face_points:
...@@ -159,8 +161,8 @@ def image_face_points(im, settings): ...@@ -159,8 +161,8 @@ def image_face_points(im, settings):
PointOfInterest( PointOfInterest(
int(x + (w * 0.5)), # face focus left/right is center int(x + (w * 0.5)), # face focus left/right is center
int(y + (h * 0.33)), # face focus up/down is close to the top of the head int(y + (h * 0.33)), # face focus up/down is close to the top of the head
size = w, size=w,
weight = 1/len(faces[1]) weight=1 / len(faces[1])
) )
) )
return results return results
...@@ -169,27 +171,29 @@ def image_face_points(im, settings): ...@@ -169,27 +171,29 @@ def image_face_points(im, settings):
gray = cv2.cvtColor(np_im, cv2.COLOR_BGR2GRAY) gray = cv2.cvtColor(np_im, cv2.COLOR_BGR2GRAY)
tries = [ tries = [
[ f'{cv2.data.haarcascades}haarcascade_eye.xml', 0.01 ], [f'{cv2.data.haarcascades}haarcascade_eye.xml', 0.01],
[ f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml', 0.05 ], [f'{cv2.data.haarcascades}haarcascade_frontalface_default.xml', 0.05],
[ f'{cv2.data.haarcascades}haarcascade_profileface.xml', 0.05 ], [f'{cv2.data.haarcascades}haarcascade_profileface.xml', 0.05],
[ f'{cv2.data.haarcascades}haarcascade_frontalface_alt.xml', 0.05 ], [f'{cv2.data.haarcascades}haarcascade_frontalface_alt.xml', 0.05],
[ f'{cv2.data.haarcascades}haarcascade_frontalface_alt2.xml', 0.05 ], [f'{cv2.data.haarcascades}haarcascade_frontalface_alt2.xml', 0.05],
[ f'{cv2.data.haarcascades}haarcascade_frontalface_alt_tree.xml', 0.05 ], [f'{cv2.data.haarcascades}haarcascade_frontalface_alt_tree.xml', 0.05],
[ f'{cv2.data.haarcascades}haarcascade_eye_tree_eyeglasses.xml', 0.05 ], [f'{cv2.data.haarcascades}haarcascade_eye_tree_eyeglasses.xml', 0.05],
[ f'{cv2.data.haarcascades}haarcascade_upperbody.xml', 0.05 ] [f'{cv2.data.haarcascades}haarcascade_upperbody.xml', 0.05]
] ]
for t in tries: for t in tries:
classifier = cv2.CascadeClassifier(t[0]) classifier = cv2.CascadeClassifier(t[0])
minsize = int(min(im.width, im.height) * t[1]) # at least N percent of the smallest side minsize = int(min(im.width, im.height) * t[1]) # at least N percent of the smallest side
try: try:
faces = classifier.detectMultiScale(gray, scaleFactor=1.1, faces = classifier.detectMultiScale(gray, scaleFactor=1.1,
minNeighbors=7, minSize=(minsize, minsize), flags=cv2.CASCADE_SCALE_IMAGE) minNeighbors=7, minSize=(minsize, minsize),
flags=cv2.CASCADE_SCALE_IMAGE)
except Exception: except Exception:
continue continue
if faces: if faces:
rects = [[f[0], f[1], f[0] + f[2], f[1] + f[3]] for f in faces] rects = [[f[0], f[1], f[0] + f[2], f[1] + f[3]] for f in faces]
return [PointOfInterest((r[0] +r[2]) // 2, (r[1] + r[3]) // 2, size=abs(r[0]-r[2]), weight=1/len(rects)) for r in rects] return [PointOfInterest((r[0] + r[2]) // 2, (r[1] + r[3]) // 2, size=abs(r[0] - r[2]),
weight=1 / len(rects)) for r in rects]
return [] return []
...@@ -198,7 +202,7 @@ def image_corner_points(im, settings): ...@@ -198,7 +202,7 @@ def image_corner_points(im, settings):
# naive attempt at preventing focal points from collecting at watermarks near the bottom # naive attempt at preventing focal points from collecting at watermarks near the bottom
gd = ImageDraw.Draw(grayscale) gd = ImageDraw.Draw(grayscale)
gd.rectangle([0, im.height*.9, im.width, im.height], fill="#999") gd.rectangle([0, im.height * .9, im.width, im.height], fill="#999")
np_im = np.array(grayscale) np_im = np.array(grayscale)
...@@ -206,7 +210,7 @@ def image_corner_points(im, settings): ...@@ -206,7 +210,7 @@ def image_corner_points(im, settings):
np_im, np_im,
maxCorners=100, maxCorners=100,
qualityLevel=0.04, qualityLevel=0.04,
minDistance=min(grayscale.width, grayscale.height)*0.06, minDistance=min(grayscale.width, grayscale.height) * 0.06,
useHarrisDetector=False, useHarrisDetector=False,
) )
...@@ -216,7 +220,7 @@ def image_corner_points(im, settings): ...@@ -216,7 +220,7 @@ def image_corner_points(im, settings):
focal_points = [] focal_points = []
for point in points: for point in points:
x, y = point.ravel() x, y = point.ravel()
focal_points.append(PointOfInterest(x, y, size=4, weight=1/len(points))) focal_points.append(PointOfInterest(x, y, size=4, weight=1 / len(points)))
return focal_points return focal_points
...@@ -247,8 +251,8 @@ def image_entropy_points(im, settings): ...@@ -247,8 +251,8 @@ def image_entropy_points(im, settings):
crop_current[move_idx[0]] += 4 crop_current[move_idx[0]] += 4
crop_current[move_idx[1]] += 4 crop_current[move_idx[1]] += 4
x_mid = int(crop_best[0] + settings.crop_width/2) x_mid = int(crop_best[0] + settings.crop_width / 2)
y_mid = int(crop_best[1] + settings.crop_height/2) y_mid = int(crop_best[1] + settings.crop_height / 2)
return [PointOfInterest(x_mid, y_mid, size=25, weight=1.0)] return [PointOfInterest(x_mid, y_mid, size=25, weight=1.0)]
...@@ -294,22 +298,23 @@ def is_square(w, h): ...@@ -294,22 +298,23 @@ def is_square(w, h):
return w == h return w == h
def download_and_cache_models(dirname): model_dir_opencv = os.path.join(paths_internal.models_path, 'opencv')
download_url = 'https://github.com/opencv/opencv_zoo/blob/91fb0290f50896f38a0ab1e558b74b16bc009428/models/face_detection_yunet/face_detection_yunet_2022mar.onnx?raw=true' if parse_version(cv2.__version__) >= parse_version('4.8'):
model_file_name = 'face_detection_yunet.onnx' model_file_path = os.path.join(model_dir_opencv, 'face_detection_yunet_2023mar.onnx')
model_url = 'https://github.com/opencv/opencv_zoo/blob/b6e370b10f641879a87890d44e42173077154a05/models/face_detection_yunet/face_detection_yunet_2023mar.onnx?raw=true'
else:
model_file_path = os.path.join(model_dir_opencv, 'face_detection_yunet.onnx')
model_url = 'https://github.com/opencv/opencv_zoo/blob/91fb0290f50896f38a0ab1e558b74b16bc009428/models/face_detection_yunet/face_detection_yunet_2022mar.onnx?raw=true'
os.makedirs(dirname, exist_ok=True)
cache_file = os.path.join(dirname, model_file_name) def download_and_cache_models():
if not os.path.exists(cache_file): if not os.path.exists(model_file_path):
print(f"downloading face detection model from '{download_url}' to '{cache_file}'") os.makedirs(model_dir_opencv, exist_ok=True)
response = requests.get(download_url) print(f"downloading face detection model from '{model_url}' to '{model_file_path}'")
with open(cache_file, "wb") as f: response = requests.get(model_url)
with open(model_file_path, "wb") as f:
f.write(response.content) f.write(response.content)
return model_file_path
if os.path.exists(cache_file):
return cache_file
return None
class PointOfInterest: class PointOfInterest:
......
...@@ -3,7 +3,7 @@ from PIL import Image, ImageOps ...@@ -3,7 +3,7 @@ from PIL import Image, ImageOps
import math import math
import tqdm import tqdm
from modules import paths, shared, images, deepbooru from modules import shared, images, deepbooru
from modules.textual_inversion import autocrop from modules.textual_inversion import autocrop
...@@ -196,7 +196,7 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pre ...@@ -196,7 +196,7 @@ def preprocess_work(process_src, process_dst, process_width, process_height, pre
dnn_model_path = None dnn_model_path = None
try: try:
dnn_model_path = autocrop.download_and_cache_models(os.path.join(paths.models_path, "opencv")) dnn_model_path = autocrop.download_and_cache_models()
except Exception as e: except Exception as e:
print("Unable to load face detection model for auto crop selection. Falling back to lower quality haar method.", e) print("Unable to load face detection model for auto crop selection. Falling back to lower quality haar method.", e)
......
...@@ -65,7 +65,7 @@ def save_config_state(name): ...@@ -65,7 +65,7 @@ def save_config_state(name):
filename = os.path.join(config_states_dir, f"{timestamp}_{name}.json") filename = os.path.join(config_states_dir, f"{timestamp}_{name}.json")
print(f"Saving backup of webui/extension state to {filename}.") print(f"Saving backup of webui/extension state to {filename}.")
with open(filename, "w", encoding="utf-8") as f: with open(filename, "w", encoding="utf-8") as f:
json.dump(current_config_state, f, indent=4) json.dump(current_config_state, f, indent=4, ensure_ascii=False)
config_states.list_config_states() config_states.list_config_states()
new_value = next(iter(config_states.all_config_states.keys()), "Current") new_value = next(iter(config_states.all_config_states.keys()), "Current")
new_choices = ["Current"] + list(config_states.all_config_states.keys()) new_choices = ["Current"] + list(config_states.all_config_states.keys())
...@@ -335,6 +335,11 @@ def normalize_git_url(url): ...@@ -335,6 +335,11 @@ def normalize_git_url(url):
return url return url
def get_extension_dirname_from_url(url):
*parts, last_part = url.split('/')
return normalize_git_url(last_part)
def install_extension_from_url(dirname, url, branch_name=None): def install_extension_from_url(dirname, url, branch_name=None):
check_access() check_access()
...@@ -346,10 +351,7 @@ def install_extension_from_url(dirname, url, branch_name=None): ...@@ -346,10 +351,7 @@ def install_extension_from_url(dirname, url, branch_name=None):
assert url, 'No URL specified' assert url, 'No URL specified'
if dirname is None or dirname == "": if dirname is None or dirname == "":
*parts, last_part = url.split('/') dirname = get_extension_dirname_from_url(url)
last_part = normalize_git_url(last_part)
dirname = last_part
target_dir = os.path.join(extensions.extensions_dir, dirname) target_dir = os.path.join(extensions.extensions_dir, dirname)
assert not os.path.exists(target_dir), f'Extension directory already exists: {target_dir}' assert not os.path.exists(target_dir), f'Extension directory already exists: {target_dir}'
...@@ -449,7 +451,8 @@ def get_date(info: dict, key): ...@@ -449,7 +451,8 @@ def get_date(info: dict, key):
def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=""): def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=""):
extlist = available_extensions["extensions"] extlist = available_extensions["extensions"]
installed_extension_urls = {normalize_git_url(extension.remote): extension.name for extension in extensions.extensions} installed_extensions = {extension.name for extension in extensions.extensions}
installed_extension_urls = {normalize_git_url(extension.remote) for extension in extensions.extensions if extension.remote is not None}
tags = available_extensions.get("tags", {}) tags = available_extensions.get("tags", {})
tags_to_hide = set(hide_tags) tags_to_hide = set(hide_tags)
...@@ -482,7 +485,7 @@ def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=" ...@@ -482,7 +485,7 @@ def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text="
if url is None: if url is None:
continue continue
existing = installed_extension_urls.get(normalize_git_url(url), None) existing = get_extension_dirname_from_url(url) in installed_extensions or normalize_git_url(url) in installed_extension_urls
extension_tags = extension_tags + ["installed"] if existing else extension_tags extension_tags = extension_tags + ["installed"] if existing else extension_tags
if any(x for x in extension_tags if x in tags_to_hide): if any(x for x in extension_tags if x in tags_to_hide):
......
...@@ -151,6 +151,11 @@ class ExtraNetworksPage: ...@@ -151,6 +151,11 @@ class ExtraNetworksPage:
continue continue
subdir = os.path.abspath(x)[len(parentdir):].replace("\\", "/") subdir = os.path.abspath(x)[len(parentdir):].replace("\\", "/")
if shared.opts.extra_networks_dir_button_function:
if not subdir.startswith("/"):
subdir = "/" + subdir
else:
while subdir.startswith("/"): while subdir.startswith("/"):
subdir = subdir[1:] subdir = subdir[1:]
...@@ -370,6 +375,9 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): ...@@ -370,6 +375,9 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname):
for page in ui.stored_extra_pages: for page in ui.stored_extra_pages:
with gr.Tab(page.title, elem_id=f"{tabname}_{page.id_page}", elem_classes=["extra-page"]) as tab: with gr.Tab(page.title, elem_id=f"{tabname}_{page.id_page}", elem_classes=["extra-page"]) as tab:
with gr.Column(elem_id=f"{tabname}_{page.id_page}_prompts", elem_classes=["extra-page-prompts"]):
pass
elem_id = f"{tabname}_{page.id_page}_cards_html" elem_id = f"{tabname}_{page.id_page}_cards_html"
page_elem = gr.HTML('Loading...', elem_id=elem_id) page_elem = gr.HTML('Loading...', elem_id=elem_id)
ui.pages.append(page_elem) ui.pages.append(page_elem)
...@@ -400,7 +408,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): ...@@ -400,7 +408,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname):
allow_prompt = "true" if page.allow_prompt else "false" allow_prompt = "true" if page.allow_prompt else "false"
allow_negative_prompt = "true" if page.allow_negative_prompt else "false" allow_negative_prompt = "true" if page.allow_negative_prompt else "false"
jscode = 'extraNetworksTabSelected("' + tabname + '", "' + f"{tabname}_{page.id_page}" + '", ' + allow_prompt + ', ' + allow_negative_prompt + ');' jscode = 'extraNetworksTabSelected("' + tabname + '", "' + f"{tabname}_{page.id_page}_prompts" + '", ' + allow_prompt + ', ' + allow_negative_prompt + ');'
tab.select(fn=lambda: [gr.update(visible=True) for _ in tab_controls], _js='function(){ ' + jscode + ' }', inputs=[], outputs=tab_controls, show_progress=False) tab.select(fn=lambda: [gr.update(visible=True) for _ in tab_controls], _js='function(){ ' + jscode + ' }', inputs=[], outputs=tab_controls, show_progress=False)
......
...@@ -134,7 +134,7 @@ class UserMetadataEditor: ...@@ -134,7 +134,7 @@ class UserMetadataEditor:
basename, ext = os.path.splitext(filename) basename, ext = os.path.splitext(filename)
with open(basename + '.json', "w", encoding="utf8") as file: with open(basename + '.json', "w", encoding="utf8") as file:
json.dump(metadata, file, indent=4) json.dump(metadata, file, indent=4, ensure_ascii=False)
def save_user_metadata(self, name, desc, notes): def save_user_metadata(self, name, desc, notes):
user_metadata = self.get_user_metadata(name) user_metadata = self.get_user_metadata(name)
......
...@@ -141,7 +141,7 @@ class UiLoadsave: ...@@ -141,7 +141,7 @@ class UiLoadsave:
def write_to_file(self, current_ui_settings): def write_to_file(self, current_ui_settings):
with open(self.filename, "w", encoding="utf8") as file: with open(self.filename, "w", encoding="utf8") as file:
json.dump(current_ui_settings, file, indent=4) json.dump(current_ui_settings, file, indent=4, ensure_ascii=False)
def dump_defaults(self): def dump_defaults(self):
"""saves default values to a file unless tjhe file is present and there was an error loading default values at start""" """saves default values to a file unless tjhe file is present and there was an error loading default values at start"""
......
...@@ -462,6 +462,15 @@ div.toprow-compact-tools{ ...@@ -462,6 +462,15 @@ div.toprow-compact-tools{
padding: 4px; padding: 4px;
} }
#settings > div.tab-nav .settings-category{
display: block;
margin: 1em 0 0.25em 0;
font-weight: bold;
text-decoration: underline;
cursor: default;
user-select: none;
}
#settings_result{ #settings_result{
height: 1.4em; height: 1.4em;
margin: 0 1.2em; margin: 0 1.2em;
...@@ -637,6 +646,8 @@ table.popup-table .link{ ...@@ -637,6 +646,8 @@ table.popup-table .link{
margin: auto; margin: auto;
padding: 2em; padding: 2em;
z-index: 1001; z-index: 1001;
max-height: 90%;
max-width: 90%;
} }
/* fullpage image viewer */ /* fullpage image viewer */
...@@ -840,8 +851,16 @@ footer { ...@@ -840,8 +851,16 @@ footer {
/* extra networks UI */ /* extra networks UI */
.extra-page .prompt{ .extra-page > div.gap{
margin: 0 0 0.5em 0; gap: 0;
}
.extra-page-prompts{
margin-bottom: 0;
}
.extra-page-prompts.extra-page-prompts-active{
margin-bottom: 1em;
} }
.extra-network-cards{ .extra-network-cards{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment