From 0512e2973c584489a0d4f6149bd5b8435d6dfc56 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 24 Aug 2023 10:01:39 +0200 Subject: [PATCH] refactor settings ui --- CHANGELOG.md | 3 +- extensions-builtin/sd-webui-agent-scheduler | 2 +- extensions-builtin/sd-webui-controlnet | 2 +- .../stable-diffusion-webui-images-browser | 2 +- javascript/settings.js | 168 +++++++++++++++++ javascript/style.css | 8 +- javascript/ui.js | 177 +----------------- modules/processing.py | 3 +- modules/sd_models.py | 9 +- modules/shared.py | 6 +- modules/ui.py | 6 +- 11 files changed, 193 insertions(+), 193 deletions(-) create mode 100644 javascript/settings.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 54c37fc1d..68a8c3e0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Change Log for SD.Next -## Update for 2023-08-22 +## Update for 2023-08-24 - general: - all system and image paths are now relative by default - fix extra networks previews - add settings validation when performing load/save + - settings tab in ui now shows settings that are changed from default values - optimize model hashing - add cli param `--skip-all` that skips all installer checks use at personal discretion, but it can be useful for bulk deployments diff --git a/extensions-builtin/sd-webui-agent-scheduler b/extensions-builtin/sd-webui-agent-scheduler index 86857553e..e8d02c7e1 160000 --- a/extensions-builtin/sd-webui-agent-scheduler +++ b/extensions-builtin/sd-webui-agent-scheduler @@ -1 +1 @@ -Subproject commit 86857553e715c8aa50f503dc2da320da8964e701 +Subproject commit e8d02c7e119a890175ab2bf6649acdbcd743b861 diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index fb000270b..9af9d6b94 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit fb000270beef677d2caedbaecb0ef923eaeef2eb +Subproject commit 9af9d6b94329092266af5be5c26b40d2e3fe96e9 diff --git a/extensions-builtin/stable-diffusion-webui-images-browser b/extensions-builtin/stable-diffusion-webui-images-browser index 15c337914..a48f65350 160000 --- a/extensions-builtin/stable-diffusion-webui-images-browser +++ b/extensions-builtin/stable-diffusion-webui-images-browser @@ -1 +1 @@ -Subproject commit 15c337914d86040d50864a37488f447cd233b2bc +Subproject commit a48f65350dcf33f367cb5a79eee95999f5d692c0 diff --git a/javascript/settings.js b/javascript/settings.js new file mode 100644 index 000000000..33411dc0d --- /dev/null +++ b/javascript/settings.js @@ -0,0 +1,168 @@ +const monitoredOpts = [ + { sd_model_checkpoint: null }, + { + sd_backend: () => { + gradioApp().getElementById('refresh_sd_model_checkpoint')?.click(); + }, + }, +]; + +function updateOpts(json_string) { + const settings_data = JSON.parse(json_string); + for (const op of monitoredOpts) { + const key = Object.keys(op)[0]; + const callback = op[key]; + if (opts[key] && opts[key] !== settings_data.values[key]) { + console.log('updateOpts', key, opts[key], settings_data.values[key]); + if (callback) callback(); + } + } + opts = settings_data.values; + opts_metadata = settings_data.metadata; + Object.entries(opts_metadata).forEach(([opt, meta]) => { + if (!opts_tabs[meta.tab_name]) opts_tabs[meta.tab_name] = {}; + if (!opts_tabs[meta.tab_name].unsaved_keys) opts_tabs[meta.tab_name].unsaved_keys = new Set(); + if (!opts_tabs[meta.tab_name].saved_keys) opts_tabs[meta.tab_name].saved_keys = new Set(); + if (!meta.is_stored) opts_tabs[meta.tab_name].unsaved_keys.add(opt); + else opts_tabs[meta.tab_name].saved_keys.add(opt); + }); +} + +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(); + } + gradioApp().querySelectorAll('#settings > .tab-content > .tabitem').forEach((elem) => { + if (elem.id === 'settings_tab_licenses' || elem.id === 'settings_show_all_pages') return; + elem.style.display = 'block'; + }); +} + +function markIfModified(setting_name, value) { + const elem = gradioApp().getElementById(`modification_indicator_${setting_name}`); + if (elem == null) return; + // Use JSON.stringify to compare nested objects (e.g. arrays for checkbox-groups) + const previous_value_json = JSON.stringify(opts[setting_name]); + const changed_value = JSON.stringify(value) !== previous_value_json; + if (changed_value) elem.title = `click to revert to previous value: ${previous_value_json}`; + + const is_unsaved = opts_metadata[setting_name].is_stored; + if (is_unsaved) elem.title = 'custom value'; + elem.disabled = !(is_unsaved || changed_value); + elem.classList.toggle('changed', changed_value); + elem.classList.toggle('unsaved', is_unsaved); + + const { tab_name } = opts_metadata[setting_name]; + if (!opts_tabs[tab_name].changed) opts_tabs[tab_name].changed = new Set(); + const changed_items = opts_tabs[tab_name].changed; + if (changed_value) changed_items.add(setting_name); + else changed_items.delete(setting_name); + const unsaved = opts_tabs[tab_name].unsaved_keys; + const saved = opts_tabs[tab_name].saved_keys; + + // Set the indicator on the tab nav element + const tab_nav_indicator = gradioApp().getElementById(`modification_indicator_${tab_name}`); + tab_nav_indicator.disabled = (changed_items.size === 0) && (unsaved.size === 0); + tab_nav_indicator.title = ''; + tab_nav_indicator.classList.toggle('changed', changed_items.size > 0); + tab_nav_indicator.classList.toggle('unsaved', saved.size > 0); + if (changed_items.size > 0) tab_nav_indicator.title += `click to reset ${changed_items.size} unapplied changes in this tab\n`; + if (saved.size > 0) tab_nav_indicator.title += `${saved.size} custom values\n${unsaved.size} default values}`; +} + +onAfterUiUpdate(async () => { + if (Object.keys(opts).length !== 0) return; + const json_elem = gradioApp().getElementById('settings_json'); + if (!json_elem) return; + json_elem.parentElement.style.display = 'none'; + const textarea = json_elem.querySelector('textarea'); + const jsdata = textarea.value; + updateOpts(jsdata); + executeCallbacks(optionsChangedCallbacks); + register_drag_drop(); + + Object.defineProperty(textarea, 'value', { + set(newValue) { + const valueProp = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value'); + const oldValue = valueProp.get.call(textarea); + valueProp.set.call(textarea, newValue); + if (oldValue !== newValue) updateOpts(textarea.value); + executeCallbacks(optionsChangedCallbacks); + }, + get() { + const valueProp = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value'); + return valueProp.get.call(textarea); + }, + }); + + const settings_search = gradioApp().querySelectorAll('#settings_search > label > textarea')[0]; + settings_search.oninput = (e) => { + setTimeout(() => { + showAllSettings(); + gradioApp().querySelectorAll('#tab_settings .tabitem').forEach((section) => { + section.querySelectorAll('.dirtyable').forEach((setting) => { + const visible = setting.innerText.toLowerCase().includes(e.target.value.toLowerCase()) || setting.id.toLowerCase().includes(e.target.value.toLowerCase()); + if (!visible) { + setting.style.display = 'none'; + } else { + setting.style.removeProperty('display'); + } + }); + }); + }, 50); + }; +}); + +onOptionsChanged(() => { + const elem = gradioApp().getElementById('sd_checkpoint_hash'); + const sd_checkpoint_hash = opts.sd_checkpoint_hash || ''; + const shorthash = sd_checkpoint_hash.substring(0, 10); + + if (elem && elem.textContent !== shorthash) { + elem.textContent = shorthash; + elem.title = sd_checkpoint_hash; + elem.href = `https://google.com/search?q=${sd_checkpoint_hash}`; + } +}); + +onOptionsChanged(() => { + const setting_elems = gradioApp().querySelectorAll('#settings [id^="setting_"]'); + setting_elems.forEach((elem) => { + const setting_name = elem.id.replace('setting_', ''); + markIfModified(setting_name, opts[setting_name]); + }); +}); + +onUiLoaded(() => { + const tab_nav_element = gradioApp().querySelector('#settings > .tab-nav'); + const tab_nav_buttons = gradioApp().querySelectorAll('#settings > .tab-nav > button'); + const tab_elements = gradioApp().querySelectorAll('#settings > div:not(.tab-nav)'); + const observer = new MutationObserver((mutations) => { + const show_all_pages_dummy = gradioApp().getElementById('settings_show_all_pages'); + if (show_all_pages_dummy.style.display === 'none') { return; } + function mutation_on_style(mut) { + return mut.type === 'attributes' && mut.attributeName === 'style'; + } + if (mutations.some(mutation_on_style)) { + showAllSettings(); + } + }); + const tab_content_wrapper = document.createElement('div'); + tab_content_wrapper.className = 'tab-content'; + tab_nav_element.parentElement.insertBefore(tab_content_wrapper, tab_nav_element.nextSibling); + + tab_elements.forEach((elem, index) => { + // Move the modification indicator to the toplevel tab button + const tab_name = elem.id.replace('settings_', ''); + const indicator = gradioApp().getElementById(`modification_indicator_${tab_name}`); + tab_nav_element.insertBefore(indicator, tab_nav_buttons[index]); + + // Add the tab content to the wrapper + tab_content_wrapper.appendChild(elem); + + // Add the mutation observer to the tab element + observer.observe(elem, { attributes: true, attributeFilter: ['style'] }); + }); +}); diff --git a/javascript/style.css b/javascript/style.css index 37ae7d136..ae8f529b3 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -128,11 +128,11 @@ div#extras_scale_to_tab div.form{ flex-direction: row; } #settings .block.gradio-checkbox { margin: 0; width: auto; } #settings .dirtyable { display: grid; grid-template-columns: .3em auto; gap: .5em; } #settings .dirtyable.hidden { display: none; } -#settings .modification-indicator { width: 100%; height: 100%; border-radius: 1em !important; padding: 0; } +#settings .modification-indicator { width: 100%; height: 100%; border-radius: 1em !important; padding: 0; width: 0 } #settings .modification-indicator:disabled { visibility: hidden; } -#settings .modification-indicator.unsaved { background: var(--color-accent-soft); } -#settings .modification-indicator.changed { background: var(--color-accent); } -#settings .modification-indicator.changed.unsaved { background-image: linear-gradient(var(--color-accent) 25%, var(--color-accent-soft) 75%); } +#settings .modification-indicator.unsaved { background: var(--color-accent-soft); width: 4px; } +#settings .modification-indicator.changed { background: var(--color-accent); width: 4px; } +#settings .modification-indicator.changed.unsaved { background-image: linear-gradient(var(--color-accent) 25%, var(--color-accent-soft) 75%); width: 4px; } #settings_result { margin: 0 1.2em; } /* live preview */ diff --git a/javascript/ui.js b/javascript/ui.js index 7e3870ec7..5100125e9 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -232,46 +232,6 @@ function register_drag_drop() { }); } -const monitoredOpts = [ - { sd_model_checkpoint: null }, - { - sd_backend: () => { - gradioApp().getElementById('refresh_sd_model_checkpoint')?.click(); - }, - }, -]; - -function updateOpts(json_string) { - const settings_data = JSON.parse(json_string); - for (const op of monitoredOpts) { - const key = Object.keys(op)[0]; - const callback = op[key]; - if (opts[key] && opts[key] !== settings_data.values[key]) { - console.log('updateOpts', key, opts[key], settings_data.values[key]); - if (callback) callback(); - } - } - opts = settings_data.values; - opts_metadata = settings_data.metadata; - Object.entries(opts_metadata).forEach(([opt, meta]) => { - if (!opts_tabs[meta.tab_name]) opts_tabs[meta.tab_name] = {}; - if (!opts_tabs[meta.tab_name].unsaved_keys) opts_tabs[meta.tab_name].unsaved_keys = new Set(); - if (!meta.is_stored) opts_tabs[meta.tab_name].unsaved_keys.add(opt); - }); -} - -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(); - } - gradioApp().querySelectorAll('#settings > .tab-content > .tabitem').forEach((elem) => { - if (elem.id === 'settings_tab_licenses' || elem.id === 'settings_show_all_pages') return; - elem.style.display = 'block'; - }); -} - function sortUIElements() { // sort top-level tabs const currSelected = gradioApp()?.querySelector('.tab-nav > .selected')?.innerText; @@ -302,69 +262,12 @@ function sortUIElements() { console.log('sortUIElements'); } -function markIfModified(setting_name, value) { - const elem = gradioApp().getElementById(`modification_indicator_${setting_name}`); - if (elem == null) return; - // Use JSON.stringify to compare nested objects (e.g. arrays for checkbox-groups) - const previous_value_json = JSON.stringify(opts[setting_name]); - const changed_value = JSON.stringify(value) !== previous_value_json; - if (changed_value) { - elem.title = `Click to revert to previous value: ${previous_value_json}`; - } - - const is_unsaved = !opts_metadata[setting_name].is_stored; - if (is_unsaved) { - elem.title = 'Default value (not saved to config)'; - } - elem.disabled = !(is_unsaved || changed_value); - elem.classList.toggle('changed', changed_value); - elem.classList.toggle('unsaved', is_unsaved); - - const { tab_name } = opts_metadata[setting_name]; - if (!opts_tabs[tab_name].changed) opts_tabs[tab_name].changed = new Set(); - const changed_items = opts_tabs[tab_name].changed; - if (changed_value) changed_items.add(setting_name); - else changed_items.delete(setting_name); - const unsaved = opts_tabs[tab_name].unsaved_keys; - - // Set the indicator on the tab nav element - const tab_nav_indicator = gradioApp().getElementById(`modification_indicator_${tab_name}`); - tab_nav_indicator.disabled = (changed_items.size === 0) && (unsaved.size === 0); - tab_nav_indicator.title = ''; - tab_nav_indicator.classList.toggle('changed', changed_items.size > 0); - tab_nav_indicator.classList.toggle('unsaved', unsaved.size > 0); - if (changed_items.size > 0) { tab_nav_indicator.title += `Click to reset ${changed_items.size} unapplied change${changed_items.size > 1 ? 's' : ''} in this tab.\n`; } - if (unsaved.size > 0) { tab_nav_indicator.title += `${unsaved.size} new default value${unsaved.size > 1 ? 's' : ''} (not yet saved).`; } -} - onAfterUiUpdate(async () => { - sortUIElements(); - if (Object.keys(opts).length !== 0) return; - const json_elem = gradioApp().getElementById('settings_json'); - if (!json_elem) return; - json_elem.parentElement.style.display = 'none'; - const textarea = json_elem.querySelector('textarea'); - const jsdata = textarea.value; - updateOpts(jsdata); - executeCallbacks(optionsChangedCallbacks); - register_drag_drop(); - - Object.defineProperty(textarea, 'value', { - set(newValue) { - const valueProp = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value'); - const oldValue = valueProp.get.call(textarea); - valueProp.set.call(textarea, newValue); - if (oldValue !== newValue) updateOpts(textarea.value); - executeCallbacks(optionsChangedCallbacks); - }, - get() { - const valueProp = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value'); - return valueProp.get.call(textarea); - }, - }); + let promptsInitialized = false; function registerTextarea(id, id_counter, id_button) { const prompt = gradioApp().getElementById(id); + if (!prompt) return; const counter = gradioApp().getElementById(id_counter); const localTextarea = gradioApp().querySelector(`#${id} > label > textarea`); if (counter.parentElement === prompt.parentElement) return; @@ -372,85 +275,15 @@ onAfterUiUpdate(async () => { prompt.parentElement.style.position = 'relative'; promptTokecountUpdateFuncs[id] = () => { update_token_counter(id_button); }; localTextarea.addEventListener('input', promptTokecountUpdateFuncs[id]); + if (!promptsInitialized) console.log('initPrompts') + promptsInitialized = true; } + sortUIElements(); registerTextarea('txt2img_prompt', 'txt2img_token_counter', 'txt2img_token_button'); registerTextarea('txt2img_neg_prompt', 'txt2img_negative_token_counter', 'txt2img_negative_token_button'); registerTextarea('img2img_prompt', 'img2img_token_counter', 'img2img_token_button'); registerTextarea('img2img_neg_prompt', 'img2img_negative_token_counter', 'img2img_negative_token_button'); - - const settings_search = gradioApp().querySelectorAll('#settings_search > label > textarea')[0]; - settings_search.oninput = (e) => { - setTimeout(() => { - showAllSettings(); - gradioApp().querySelectorAll('#tab_settings .tabitem').forEach((section) => { - section.querySelectorAll('.dirtyable').forEach((setting) => { - const visible = setting.innerText.toLowerCase().includes(e.target.value.toLowerCase()) || setting.id.toLowerCase().includes(e.target.value.toLowerCase()); - if (!visible) { - setting.style.display = 'none'; - } else { - setting.style.removeProperty('display'); - } - }); - }); - }, 50); - }; -}); - -onOptionsChanged(() => { - const elem = gradioApp().getElementById('sd_checkpoint_hash'); - const sd_checkpoint_hash = opts.sd_checkpoint_hash || ''; - const shorthash = sd_checkpoint_hash.substring(0, 10); - - if (elem && elem.textContent !== shorthash) { - elem.textContent = shorthash; - elem.title = sd_checkpoint_hash; - elem.href = `https://google.com/search?q=${sd_checkpoint_hash}`; - } -}); - -onOptionsChanged(() => { - const setting_elems = gradioApp().querySelectorAll('#settings [id^="setting_"]'); - setting_elems.forEach((elem) => { - const setting_name = elem.id.replace('setting_', ''); - markIfModified(setting_name, opts[setting_name]); - }); -}); - -onUiLoaded(() => { - const tab_nav_element = gradioApp().querySelector('#settings > .tab-nav'); - const tab_nav_buttons = gradioApp().querySelectorAll('#settings > .tab-nav > button'); - const tab_elements = gradioApp().querySelectorAll('#settings > div:not(.tab-nav)'); - - // HACK Add mutation observer to keep gradio from closing setting tabs when showing all pages - const observer = new MutationObserver((mutations) => { - const show_all_pages_dummy = gradioApp().getElementById('settings_show_all_pages'); - if (show_all_pages_dummy.style.display === 'none') { return; } - function mutation_on_style(mut) { - return mut.type === 'attributes' && mut.attributeName === 'style'; - } - if (mutations.some(mutation_on_style)) { - showAllSettings(); - } - }); - - // Add a wrapper for the tab content (everything but the tab nav) - const tab_content_wrapper = document.createElement('div'); - tab_content_wrapper.className = 'tab-content'; - tab_nav_element.parentElement.insertBefore(tab_content_wrapper, tab_nav_element.nextSibling); - - tab_elements.forEach((elem, index) => { - // Move the modification indicator to the toplevel tab button - const tab_name = elem.id.replace('settings_', ''); - const indicator = gradioApp().getElementById(`modification_indicator_${tab_name}`); - tab_nav_element.insertBefore(indicator, tab_nav_buttons[index]); - - // Add the tab content to the wrapper - tab_content_wrapper.appendChild(elem); - - // Add the mutation observer to the tab element - observer.observe(elem, { attributes: true, attributeFilter: ['style'] }); - }); }); function update_txt2img_tokens(...args) { diff --git a/modules/processing.py b/modules/processing.py index 8142fd717..2b0df158c 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -812,11 +812,12 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if not p.disable_extra_networks and extra_network_data: extra_networks.deactivate(p, extra_network_data) + res = Processed( p, images_list=output_images, seed=p.all_seeds[0], - info=infotext(), + info=infotext() if 'infotext' in globals() else '', comments="\n".join(comments), subseed=p.all_subseeds[0], index_of_first_image=index_of_first_image, diff --git a/modules/sd_models.py b/modules/sd_models.py index a4b99268e..40af872c0 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -188,9 +188,12 @@ def update_model_hashes(): shared.log.info(f'Models list: full hash missing for {len(lst)} out of {len(checkpoints_list)} models') for ckpt in lst: ckpt.sha256 = hashes.sha256(ckpt.filename, f"checkpoint/{ckpt.name}") - ckpt.shorthash = ckpt.sha256[0:10] - txt.append(f'Calculated full hash: {ckpt.title} {ckpt.shorthash}') - txt.append(f'Updated full hashes for {len(lst)} out of {len(checkpoints_list)} models') + ckpt.shorthash = ckpt.sha256[0:10] if ckpt.sha256 is not None else None + if ckpt.sha256 is not None: + txt.append(f'Calculated full hash: {ckpt.title} {ckpt.shorthash}') + else: + txt.append(f'Skipped hash calculation: {ckpt.title}') + txt.append(f'Updated hashes for {len(lst)} out of {len(checkpoints_list)} models') txt = '
'.join(txt) return txt diff --git a/modules/shared.py b/modules/shared.py index b4320e7af..b2a27a2b7 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -702,9 +702,7 @@ class Options: def get_default(self, key): """returns the default value for the key""" data_label = self.data_labels.get(key) - if data_label is None: - return None - return data_label.default + return data_label.default if data_label is not None else None def save(self, filename): if cmd_opts.freeze: @@ -753,7 +751,7 @@ class Options: if info is None and k not in compatibility_opts: unknown_settings.append(k) if len(unknown_settings) > 0: - log.warning(f"Unknown settings: {unknown_settings}") + log.debug(f"Unknown settings: {unknown_settings}") def onchange(self, key, func, call=True): item = self.data_labels.get(key) diff --git a/modules/ui.py b/modules/ui.py index dfd3b9292..1f031abe8 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -910,11 +910,7 @@ def create_ui(startup_timer = None): if not is_quicksettings: dirtyable_setting = gr.Group(elem_classes="dirtyable", visible=(args or {}).get("visible", True)) dirtyable_setting.__enter__() - dirty_indicator = gr.Button( - "", - elem_classes="modification-indicator", - elem_id="modification_indicator_" + key - ) + dirty_indicator = gr.Button("", elem_classes="modification-indicator", elem_id="modification_indicator_" + key) if info.refresh is not None: if is_quicksettings: