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