diff --git a/CHANGELOG.md b/CHANGELOG.md
index 40840a2a2..9b836a13e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,8 @@
# Change Log for SD.Next
-## Update for 2025-03-28
+## Update for 2025-03-30
-### Highlights for 2025-03-28
+### Highlights for 2025-03-30
Brand new Video processing module with support for all latest models: **WAN21, Hunyuan, LTX, Cog, Allegro, Mochi1, Latte1** in both T2V and I2V workflows
And combined with *on-the-fly quantization*, support for *Local/Tiny/Remote* VAE, acceleration modules such as *FasterCache or PAB* and more!
@@ -15,7 +15,7 @@ Plus...
- More quantization options and granular control
- Pretty big performance updates to a) Any model using DiT based architecture: new caching methods, b) ZLUDA: new attention methods
-### Details for 2025-03-28
+### Details for 2025-03-30
- **Video tab**
- see [Video Wiki](https://github.com/vladmandic/sdnext/wiki/Video) for details!
@@ -129,6 +129,10 @@ Plus...
- **LoRA** enable memory cache by default
- **Samplers** add ability to set sigma adjustment for each sampler
- **ModernUI** updates
+ - **CSS** updates
+ - settings vertiocal/dirty indicator restores to default setting instead to previous value
+ - video interpolate do not skip duplicate frames
+ - **settings UI** full refactor
- **Wiki/Docs**
- updated [Models](https://github.com/vladmandic/sdnext/wiki/Models) info
- new [Video](https://github.com/vladmandic/sdnext/wiki/Video) guide
diff --git a/extensions-builtin/sd-extension-system-info b/extensions-builtin/sd-extension-system-info
index 8c7edb3be..ce373b9c2 160000
--- a/extensions-builtin/sd-extension-system-info
+++ b/extensions-builtin/sd-extension-system-info
@@ -1 +1 @@
-Subproject commit 8c7edb3be11b8b8c2d2dcd0421e93345bd20fcae
+Subproject commit ce373b9c27544f56ad73a1f7fe2c5530a89c1c32
diff --git a/javascript/black-teal.css b/javascript/black-teal.css
index 851c03953..8a99cb91c 100644
--- a/javascript/black-teal.css
+++ b/javascript/black-teal.css
@@ -54,15 +54,21 @@
--line-md: 1.4em;
--line-lg: 1.5em;
--range-shadow:
- -20em 0 0 0 hsl(180, 54%, 2%), -19em 0 0 0 hsl(180, 54%, 5%), -18em 0 0 0 hsl(180, 54%, 0%), -17em 0 0 0 hsl(180, 54%, 11%),
- -16em 0 0 0 hsl(180, 54%, 14%), -15em 0 0 0 hsl(180, 54%, 17%), -14em 0 0 0 hsl(180, 54%, 20%), -13em 0 0 0 hsl(180, 54%, 23%),
+ -32em 0 0 0 hsl(180, 54%, 6%), -31em 0 0 0 hsl(180, 54%, 7%), -30em 0 0 0 hsl(180, 54%, 8%), -29em 0 0 0 hsl(180, 54%, 9%),
+ -28em 0 0 0 hsl(180, 54%, 10%), -27em 0 0 0 hsl(180, 54%, 11%), -26em 0 0 0 hsl(180, 54%, 12%), -25em 0 0 0 hsl(180, 54%, 13%),
+ -24em 0 0 0 hsl(180, 54%, 14%), -23em 0 0 0 hsl(180, 54%, 15%), -22em 0 0 0 hsl(180, 54%, 16%), -21em 0 0 0 hsl(180, 54%, 17%),
+ -20em 0 0 0 hsl(180, 54%, 18%), -19em 0 0 0 hsl(180, 54%, 19%), -18em 0 0 0 hsl(180, 54%, 20%), -17em 0 0 0 hsl(180, 54%, 21%),
+ -16em 0 0 0 hsl(180, 54%, 22%), -15em 0 0 0 hsl(180, 54%, 23%), -14em 0 0 0 hsl(180, 54%, 24%), -13em 0 0 0 hsl(180, 54%, 25%),
-12em 0 0 0 hsl(180, 54%, 26%), -11em 0 0 0 hsl(180, 54%, 29%), -10em 0 0 0 hsl(180, 54%, 32%), -9em 0 0 0 hsl(180, 54%, 35%),
-8em 0 0 0 hsl(180, 54%, 38%), -7em 0 0 0 hsl(180, 54%, 41%), -6em 0 0 0 hsl(180, 54%, 44%), -5em 0 0 0 hsl(180, 54%, 47%),
-4em 0 0 0 hsl(180, 54%, 50%), -3em 0 0 0 hsl(180, 54%, 53%), -2em 0 0 0 hsl(180, 54%, 56%), -1em 0 0 0 hsl(180, 54%, 59%),
1em 0 0 0 var(--neutral-800), 2em 0 0 0 var(--neutral-800), 3em 0 0 0 var(--neutral-800), 4em 0 0 0 var(--neutral-800),
5em 0 0 0 var(--neutral-800), 6em 0 0 0 var(--neutral-800), 7em 0 0 0 var(--neutral-800), 8em 0 0 0 var(--neutral-800),
9em 0 0 0 var(--neutral-800), 10em 0 0 0 var(--neutral-800), 11em 0 0 0 var(--neutral-800), 12em 0 0 0 var(--neutral-800),
- 13em 0 0 0 var(--neutral-800), 14em 0 0 0 var(--neutral-800), 15em 0 0 0 var(--neutral-800), 16em 0 0 0 var(--neutral-800);
+ 13em 0 0 0 var(--neutral-800), 14em 0 0 0 var(--neutral-800), 15em 0 0 0 var(--neutral-800), 16em 0 0 0 var(--neutral-800),
+ 17em 0 0 0 var(--neutral-800), 18em 0 0 0 var(--neutral-800), 19em 0 0 0 var(--neutral-800), 20em 0 0 0 var(--neutral-800),
+ 21em 0 0 0 var(--neutral-800), 22em 0 0 0 var(--neutral-800), 23em 0 0 0 var(--neutral-800), 24em 0 0 0 var(--neutral-800),
+ 25em 0 0 0 var(--neutral-800), 26em 0 0 0 var(--neutral-800), 27em 0 0 0 var(--neutral-800), 28em 0 0 0 var(--neutral-800);
}
html { font-size: var(--font-size); font-family: var(--font); }
@@ -70,13 +76,6 @@ body, button, input, select, textarea { font-family: var(--font); }
button { max-width: 400px; white-space: nowrap; }
img { background-color: var(--background-color); }
-/*
-input[type=range] { height: var(--line-xs) !important; appearance: none !important; margin-top: 0 !important; min-width: max(4em, 100%) !important; background-color: var(--background-color) !important; width: 100% !important; background: transparent !important; }
-input[type=range]::-webkit-slider-runnable-track { width: 100% !important; height: 6px !important; cursor: pointer !important; background: var(--input-background-fill) !important; border-radius: var(--radius-lg) !important; border: 0px solid var(--neutral-900) !important; }
-input[type=range]::-moz-range-track { width: 100% !important; height: 6px !important; cursor: pointer !important; background: var(--input-background-fill) !important; border-radius: var(--radius-lg) !important; border: 0px solid var(--neutral-900) !important; }
-input[type=range]::-webkit-slider-thumb { border: 0px solid #000000 !important; height: var(--line-xs) !important; width: var(--line-md) !important; border-radius: var(--radius-lg) !important; background: var(--highlight-color) !important; cursor: pointer !important; appearance: none !important; margin-top: -4px !important; }
-input[type=range]::-moz-range-thumb { border: 0px solid #000000 !important; height: var(--line-xs) !important; width: var(--line-md) !important; border-radius: var(--radius-lg) !important; background: var(--highlight-color) !important; cursor: pointer !important; appearance: none !important; margin-top: -4px !important; }
-*/
input[type='range'] { display: block; margin: 0; padding: 0; height: 1em; background-color: transparent; overflow: hidden; cursor: pointer; box-shadow: 0 0 0 0 transparent; -webkit-appearance: none; appearance: none; }
input[type='range']::-webkit-slider-thumb { height: .9em; width: .9em; background-color: hsl(180, 54%, 61%); box-shadow: var(--range-shadow); border-radius: var(--radius-xs); }
input[type='range']::-webkit-slider-runnable-track, input[type='range']::-webkit-slider-thumb { -webkit-appearance: none; }
diff --git a/javascript/sdnext.css b/javascript/sdnext.css
index 71f8cb43e..f5e1a949d 100644
--- a/javascript/sdnext.css
+++ b/javascript/sdnext.css
@@ -154,25 +154,24 @@ div#extras_scale_to_tab div.form { flex-direction: row; }
#si-sparkline-memo, #si-sparkline-load { background-color: #111; }
#quicksettings { width: fit-content; }
#quicksettings>button { padding: 0 1em 0 0; align-self: end; margin-bottom: 6px; }
-#settings { display: flex; gap: var(--layout-gap); }
-#settings div { border: none; gap: 0; margin: 0 0 var(--layout-gap) 0px; padding: 0; }
-#settings>div.tab-content { flex: 10 0 75%; display: grid; }
-#settings>div.tab-content>div { border: none; padding: 0; }
+
+#settings { display: flex; margin-left: 0.5em; }
+#settings>div.tab-content { margin-top: 1em; }
+#settings>div.tab-content>div>div { gap: 0; }
#settings>div.tab-content>div>div>div>div>div { flex-direction: unset; }
-#settings>div.tab-nav { display: grid; grid-template-columns: repeat(auto-fill, .5em minmax(10em, 1fr)); flex: 1 0 auto; width: 12em; align-self: flex-start; gap: 8px; }
-#settings>div.tab-nav button { display: block; border: none; text-align: left; white-space: initial; padding: 0; }
-#settings>div.tab-nav>#settings_show_all_pages { padding: var(--size-2) var(--size-4); }
+#settings>div.tab-nav { width: 14em; display: block; background: var(--neutral-900); border-radius: var(--block-radius); margin-right: 1em;}
+#settings>div.tab-nav button { width: 100%; height: 2em; text-align: left; border: none; border-radius: var(--block-radius); }
+#settings .dirtyable.hidden { visibility: hidden; }
+#settings .modification-indicator { background: none; border-radius: var(--radius-lg); padding: 0; width: 4px !important; height: 2em !important; position: absolute; float: left; left: -6px; }
+#settings .modification-indicator:disabled { background: none; }
+#settings .modification-indicator.saved { background: var(--color-accent-soft); }
+#settings .modification-indicator.changed { background: var(--color-accent); }
+#settings .modification-indicator.changed.unsaved { background: var(--color-warning); }
#settings .block.gradio-checkbox { margin: 0; width: auto; }
-#settings .dirtyable { gap: .5em; }
-#settings .dirtyable.hidden { display: none; }
-#settings .modification-indicator { height: 1.2em; border-radius: 1em !important; padding: 0; width: 0; margin-right: 0.5em; border-left: inset; }
-#settings .modification-indicator:disabled { visibility: hidden; }
-#settings .modification-indicator.saved { background: var(--color-accent-soft); width: var(--spacing-sm); }
-#settings .modification-indicator.changed { background: var(--color-accent); width: var(--spacing-sm); }
-#settings .modification-indicator.changed.unsaved { background-image: linear-gradient(var(--color-accent) 25%, var(--color-accent-soft) 75%); width: var(--spacing-sm); }
-#settings_result { margin: 0 1.2em; }
-#tab_settings .gradio-slider, #tab_settings .gradio-dropdown { width: 300px !important; max-width: 300px; }
-#tab_settings textarea { max-width: 500px; }
+#settings .block.gradio-number { min-width: 500px; }
+#settings .gradio-slider, #tab_settings .gradio-dropdown { width: 500px !important; max-width: 500px; }
+#settings textarea { width: 500px !important; max-width: 500px; }
+
.licenses { display: block !important; }
/* live preview */
diff --git a/javascript/settings.js b/javascript/settings.js
index 1a891c844..7a43969ac 100644
--- a/javascript/settings.js
+++ b/javascript/settings.js
@@ -49,8 +49,8 @@ async function updateOpts(json_string) {
function showAllSettings() {
// Try to ensure that the show all settings tab is opened by clicking on its tab button
- const tab_dirty_indicator = gradioApp().getElementById('modification_indicator_show_all_pages');
- if (tab_dirty_indicator && tab_dirty_indicator.nextSibling) tab_dirty_indicator.nextSibling.click();
+ // const tab_dirty_indicator = gradioApp().getElementById('modification_indicator_show_all_pages');
+ // if (tab_dirty_indicator && tab_dirty_indicator.nextSibling) tab_dirty_indicator.nextSibling.click();
getSettingsTabs().forEach((elem) => {
if (elem.id === 'settings_tab_licenses' || elem.id === 'settings_show_all_pages') return;
elem.style.display = 'block';
@@ -192,9 +192,12 @@ async function initSettings() {
tabContentWrapper.className = 'tab-content';
tabNavElements.parentElement.insertBefore(tabContentWrapper, tabNavElements.nextSibling);
tabElements.forEach((elem, index) => {
- const tabName = elem.id.replace('settings_', '');
+ const tabName = elem.id.replace('settings_section_tab_', '');
const indicator = gradioApp().getElementById(`modification_indicator_${tabName}`);
- tabNavElements.insertBefore(indicator, tabNavButtons[index]);
+ if (indicator) {
+ tabNavElements.insertBefore(document.createElement('br'), tabNavButtons[index]);
+ tabNavElements.insertBefore(indicator, tabNavButtons[index]);
+ }
tabContentWrapper.appendChild(elem);
observer.observe(elem, { attributes: true, attributeFilter: ['style'] });
});
diff --git a/modules/img2img.py b/modules/img2img.py
index 75971c608..ca71ff0e7 100644
--- a/modules/img2img.py
+++ b/modules/img2img.py
@@ -6,9 +6,10 @@ from PIL import Image, ImageOps, ImageFilter, ImageEnhance, ImageChops, Unidenti
import modules.scripts
from modules import shared, processing, images
from modules.generation_parameters_copypaste import create_override_settings_dict
-from modules.ui import plaintext_to_html
+from modules.ui_common import plaintext_to_html
from modules.memstats import memory_stats
+
debug = shared.log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None
debug('Trace: PROCESS')
diff --git a/modules/sd_modules.py b/modules/sd_modules.py
index 9a619a2f6..781287f9a 100644
--- a/modules/sd_modules.py
+++ b/modules/sd_modules.py
@@ -31,7 +31,7 @@ def get_signature(cls):
def get_module_stats(name, module):
if not isinstance(module, torch.nn.Module):
- return
+ return None
try:
module_size = sum(p.numel() * p.element_size() for p in module.parameters(recurse=True)) / 1024 / 1024 / 1024
param_num = sum(p.numel() for p in module.parameters(recurse=True)) / 1024 / 1024 / 1024
diff --git a/modules/shared.py b/modules/shared.py
index 5d785f1a5..89eba4655 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -46,7 +46,7 @@ tab_names = []
extra_networks = []
options_templates = {}
hypernetworks = {}
-settings_components = None
+settings_components = {}
restricted_opts = {
"samples_filename_pattern",
"directories_filename_pattern",
@@ -635,7 +635,7 @@ options_templates.update(options_section(('system-paths', "System Paths"), {
"unet_dir": OptionInfo(os.path.join(paths.models_path, 'UNET'), "Folder with UNET files", folder=True),
"te_dir": OptionInfo(os.path.join(paths.models_path, 'Text-encoder'), "Folder with Text encoder files", folder=True),
"lora_dir": OptionInfo(os.path.join(paths.models_path, 'Lora'), "Folder with LoRA network(s)", folder=True),
- "styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "File or Folder with user-defined styles", folder=True),
+ "styles_dir": OptionInfo(os.path.join(paths.models_path, 'styles'), "File or Folder with user-defined styles", folder=True),
"wildcards_dir": OptionInfo(os.path.join(paths.models_path, 'wildcards'), "Folder with user-defined wildcards", folder=True),
"embeddings_dir": OptionInfo(os.path.join(paths.models_path, 'embeddings'), "Folder with textual inversion embeddings", folder=True),
"hypernetwork_dir": OptionInfo(os.path.join(paths.models_path, 'hypernetworks'), "Folder with Hypernetwork models", folder=True),
@@ -946,7 +946,7 @@ options_templates.update(options_section(('extra_networks', "Networks"), {
"wildcards_enabled": OptionInfo(True, "Enable file wildcards support"),
}))
-options_templates.update(options_section((None, "Internal options"), {
+options_templates.update(options_section((None, "Hidden options"), {
"diffusers_version": OptionInfo("", "Diffusers version", gr.Textbox, {"visible": False}),
"disabled_extensions": OptionInfo([], "Disable these extensions"),
"sd_checkpoint_hash": OptionInfo("", "SHA256 hash of the current checkpoint"),
diff --git a/modules/txt2img.py b/modules/txt2img.py
index 08184fe43..151110d07 100644
--- a/modules/txt2img.py
+++ b/modules/txt2img.py
@@ -1,7 +1,7 @@
import os
from modules import shared, processing, scripts
from modules.generation_parameters_copypaste import create_override_settings_dict
-from modules.ui import plaintext_to_html
+from modules.ui_common import plaintext_to_html
debug = shared.log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None
diff --git a/modules/ui.py b/modules/ui.py
index 91fcf3cf3..1c4b7ec0f 100644
--- a/modules/ui.py
+++ b/modules/ui.py
@@ -1,28 +1,17 @@
-import os
import mimetypes
import gradio as gr
import gradio.routes
import gradio.utils
-from modules.call_queue import wrap_gradio_call, wrap_gradio_gpu_call # pylint: disable=unused-import
-from modules import timer, gr_hijack, shared, theme, sd_models, script_callbacks, modelloader, ui_common, ui_loadsave, ui_symbols, ui_javascript, ui_sections, generation_parameters_copypaste, call_queue
+from modules import errors, timer, gr_hijack, shared, script_callbacks, ui_common, ui_symbols, ui_javascript, ui_sections, generation_parameters_copypaste, call_queue, scripts
from modules.paths import script_path, data_path # pylint: disable=unused-import
-from modules.dml import directml_override_opts
-from modules.onnx_impl import install_olive
-import modules.scripts
-import modules.errors
-modules.errors.install()
+errors.install()
mimetypes.init()
mimetypes.add_type('application/javascript', '.js')
mimetypes.add_type('image/webp', '.webp')
mimetypes.add_type('image/jxl', '.jxl')
-log = shared.log
-opts = shared.opts
-cmd_opts = shared.cmd_opts
-ui_system_tabs = None
-paste_function = None
-wrap_queued_call = call_queue.wrap_queued_call
+gr_hijack.init()
switch_values_symbol = ui_symbols.switch
detect_image_size_symbol = ui_symbols.detect
paste_symbol = ui_symbols.paste
@@ -32,11 +21,16 @@ folder_symbol = ui_symbols.folder
extra_networks_symbol = ui_symbols.networks
apply_style_symbol = ui_symbols.apply
save_style_symbol = ui_symbols.save
-gr_hijack.init()
+wrap_queued_call = call_queue.wrap_queued_call # compatibility item
+wrap_gradio_call = call_queue.wrap_gradio_call # compatibility item
+wrap_gradio_gpu_call = call_queue.wrap_gradio_gpu_call # compatibility item
+plaintext_to_html = ui_common.plaintext_to_html # compatibility item
+infotext_to_html = ui_common.infotext_to_html # compatibility item
create_sampler_and_steps_selection = ui_sections.create_sampler_and_steps_selection # compatibility item
+ui_system_tabs = None # required for system-info
-if not cmd_opts.share and not cmd_opts.listen:
+if not shared.cmd_opts.share and not shared.cmd_opts.listen:
# fix gradio phoning home
gradio.utils.version_check = lambda: None
gradio.utils.get_local_ip_address = lambda: '127.0.0.1'
@@ -55,14 +49,6 @@ def create_output_panel(tabname, outdir): # pylint: disable=unused-argument # ou
return a, b, c, e
-def plaintext_to_html(text): # may be referenced by extensions
- return ui_common.plaintext_to_html(text)
-
-
-def infotext_to_html(text): # may be referenced by extensions
- return ui_common.infotext_to_html(text)
-
-
def send_gradio_gallery_to_image(x):
if len(x) == 0:
return None
@@ -81,43 +67,6 @@ def setup_progressbar(*args, **kwargs): # pylint: disable=unused-argument
pass
-def apply_setting(key, value):
- if value is None:
- return gr.update()
- if shared.cmd_opts.freeze:
- return gr.update()
- if key == 'sd_backend':
- return gr.update()
- if shared.opts.disable_weights_auto_swap and key in ['sd_model_checkpoint', 'sd_model_refiner', 'sd_model_dict', 'sd_vae', 'sd_unet', 'sd_text_encoder']:
- return gr.update()
- if key == "sd_model_checkpoint":
- ckpt_info = sd_models.get_closet_checkpoint_match(value)
- if ckpt_info is not None:
- value = ckpt_info.title
- else:
- return gr.update()
- comp_args = opts.data_labels[key].component_args
- if comp_args and isinstance(comp_args, dict) and comp_args.get('visible') is False:
- return gr.update()
- valtype = type(opts.data_labels[key].default)
- oldval = opts.data.get(key, None)
- opts.data[key] = valtype(value) if valtype != type(None) else value
- if oldval != value and opts.data_labels[key].onchange is not None:
- opts.data_labels[key].onchange()
- opts.save(shared.config_filename)
- return getattr(opts, key)
-
-
-def get_value_for_setting(key):
- value = getattr(opts, key)
- info = opts.data_labels[key]
- args = info.component_args() if callable(info.component_args) else info.component_args or {}
- args = {k: v for k, v in args.items() if k not in {'precision', 'multiselect', 'visible'}}
- # if not args:
- # return gr.update()
- return gr.update(value=value, **args)
-
-
def ordered_ui_categories():
return ['dimensions', 'sampler', 'seed', 'denoising', 'cfg', 'checkboxes', 'accordions', 'override_settings', 'scripts'] # a1111 compatibility item, not implemented
@@ -127,6 +76,7 @@ def create_ui(startup_timer = None):
timer.startup = timer.Timer()
ui_javascript.reload_javascript()
generation_parameters_copypaste.reset()
+ scripts.scripts_current = None
with gr.Blocks(analytics_enabled=False) as txt2img_interface:
from modules import ui_txt2img
@@ -138,8 +88,6 @@ def create_ui(startup_timer = None):
ui_img2img.create_ui()
timer.startup.record("ui-img2img")
- modules.scripts.scripts_current = None
-
with gr.Blocks(analytics_enabled=False) as control_interface:
if shared.native:
from modules import ui_control
@@ -172,224 +120,13 @@ def create_ui(startup_timer = None):
ui_gallery.create_ui()
timer.startup.record("ui-gallery")
- def create_setting_component(key, is_quicksettings=False):
- def fun():
- return opts.data[key] if key in opts.data else opts.data_labels[key].default
-
- info = opts.data_labels[key]
- t = type(info.default)
- args = (info.component_args() if callable(info.component_args) else info.component_args) or {}
- if info.component is not None:
- comp = info.component
- elif t == str:
- comp = gr.Textbox
- elif t == int:
- comp = gr.Number
- elif t == bool:
- comp = gr.Checkbox
- else:
- raise ValueError(f'bad options item type: {t} for key {key}')
- elem_id = f"setting_{key}"
- dirty_indicator = None
-
- if not is_quicksettings:
- dirtyable_setting = gr.Group(elem_classes="dirtyable", visible=args.get("visible", True))
- dirtyable_setting.__enter__()
- dirty_indicator = gr.Button("", elem_classes="modification-indicator", elem_id="modification_indicator_" + key)
-
- if info.refresh is not None:
- if is_quicksettings:
- res = comp(label=info.label, value=fun(), elem_id=elem_id, **args)
- ui_common.create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}")
- else:
- with gr.Row():
- res = comp(label=info.label, value=fun(), elem_id=elem_id, **args)
- ui_common.create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}")
- elif info.folder is not None:
- with gr.Row():
- res = comp(label=info.label, value=fun(), elem_id=elem_id, elem_classes="folder-selector", **args)
- # ui_common.create_browse_button(res, f"folder_{key}")
- else:
- try:
- res = comp(label=info.label, value=fun(), elem_id=elem_id, **args)
- except Exception as e:
- log.error(f'Error creating setting: {key} {e}')
- res = None
-
- if res is not None and not is_quicksettings:
- res.change(fn=None, inputs=res, _js=f'(val) => markIfModified("{key}", val)')
- if dirty_indicator is not None:
- dirty_indicator.click(fn=lambda: getattr(opts, key), outputs=res, show_progress=False)
- dirtyable_setting.__exit__()
-
- return res
-
- def create_dirty_indicator(key, keys_to_reset, **kwargs):
- def get_opt_values():
- return [getattr(opts, _key) for _key in keys_to_reset]
-
- elements_to_reset = [component_dict[_key] for _key in keys_to_reset if component_dict[_key] is not None]
- indicator = gr.Button("", elem_classes="modification-indicator", elem_id=f"modification_indicator_{key}", **kwargs)
- indicator.click(fn=get_opt_values, outputs=elements_to_reset, show_progress=False)
- return indicator
-
- loadsave = ui_loadsave.UiLoadsave(cmd_opts.ui_config)
- components = []
- component_dict = {}
- shared.settings_components = component_dict
-
- script_callbacks.ui_settings_callback()
- opts.reorder()
-
- def run_settings(*args):
- changed = []
- for key, value, comp in zip(opts.data_labels.keys(), args, components):
- if comp == dummy_component or value=='dummy':
- continue
- if getattr(comp, 'visible', True) is False:
- continue
- if not opts.same_type(value, opts.data_labels[key].default):
- log.error(f'Setting bad value: {key}={value} expecting={type(opts.data_labels[key].default).__name__}')
- continue
- if opts.set(key, value):
- changed.append(key)
- if shared.opts.cuda_compile_backend == "olive-ai":
- install_olive()
- if cmd_opts.use_directml:
- directml_override_opts()
- if cmd_opts.use_openvino:
- if "Model" not in shared.opts.cuda_compile:
- shared.log.warning("OpenVINO: Enabling Torch Compile Model")
- shared.opts.cuda_compile.append("Model")
- if shared.opts.cuda_compile_backend != "openvino_fx":
- shared.log.warning("OpenVINO: Setting Torch Compiler backend to OpenVINO FX")
- shared.opts.cuda_compile_backend = "openvino_fx"
- if shared.opts.sd_backend != "diffusers":
- shared.log.warning("OpenVINO: Setting backend to Diffusers")
- shared.opts.sd_backend = "diffusers"
- try:
- if len(changed) > 0:
- opts.save(shared.config_filename)
- log.info(f'Settings: changed={len(changed)} {changed}')
- except RuntimeError:
- log.error(f'Settings failed: change={len(changed)} {changed}')
- return opts.dumpjson(), f'{len(changed)} Settings changed without save: {", ".join(changed)}'
- return opts.dumpjson(), f'{len(changed)} Settings changed{": " if len(changed) > 0 else ""}{", ".join(changed)}'
-
- def run_settings_single(value, key, progress=False):
- if not opts.same_type(value, opts.data_labels[key].default):
- return gr.update(visible=True), opts.dumpjson()
- if not opts.set(key, value):
- return gr.update(value=getattr(opts, key)), opts.dumpjson()
- if key == "cuda_compile_backend" and value == "olive-ai":
- install_olive()
- if cmd_opts.use_directml:
- directml_override_opts()
- opts.save(shared.config_filename)
- log.debug(f'Setting changed: {key}={value} progress={progress}')
- return get_value_for_setting(key), opts.dumpjson()
-
with gr.Blocks(analytics_enabled=False) as settings_interface:
- with gr.Row(elem_id="system_row"):
- restart_submit = gr.Button(value="Restart server", variant='primary', elem_id="restart_submit")
- shutdown_submit = gr.Button(value="Shutdown server", variant='primary', elem_id="shutdown_submit")
- unload_sd_model = gr.Button(value='Unload model', variant='primary', elem_id="sett_unload_sd_model")
- reload_sd_model = gr.Button(value='Reload model', variant='primary', elem_id="sett_reload_sd_model")
- enable_profiling = gr.Button(value='Start profiling', variant='primary', elem_id="enable_profiling")
-
- with gr.Tabs(elem_id="system") as system_tabs:
- global ui_system_tabs # pylint: disable=global-statement
- ui_system_tabs = system_tabs
- with gr.TabItem("Settings", id="system_settings", elem_id="tab_settings"):
- with gr.Row(elem_id="settings_row"):
- settings_submit = gr.Button(value="Apply settings", variant='primary', elem_id="settings_submit")
- preview_theme = gr.Button(value="Preview theme", variant='primary', elem_id="settings_preview_theme")
- defaults_submit = gr.Button(value="Restore defaults", variant='primary', elem_id="defaults_submit")
- with gr.Row():
- _settings_search = gr.Text(label="Search", elem_id="settings_search")
-
- result = gr.HTML(elem_id="settings_result")
- quicksettings_names = opts.quicksettings_list
- quicksettings_names = {x: i for i, x in enumerate(quicksettings_names) if x != 'quicksettings'}
- quicksettings_list = []
-
- previous_section = []
- tab_item_keys = []
- current_tab = None
- current_row = None
- dummy_component = gr.Textbox(visible=False, value='dummy')
- with gr.Tabs(elem_id="settings"):
- for i, (k, item) in enumerate(opts.data_labels.items()):
- section_must_be_skipped = item.section[0] is None
- if previous_section != item.section and not section_must_be_skipped:
- if len(item.section) == 2:
- elem_id, text = item.section
- elif len(item.section) == 3:
- _category, elem_id, text = item.section
- else:
- shared.log.error(f'Settings: section={item.section} invalid')
- continue
- if current_tab is not None and len(previous_section) > 0:
- create_dirty_indicator(previous_section[0], tab_item_keys)
- tab_item_keys = []
- current_row.__exit__()
- current_tab.__exit__()
- current_tab = gr.TabItem(elem_id=f"settings_{elem_id}", label=text)
- current_tab.__enter__()
- current_row = gr.Column(variant='compact')
- current_row.__enter__()
- previous_section = item.section
- if k in quicksettings_names and not shared.cmd_opts.freeze:
- quicksettings_list.append((i, k, item))
- components.append(dummy_component)
- elif section_must_be_skipped:
- components.append(dummy_component)
- else:
- component = create_setting_component(k)
- component_dict[k] = component
- tab_item_keys.append(k)
- components.append(component)
- if current_tab is not None and len(previous_section) > 0:
- create_dirty_indicator(previous_section[0], tab_item_keys)
- tab_item_keys = []
- current_row.__exit__()
- current_tab.__exit__()
-
- request_notifications = gr.Button(value='Request browser notifications', elem_id="request_notifications", visible=False)
- with gr.TabItem("Show all pages", elem_id="settings_show_all_pages"):
- create_dirty_indicator("show_all_pages", [], interactive=False)
-
- with gr.TabItem("Update", id="system_update", elem_id="tab_update"):
- from modules import update
- update.create_ui()
-
- with gr.TabItem("User interface", id="system_config", elem_id="tab_config"):
- loadsave.create_ui()
- create_dirty_indicator("tab_defaults", [], interactive=False)
-
- with gr.TabItem("ONNX", id="onnx_config", elem_id="tab_onnx"):
- from modules.onnx_impl import ui as ui_onnx
- ui_onnx.create_ui()
-
- def unload_sd_weights():
- modules.sd_models.unload_model_weights(op='model')
- modules.sd_models.unload_model_weights(op='refiner')
-
- def reload_sd_weights():
- modules.sd_models.reload_model_weights(force=True)
-
- def switch_profiling():
- shared.cmd_opts.profile = not shared.cmd_opts.profile
- shared.log.warning(f'Profiling: {shared.cmd_opts.profile}')
- return 'Stop profiling' if shared.cmd_opts.profile else 'Start profiling'
-
- unload_sd_model.click(fn=unload_sd_weights, inputs=[], outputs=[])
- reload_sd_model.click(fn=reload_sd_weights, inputs=[], outputs=[])
- enable_profiling.click(fn=switch_profiling, inputs=[], outputs=[enable_profiling])
- request_notifications.click(fn=lambda: None, inputs=[], outputs=[], _js='function(){}')
- preview_theme.click(fn=None, _js='previewTheme', inputs=[], outputs=[])
-
- timer.startup.record("ui-settings")
+ from modules import ui_settings
+ ui_settings.create_ui()
+ global ui_system_tabs # pylint: disable=global-statement
+ ui_system_tabs = ui_settings.ui_system_tabs
+ shared.opts.reorder()
+ timer.startup.record("ui-extensions")
with gr.Blocks(analytics_enabled=False) as info_interface:
with gr.Tabs(elem_id="tabs_info"):
@@ -401,6 +138,11 @@ def create_ui(startup_timer = None):
from modules import ui_docs
ui_docs.create_ui_wiki()
+ with gr.Blocks(analytics_enabled=False) as extensions_interface:
+ from modules import ui_extensions
+ ui_extensions.create_ui()
+ timer.startup.record("ui-extensions")
+
interfaces = []
interfaces += [(txt2img_interface, "Text", "txt2img")]
interfaces += [(img2img_interface, "Image", "img2img")]
@@ -415,123 +157,12 @@ def create_ui(startup_timer = None):
interfaces += script_callbacks.ui_tabs_callback()
interfaces += [(settings_interface, "System", "system")]
interfaces += [(info_interface, "Info", "info")]
-
- from modules import ui_extensions
- extensions_interface = ui_extensions.create_ui()
interfaces += [(extensions_interface, "Extensions", "extensions")]
- timer.startup.record("ui-extensions")
+
+ ui_app = ui_settings.create_quicksettings(interfaces)
shared.tab_names = []
for _interface, label, _ifid in interfaces:
shared.tab_names.append(label)
- with gr.Blocks(theme=theme.gradio_theme, analytics_enabled=False, title="SD.Next") as ui_app:
- with gr.Row(elem_id="quicksettings", variant="compact"):
- for _i, k, _item in sorted(quicksettings_list, key=lambda x: quicksettings_names.get(x[1], x[0])):
- component = create_setting_component(k, is_quicksettings=True)
- component_dict[k] = component
-
- generation_parameters_copypaste.connect_paste_params_buttons()
-
- with gr.Tabs(elem_id="tabs") as tabs:
- for interface, label, ifid in interfaces:
- if interface is None:
- continue
- # if label in shared.opts.hidden_tabs or label == '':
- # continue
- with gr.TabItem(label, id=ifid, elem_id=f"tab_{ifid}"):
- # log.debug(f'UI render: id={ifid}')
- interface.render()
- for interface, _label, ifid in interfaces:
- if interface is None:
- continue
- if ifid in ["extensions", "system"]:
- continue
- loadsave.add_block(interface, ifid)
- loadsave.add_component(f"webui/Tabs@{tabs.elem_id}", tabs)
- loadsave.setup_ui()
-
- if opts.notification_audio_enable and os.path.exists(os.path.join(script_path, opts.notification_audio_path)):
- gr.Audio(interactive=False, value=os.path.join(script_path, opts.notification_audio_path), elem_id="audio_notification", visible=False)
-
- text_settings = gr.Textbox(elem_id="settings_json", value=lambda: opts.dumpjson(), visible=False)
- components = [c for c in components if c is not None]
- settings_submit.click(
- fn=wrap_gradio_call(run_settings, extra_outputs=[gr.update()]),
- inputs=components,
- outputs=[text_settings, result],
- )
- defaults_submit.click(fn=lambda: shared.restore_defaults(restart=True), _js="restartReload")
- restart_submit.click(fn=lambda: shared.restart_server(restart=True), _js="restartReload")
- shutdown_submit.click(fn=lambda: shared.restart_server(restart=False), _js="restartReload")
-
- for _i, k, _item in quicksettings_list:
- component = component_dict[k]
- info = opts.data_labels[k]
- if isinstance(component, gr.components.Textbox):
- change_handlers = [component.blur, component.submit]
- else:
- change_handlers = [component.release if hasattr(component, 'release') else component.change]
- for change_handler in change_handlers:
- change_handler(
- fn=lambda value, k=k, progress=info.refresh is not None: run_settings_single(value, key=k, progress=progress),
- inputs=[component],
- outputs=[component, text_settings],
- show_progress=info.refresh is not None,
- )
-
- dummy_component = gr.Textbox(visible=False, value='dummy')
- button_set_checkpoint = gr.Button('Change model', elem_id='change_checkpoint', visible=False)
- button_set_checkpoint.click(
- fn=lambda value, _: run_settings_single(value, key='sd_model_checkpoint'),
- _js="function(v){ var res = desiredCheckpointName; desiredCheckpointName = ''; return [res || v, null]; }",
- inputs=[component_dict['sd_model_checkpoint'], dummy_component],
- outputs=[component_dict['sd_model_checkpoint'], text_settings],
- )
- button_set_refiner = gr.Button('Change refiner', elem_id='change_refiner', visible=False)
- button_set_refiner.click(
- fn=lambda value, _: run_settings_single(value, key='sd_model_checkpoint'),
- _js="function(v){ var res = desiredCheckpointName; desiredCheckpointName = ''; return [res || v, null]; }",
- inputs=[component_dict['sd_model_refiner'], dummy_component],
- outputs=[component_dict['sd_model_refiner'], text_settings],
- )
- button_set_vae = gr.Button('Change VAE', elem_id='change_vae', visible=False)
- button_set_vae.click(
- fn=lambda value, _: run_settings_single(value, key='sd_vae'),
- _js="function(v){ var res = desiredVAEName; desiredVAEName = ''; return [res || v, null]; }",
- inputs=[component_dict['sd_vae'], dummy_component],
- outputs=[component_dict['sd_vae'], text_settings],
- )
-
- def reference_submit(model):
- if '@' not in model: # diffusers
- loaded = modelloader.load_reference(model)
- return model if loaded else opts.sd_model_checkpoint
- else: # civitai
- model, url = model.split('@')
- loaded = modelloader.load_civitai(model, url)
- return loaded if loaded is not None else opts.sd_model_checkpoint
-
- button_set_reference = gr.Button('Change reference', elem_id='change_reference', visible=False)
- button_set_reference.click(
- fn=reference_submit,
- _js="function(v){ return desiredCheckpointName; }",
- inputs=[component_dict['sd_model_checkpoint']],
- outputs=[component_dict['sd_model_checkpoint']],
- )
- component_keys = [k for k in opts.data_labels.keys() if k in component_dict]
-
- def get_settings_values():
- return [get_value_for_setting(key) for key in component_keys]
-
- ui_app.load(
- fn=get_settings_values,
- inputs=[],
- outputs=[component_dict[k] for k in component_keys if component_dict[k] is not None],
- queue=False,
- )
-
- timer.startup.record("ui-defaults")
- loadsave.dump_defaults()
- ui_app.ui_loadsave = loadsave
return ui_app
diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py
index 2b75df0e5..ef40092ec 100644
--- a/modules/ui_extensions.py
+++ b/modules/ui_extensions.py
@@ -5,7 +5,8 @@ import errno
import html
from datetime import datetime, timedelta
import gradio as gr
-from modules import extensions, shared, paths, errors, ui_symbols
+from modules import extensions, shared, paths, errors, ui_symbols, call_queue
+
debug = shared.log.debug if os.environ.get('SD_EXT_DEBUG', None) is not None else lambda *args, **kwargs: None
extensions_index = "https://vladmandic.github.io/sd-data/pages/extensions.json"
@@ -437,86 +438,84 @@ def create_html(search_text, sort_column):
def create_ui():
import modules.ui
- with gr.Blocks(analytics_enabled=False) as ui:
- extensions_disable_all = gr.Radio(label="Disable all extensions", choices=["none", "user", "all"], value=shared.opts.disable_all_extensions, elem_id="extensions_disable_all", visible=False)
- extensions_disabled_list = gr.Text(elem_id="extensions_disabled_list", visible=False, container=False)
- extensions_update_list = gr.Text(elem_id="extensions_update_list", visible=False, container=False)
- with gr.Tabs(elem_id="tabs_extensions"):
- with gr.TabItem("Manage extensions", id="manage"):
- with gr.Row(elem_id="extensions_installed_top"):
- extension_to_install = gr.Text(elem_id="extension_to_install", visible=False)
- install_extension_button = gr.Button(elem_id="install_extension_button", visible=False)
- uninstall_extension_button = gr.Button(elem_id="uninstall_extension_button", visible=False)
- update_extension_button = gr.Button(elem_id="update_extension_button", visible=False)
- with gr.Column(scale=4):
- search_text = gr.Text(label="Search")
- with gr.Column(scale=1):
- sort_column = gr.Dropdown(value="default", label="Sort by", choices=list(sort_ordering.keys()), multiselect=False)
- with gr.Column(scale=1):
- refresh_extensions_button = gr.Button(value="Refresh extension list", variant="primary")
- check = gr.Button(value="Update all installed", variant="primary")
- apply = gr.Button(value="Apply changes", variant="primary")
- list_extensions()
- gr.HTML('''
- Extension list
- - Refesh extension list to download latest list with status
- - Check status of an extension by looking at status icon before installing it
- - After any operation such as install/uninstall or enable/disable, please restart the server
- ''')
- gr.HTML('')
- info = gr.HTML('')
- extensions_table = gr.HTML(create_html(search_text.value, sort_column.value))
- check.click(
- fn=modules.ui.wrap_gradio_call(check_updates, extra_outputs=[gr.update()]),
- _js="extensions_check",
- inputs=[info, extensions_disabled_list, search_text, sort_column],
- outputs=[extensions_table, info],
- )
- apply.click(
- fn=apply_changes,
- _js="extensions_apply",
- inputs=[extensions_disabled_list, extensions_update_list, extensions_disable_all],
- outputs=[],
- )
- refresh_extensions_button.click(
- fn=modules.ui.wrap_gradio_call(refresh_extensions_list, extra_outputs=[gr.update(), gr.update()]),
- inputs=[search_text, sort_column],
- outputs=[extensions_table, info],
- )
- install_extension_button.click(
- fn=modules.ui.wrap_gradio_call(install_extension, extra_outputs=[gr.update(), gr.update(), gr.update()]),
- inputs=[extension_to_install, search_text, sort_column],
- outputs=[extensions_table, info],
- )
- uninstall_extension_button.click(
- fn=modules.ui.wrap_gradio_call(uninstall_extension, extra_outputs=[gr.update(), gr.update(), gr.update()]),
- inputs=[extension_to_install, search_text, sort_column],
- outputs=[extensions_table, info],
- )
- update_extension_button.click(
- fn=modules.ui.wrap_gradio_call(update_extension, extra_outputs=[gr.update(), gr.update(), gr.update()]),
- inputs=[extension_to_install, search_text, sort_column],
- outputs=[extensions_table, info],
- )
- search_text.change(
- fn=modules.ui.wrap_gradio_call(search_extensions, extra_outputs=[gr.update(), gr.update()]),
- inputs=[search_text, sort_column],
- outputs=[extensions_table, info],
- )
- sort_column.change(
- fn=modules.ui.wrap_gradio_call(search_extensions, extra_outputs=[gr.update(), gr.update()]),
- inputs=[search_text, sort_column],
- outputs=[extensions_table, info],
- )
- with gr.TabItem("Manual install", id="install_from_url"):
- install_url = gr.Text(label="Extension GIT repository URL")
- install_branch = gr.Text(label="Specific branch name", placeholder="Leave empty for default main branch")
- install_dirname = gr.Text(label="Local directory name", placeholder="Leave empty for auto")
- install_button = gr.Button(value="Install", variant="primary")
- info = gr.HTML(elem_id="extension_info")
- install_button.click(
- fn=modules.ui.wrap_gradio_call(install_extension_from_url, extra_outputs=[gr.update()]),
- inputs=[install_dirname, install_url, install_branch, search_text, sort_column],
- outputs=[extensions_table, info],
- )
- return ui
+ extensions_disable_all = gr.Radio(label="Disable all extensions", choices=["none", "user", "all"], value=shared.opts.disable_all_extensions, elem_id="extensions_disable_all", visible=False)
+ extensions_disabled_list = gr.Text(elem_id="extensions_disabled_list", visible=False, container=False)
+ extensions_update_list = gr.Text(elem_id="extensions_update_list", visible=False, container=False)
+ with gr.Tabs(elem_id="tabs_extensions"):
+ with gr.TabItem("Manage extensions", id="manage"):
+ with gr.Row(elem_id="extensions_installed_top"):
+ extension_to_install = gr.Text(elem_id="extension_to_install", visible=False)
+ install_extension_button = gr.Button(elem_id="install_extension_button", visible=False)
+ uninstall_extension_button = gr.Button(elem_id="uninstall_extension_button", visible=False)
+ update_extension_button = gr.Button(elem_id="update_extension_button", visible=False)
+ with gr.Column(scale=4):
+ search_text = gr.Text(label="Search")
+ with gr.Column(scale=1):
+ sort_column = gr.Dropdown(value="default", label="Sort by", choices=list(sort_ordering.keys()), multiselect=False)
+ with gr.Column(scale=1):
+ refresh_extensions_button = gr.Button(value="Refresh extension list", variant="primary")
+ check = gr.Button(value="Update all installed", variant="primary")
+ apply = gr.Button(value="Apply changes", variant="primary")
+ list_extensions()
+ gr.HTML('''
+ Extension list
+ - Refesh extension list to download latest list with status
+ - Check status of an extension by looking at status icon before installing it
+ - After any operation such as install/uninstall or enable/disable, please restart the server
+ ''')
+ gr.HTML('')
+ info = gr.HTML('')
+ extensions_table = gr.HTML(create_html(search_text.value, sort_column.value))
+ check.click(
+ fn=call_queue.wrap_gradio_call(check_updates, extra_outputs=[gr.update()]),
+ _js="extensions_check",
+ inputs=[info, extensions_disabled_list, search_text, sort_column],
+ outputs=[extensions_table, info],
+ )
+ apply.click(
+ fn=apply_changes,
+ _js="extensions_apply",
+ inputs=[extensions_disabled_list, extensions_update_list, extensions_disable_all],
+ outputs=[],
+ )
+ refresh_extensions_button.click(
+ fn=call_queue.wrap_gradio_call(refresh_extensions_list, extra_outputs=[gr.update(), gr.update()]),
+ inputs=[search_text, sort_column],
+ outputs=[extensions_table, info],
+ )
+ install_extension_button.click(
+ fn=call_queue.wrap_gradio_call(install_extension, extra_outputs=[gr.update(), gr.update(), gr.update()]),
+ inputs=[extension_to_install, search_text, sort_column],
+ outputs=[extensions_table, info],
+ )
+ uninstall_extension_button.click(
+ fn=call_queue.wrap_gradio_call(uninstall_extension, extra_outputs=[gr.update(), gr.update(), gr.update()]),
+ inputs=[extension_to_install, search_text, sort_column],
+ outputs=[extensions_table, info],
+ )
+ update_extension_button.click(
+ fn=call_queue.wrap_gradio_call(update_extension, extra_outputs=[gr.update(), gr.update(), gr.update()]),
+ inputs=[extension_to_install, search_text, sort_column],
+ outputs=[extensions_table, info],
+ )
+ search_text.change(
+ fn=call_queue.wrap_gradio_call(search_extensions, extra_outputs=[gr.update(), gr.update()]),
+ inputs=[search_text, sort_column],
+ outputs=[extensions_table, info],
+ )
+ sort_column.change(
+ fn=call_queue.wrap_gradio_call(search_extensions, extra_outputs=[gr.update(), gr.update()]),
+ inputs=[search_text, sort_column],
+ outputs=[extensions_table, info],
+ )
+ with gr.TabItem("Manual install", id="install_from_url"):
+ install_url = gr.Text(label="Extension GIT repository URL")
+ install_branch = gr.Text(label="Specific branch name", placeholder="Leave empty for default main branch")
+ install_dirname = gr.Text(label="Local directory name", placeholder="Leave empty for auto")
+ install_button = gr.Button(value="Install", variant="primary")
+ info = gr.HTML(elem_id="extension_info")
+ install_button.click(
+ fn=call_queue.wrap_gradio_call(install_extension_from_url, extra_outputs=[gr.update()]),
+ inputs=[install_dirname, install_url, install_branch, search_text, sort_column],
+ outputs=[extensions_table, info],
+ )
diff --git a/modules/ui_settings.py b/modules/ui_settings.py
new file mode 100644
index 000000000..0a4f3b1eb
--- /dev/null
+++ b/modules/ui_settings.py
@@ -0,0 +1,371 @@
+import os
+import gradio as gr
+from modules import timer, shared, paths, theme, sd_models, modelloader, ui_common, ui_loadsave, generation_parameters_copypaste, call_queue, script_callbacks
+
+
+ui_system_tabs = None # required for system-info
+dummy_component = gr.Textbox(visible=False, value='dummy')
+text_settings = gr.Textbox(elem_id="settings_json", value=lambda: shared.opts.dumpjson(), visible=False)
+loadsave = ui_loadsave.UiLoadsave(shared.cmd_opts.ui_config)
+quicksettings_names = {x: i for i, x in enumerate(shared.opts.quicksettings_list) if x != 'quicksettings'}
+quicksettings_list = []
+components = []
+
+
+def apply_setting(key, value):
+ if value is None:
+ return gr.update()
+ if shared.cmd_opts.freeze:
+ return gr.update()
+ if key == 'sd_backend':
+ return gr.update()
+ if shared.opts.disable_weights_auto_swap and key in ['sd_model_checkpoint', 'sd_model_refiner', 'sd_model_dict', 'sd_vae', 'sd_unet', 'sd_text_encoder']:
+ return gr.update()
+ if key == "sd_model_checkpoint":
+ ckpt_info = sd_models.get_closet_checkpoint_match(value)
+ if ckpt_info is not None:
+ value = ckpt_info.title
+ else:
+ return gr.update()
+ comp_args = shared.opts.data_labels[key].component_args
+ if comp_args and isinstance(comp_args, dict) and comp_args.get('visible') is False:
+ return gr.update()
+ valtype = type(shared.opts.data_labels[key].default)
+ oldval = shared.opts.data.get(key, None)
+ shared.opts.data[key] = valtype(value) if valtype != type(None) else value
+ if oldval != value and shared.opts.data_labels[key].onchange is not None:
+ shared.opts.data_labels[key].onchange()
+ shared.opts.save(shared.config_filename)
+ return getattr(shared.opts, key)
+
+
+def get_value_for_setting(key):
+ value = getattr(shared.opts, key)
+ info = shared.opts.data_labels[key]
+ args = info.component_args() if callable(info.component_args) else info.component_args or {}
+ args = {k: v for k, v in args.items() if k not in {'precision', 'multiselect', 'visible'}}
+ return gr.update(value=value, **args)
+
+
+def ordered_ui_categories():
+ return ['dimensions', 'sampler', 'seed', 'denoising', 'cfg', 'checkboxes', 'accordions', 'override_settings', 'scripts'] # a1111 compatibility item, not implemented
+
+
+def create_setting_component(key, is_quicksettings=False):
+ def fun():
+ return shared.opts.data[key] if key in shared.opts.data else shared.opts.data_labels[key].default
+
+ info = shared.opts.data_labels[key]
+ t = type(info.default)
+ args = (info.component_args() if callable(info.component_args) else info.component_args) or {}
+ if info.component is not None:
+ comp = info.component
+ elif t == str:
+ comp = gr.Textbox
+ elif t == int:
+ comp = gr.Number
+ elif t == bool:
+ comp = gr.Checkbox
+ else:
+ raise ValueError(f'bad options item type: {t} for key {key}')
+ elem_id = f"setting_{key}"
+ dirty_indicator = None
+
+ if not is_quicksettings:
+ dirtyable_setting = gr.Group(elem_classes="dirtyable", visible=args.get("visible", True))
+ dirtyable_setting.__enter__()
+ dirty_indicator = gr.Button("", elem_classes="modification-indicator", elem_id=f"modification_indicator_{key}")
+
+ if info.refresh is not None:
+ if is_quicksettings:
+ res = comp(label=info.label, value=fun(), elem_id=elem_id, **args)
+ ui_common.create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}")
+ else:
+ with gr.Row():
+ res = comp(label=info.label, value=fun(), elem_id=elem_id, **args)
+ ui_common.create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}")
+ elif info.folder is not None:
+ with gr.Row():
+ res = comp(label=info.label, value=fun(), elem_id=elem_id, elem_classes="folder-selector", **args)
+ # ui_common.create_browse_button(res, f"folder_{key}")
+ else:
+ try:
+ res = comp(label=info.label, value=fun(), elem_id=elem_id, **args)
+ except Exception as e:
+ shared.log.error(f'Error creating setting: {key} {e}')
+ res = None
+
+ if res is not None and not is_quicksettings:
+ res.change(fn=None, inputs=res, _js=f'(val) => markIfModified("{key}", val)')
+ if dirty_indicator is not None:
+ dirty_indicator.click(fn=lambda: shared.opts.get_default(key), outputs=[res], show_progress=False)
+ dirtyable_setting.__exit__()
+
+ return res
+
+def create_dirty_indicator(key, keys_to_reset, **kwargs):
+ def get_default_values():
+ values = [shared.opts.get_default(key) for key in keys_to_reset]
+ shared.log.debug(f'Settings restore: section={key} keys={keys_to_reset} values={values}')
+ return values
+
+ elements_to_reset = [shared.settings_components[_key] for _key in keys_to_reset if shared.settings_components[_key] is not None]
+ indicator = gr.Button('', elem_classes="modification-indicator", elem_id=f"modification_indicator_{key}", **kwargs)
+ indicator.click(fn=get_default_values, outputs=elements_to_reset, show_progress=True)
+ return indicator
+
+
+def run_settings(*args):
+ changed = []
+ for key, value, comp in zip(shared.opts.data_labels.keys(), args, components):
+ if comp == dummy_component or value=='dummy':
+ continue
+ if getattr(comp, 'visible', True) is False:
+ continue
+ if not shared.opts.same_type(value, shared.opts.data_labels[key].default):
+ shared.log.error(f'Setting bad value: {key}={value} expecting={type(shared.opts.data_labels[key].default).__name__}')
+ continue
+ if shared.opts.set(key, value):
+ changed.append(key)
+ if shared.opts.cuda_compile_backend == "olive-ai":
+ from modules.onnx_impl import install_olive
+ install_olive()
+ if shared.cmd_opts.use_directml:
+ from modules.dml import directml_override_opts
+ directml_override_opts()
+ if shared.cmd_opts.use_openvino:
+ if "Model" not in shared.opts.cuda_compile:
+ shared.log.warning("OpenVINO: Enabling Torch Compile Model")
+ shared.opts.cuda_compile.append("Model")
+ if shared.opts.cuda_compile_backend != "openvino_fx":
+ shared.log.warning("OpenVINO: Setting Torch Compiler backend to OpenVINO FX")
+ shared.opts.cuda_compile_backend = "openvino_fx"
+ if shared.opts.sd_backend != "diffusers":
+ shared.log.warning("OpenVINO: Setting backend to Diffusers")
+ shared.opts.sd_backend = "diffusers"
+ try:
+ if len(changed) > 0:
+ shared.opts.save(shared.config_filename)
+ shared.log.info(f'Settings: changed={len(changed)} {changed}')
+ except RuntimeError:
+ shared.log.error(f'Settings failed: change={len(changed)} {changed}')
+ return shared.opts.dumpjson(), f'{len(changed)} Settings changed without save: {", ".join(changed)}'
+ return shared.opts.dumpjson(), f'{len(changed)} Settings changed{": " if len(changed) > 0 else ""}{", ".join(changed)}'
+
+def run_settings_single(value, key, progress=False):
+ if not shared.opts.same_type(value, shared.opts.data_labels[key].default):
+ return gr.update(visible=True), shared.opts.dumpjson()
+ if not shared.opts.set(key, value):
+ return gr.update(value=getattr(shared.opts, key)), shared.opts.dumpjson()
+ if key == "cuda_compile_backend" and value == "olive-ai":
+ from modules.onnx_impl import install_olive
+ install_olive()
+ if shared.cmd_opts.use_directml:
+ from modules.dml import directml_override_opts
+ directml_override_opts()
+ shared.opts.save(shared.config_filename)
+ shared.log.debug(f'Setting changed: {key}={value} progress={progress}')
+ return get_value_for_setting(key), shared.opts.dumpjson()
+
+
+def create_ui():
+ with gr.Row(elem_id="system_row"):
+ restart_submit = gr.Button(value="Restart server", variant='primary', elem_id="restart_submit")
+ shutdown_submit = gr.Button(value="Shutdown server", variant='primary', elem_id="shutdown_submit")
+ unload_sd_model = gr.Button(value='Unload model', variant='primary', elem_id="sett_unload_sd_model")
+ reload_sd_model = gr.Button(value='Reload model', variant='primary', elem_id="sett_reload_sd_model")
+ enable_profiling = gr.Button(value='Start profiling', variant='primary', elem_id="enable_profiling")
+
+ with gr.Tabs(elem_id="system") as system_tabs:
+ global ui_system_tabs # pylint: disable=global-statement
+ ui_system_tabs = system_tabs
+ with gr.TabItem("Settings", id="system_settings", elem_id="tab_settings"):
+ with gr.Row(elem_id="settings_row"):
+ settings_submit = gr.Button(value="Apply settings", variant='primary', elem_id="settings_submit")
+ preview_theme = gr.Button(value="Preview theme", variant='primary', elem_id="settings_preview_theme")
+ defaults_submit = gr.Button(value="Restore defaults", variant='primary', elem_id="defaults_submit")
+ with gr.Row():
+ _settings_search = gr.Text(label="Search", elem_id="settings_search")
+
+ result = gr.HTML(elem_id="settings_result")
+ script_callbacks.ui_settings_callback() # let extensions create settings
+ sections = []
+ for item in shared.opts.data_labels.values(): # get unique sections from all items
+ if len(item.section) == 2:
+ section_id, section_text = item.section
+ elif len(item.section) == 3: # compatibility item with a1111 extensions
+ _category, section_id, section_text = item.section
+ item.section = section_id, section_text
+ else:
+ section_id = None
+ item.section = None, 'Hidden'
+ if (section_id, section_text) not in sections:
+ sections.append((section_id, section_text))
+
+ with gr.Tabs(elem_id="settings"):
+ for (section_id, section_text) in sections:
+ items = [item for item in shared.opts.data_labels.items() if item[1].section[0] == section_id] # find all items in this section
+ hidden = section_id is None or 'hidden' in section_id.lower() or 'hidden' in section_text.lower()
+ shared.log.trace(f'Settings: section="{section_id}" title="{section_text}" items={len(items)} hidden={hidden}')
+ if hidden:
+ components.append(dummy_component)
+ else:
+ with gr.TabItem(elem_id=f"settings_section_tab_{section_id}", label=section_text):
+ current_items = []
+ for (key, item) in items:
+ if key in quicksettings_names:
+ quicksettings_list.append((key, item))
+ components.append(dummy_component) # TODO: quicksettings should clone insetad of move
+ else:
+ with gr.Row(elem_id=f"settings_section_row_{section_id}"): # only so we can add dirty indicator at the start of the row
+ component = create_setting_component(key)
+ shared.settings_components[key] = component
+ current_items.append(key)
+ components.append(component)
+ create_dirty_indicator(section_id, current_items)
+
+ with gr.TabItem("Show all pages", elem_id="settings_show_all_pages"):
+ create_dirty_indicator("show_all_pages", [])
+ request_notifications = gr.Button(value='Request browser notifications', elem_id="request_notifications", visible=False)
+
+
+ with gr.TabItem("Update", id="system_update", elem_id="tab_update"):
+ from modules import update
+ update.create_ui()
+
+ with gr.TabItem("User interface", id="system_config", elem_id="tab_config"):
+ loadsave.create_ui()
+ create_dirty_indicator("tab_defaults", [], interactive=False)
+
+ with gr.TabItem("ONNX", id="onnx_config", elem_id="tab_onnx"):
+ from modules.onnx_impl import ui as ui_onnx
+ ui_onnx.create_ui()
+
+ def unload_sd_weights():
+ sd_models.unload_model_weights(op='model')
+ sd_models.unload_model_weights(op='refiner')
+
+ def reload_sd_weights():
+ sd_models.reload_model_weights(force=True)
+
+ def switch_profiling():
+ shared.cmd_opts.profile = not shared.cmd_opts.profile
+ shared.log.warning(f'Profiling: {shared.cmd_opts.profile}')
+ return 'Stop profiling' if shared.cmd_opts.profile else 'Start profiling'
+
+ unload_sd_model.click(fn=unload_sd_weights, inputs=[], outputs=[])
+ reload_sd_model.click(fn=reload_sd_weights, inputs=[], outputs=[])
+ enable_profiling.click(fn=switch_profiling, inputs=[], outputs=[enable_profiling])
+ request_notifications.click(fn=lambda: None, inputs=[], outputs=[], _js='function(){}')
+ preview_theme.click(fn=None, _js='previewTheme', inputs=[], outputs=[])
+ settings_submit.click(
+ fn=call_queue.wrap_gradio_call(run_settings, extra_outputs=[gr.update()]),
+ inputs=components,
+ outputs=[text_settings, result],
+ )
+ defaults_submit.click(fn=lambda: shared.restore_defaults(restart=True), _js="restartReload")
+ restart_submit.click(fn=lambda: shared.restart_server(restart=True), _js="restartReload")
+ shutdown_submit.click(fn=lambda: shared.restart_server(restart=False), _js="restartReload")
+
+
+def create_quicksettings(interfaces):
+ shared.tab_names = []
+ for _interface, label, _ifid in interfaces:
+ shared.tab_names.append(label)
+
+ with gr.Blocks(theme=theme.gradio_theme, analytics_enabled=False, title="SD.Next") as ui_app:
+ with gr.Row(elem_id="quicksettings", variant="compact"):
+ for k, _item in sorted(quicksettings_list, key=lambda x: quicksettings_names.get(x[1], x[0])):
+ component = create_setting_component(k, is_quicksettings=True)
+ shared.settings_components[k] = component
+
+ generation_parameters_copypaste.connect_paste_params_buttons()
+
+ with gr.Tabs(elem_id="tabs") as tabs:
+ for interface, label, ifid in interfaces:
+ if interface is None:
+ continue
+ with gr.TabItem(label, id=ifid, elem_id=f"tab_{ifid}"):
+ interface.render()
+ for interface, _label, ifid in interfaces:
+ if interface is None:
+ continue
+ if ifid in ["extensions", "system"]:
+ continue
+ loadsave.add_block(interface, ifid)
+ loadsave.add_component(f"webui/Tabs@{tabs.elem_id}", tabs)
+ loadsave.setup_ui()
+
+ if shared.opts.notification_audio_enable and os.path.exists(os.path.join(paths.script_path, shared.opts.notification_audio_path)):
+ gr.Audio(interactive=False, value=os.path.join(paths.script_path, shared.opts.notification_audio_path), elem_id="audio_notification", visible=False)
+
+ for k, _item in quicksettings_list:
+ component = shared.settings_components[k]
+ info = shared.opts.data_labels[k]
+ if isinstance(component, gr.components.Textbox):
+ change_handlers = [component.blur, component.submit]
+ else:
+ change_handlers = [component.release if hasattr(component, 'release') else component.change]
+ for change_handler in change_handlers:
+ change_handler(
+ fn=lambda value, k=k, progress=info.refresh is not None: run_settings_single(value, key=k, progress=progress),
+ inputs=[component],
+ outputs=[component, text_settings],
+ show_progress=info.refresh is not None,
+ )
+
+ dummy_component = gr.Textbox(visible=False, value='dummy')
+ button_set_checkpoint = gr.Button('Change model', elem_id='change_checkpoint', visible=False)
+ button_set_checkpoint.click(
+ fn=lambda value, _: run_settings_single(value, key='sd_model_checkpoint'),
+ _js="function(v){ var res = desiredCheckpointName; desiredCheckpointName = ''; return [res || v, null]; }",
+ inputs=[shared.settings_components['sd_model_checkpoint'], dummy_component],
+ outputs=[shared.settings_components['sd_model_checkpoint'], text_settings],
+ )
+ button_set_refiner = gr.Button('Change refiner', elem_id='change_refiner', visible=False)
+ button_set_refiner.click(
+ fn=lambda value, _: run_settings_single(value, key='sd_model_checkpoint'),
+ _js="function(v){ var res = desiredCheckpointName; desiredCheckpointName = ''; return [res || v, null]; }",
+ inputs=[shared.settings_components['sd_model_refiner'], dummy_component],
+ outputs=[shared.settings_components['sd_model_refiner'], text_settings],
+ )
+ button_set_vae = gr.Button('Change VAE', elem_id='change_vae', visible=False)
+ button_set_vae.click(
+ fn=lambda value, _: run_settings_single(value, key='sd_vae'),
+ _js="function(v){ var res = desiredVAEName; desiredVAEName = ''; return [res || v, null]; }",
+ inputs=[shared.settings_components['sd_vae'], dummy_component],
+ outputs=[shared.settings_components['sd_vae'], text_settings],
+ )
+
+ def reference_submit(model):
+ if '@' not in model: # diffusers
+ loaded = modelloader.load_reference(model)
+ return model if loaded else shared.opts.sd_model_checkpoint
+ else: # civitai
+ model, url = model.split('@')
+ loaded = modelloader.load_civitai(model, url)
+ return loaded if loaded is not None else shared.opts.sd_model_checkpoint
+
+ button_set_reference = gr.Button('Change reference', elem_id='change_reference', visible=False)
+ button_set_reference.click(
+ fn=reference_submit,
+ _js="function(v){ return desiredCheckpointName; }",
+ inputs=[shared.settings_components['sd_model_checkpoint']],
+ outputs=[shared.settings_components['sd_model_checkpoint']],
+ )
+ component_keys = [k for k in shared.opts.data_labels.keys() if k in shared.settings_components]
+
+ def get_settings_values():
+ return [get_value_for_setting(key) for key in component_keys]
+
+ ui_app.load(
+ fn=get_settings_values,
+ inputs=[],
+ outputs=[shared.settings_components[k] for k in component_keys if shared.settings_components[k] is not None],
+ queue=False,
+ )
+
+ timer.startup.record("ui-defaults")
+ loadsave.dump_defaults()
+ ui_app.ui_loadsave = loadsave
+ return ui_app
diff --git a/wiki b/wiki
index 9aff8cd69..90e18e0d1 160000
--- a/wiki
+++ b/wiki
@@ -1 +1 @@
-Subproject commit 9aff8cd69b01570bd7fd2d52b0f9da6baec9b3be
+Subproject commit 90e18e0d17ab43cddbe3d8ff9169707d1d289a41