From 8ec16d5b8c683c4fe65263df4ec04bbd7968092f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 28 Jul 2025 13:47:13 -0400 Subject: [PATCH] update modernui Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 9 +++++---- extensions-builtin/sdnext-modernui | 2 +- javascript/gallery.js | 5 +++-- modules/postprocess/yolo.py | 4 ++-- modules/ui_control.py | 6 ++---- modules/ui_control_helpers.py | 6 ++++-- modules/ui_postprocessing.py | 2 ++ modules/ui_sections.py | 12 ++++++------ 8 files changed, 25 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52ee42738..381b83bd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ # Change Log for SD.Next -## Update for 2025-07-27 +## Update for 2025-07-28 -### Highlights for 2025-07-27 +### Highlights for 2025-07-28 Feature highlights include: -- **ModernUI** layout redesign which should make it more user friendly and easier to navigate plus several new UI themes! +- **ModernUI** has quite some redesign which should make it more user friendly and easier to navigate plus several new UI themes! - New models [WanAI Wan 2.1](https://wan.video/) for text-to-image workflows, [FreePix F-Lite](https://huggingface.co/Freepik/F-Lite), [Bria 3.2](https://huggingface.co/briaai/BRIA-3.2), [bigASP 2.5](https://civitai.com/models/1789765?modelVersionId=2025412) - Redesigned [LTXVideo](https://vladmandic.github.io/sdnext-docs/Video) interface with support for general video models plus optimized [FramePack](https://vladmandic.github.io/sdnext-docs/FramePack) and [LTXVideo](https://vladmandic.github.io/sdnext-docs/LTX) support - Fully integrated nudity detection and optional censorship with [NudeNet](https://vladmandic.github.io/sdnext-docs/NudeNet) @@ -29,7 +29,7 @@ For details, see [ChangeLog](https://github.com/vladmandic/automatic/blob/master [ReadMe](https://github.com/vladmandic/automatic/blob/master/README.md) | [ChangeLog](https://github.com/vladmandic/automatic/blob/master/CHANGELOG.md) | [Docs](https://vladmandic.github.io/sdnext-docs/) | [WiKi](https://github.com/vladmandic/automatic/wiki) | [Discord](https://discord.com/invite/sd-next-federal-batch-inspectors-1101998836328697867) -### Details for 2025-07-27 +### Details for 2025-07-28 - **License** - SD.Next [license](https://github.com/vladmandic/sdnext/blob/dev/LICENSE.txt) switched from **aGPL-v3.0** to **Apache-v2.0** @@ -127,6 +127,7 @@ For details, see [ChangeLog](https://github.com/vladmandic/automatic/blob/master see [docs](https://vladmandic.github.io/sdnext-docs/Detailer/) for more information - **Detailer** add option to merge multiple results from each detailer model for example, hands model can result in two hands each being processed separately or both hands can be merged into one composite job + - **Control** auto-update width/height on image upload - **SDNQ** - use inference context during quantization - use static compile diff --git a/extensions-builtin/sdnext-modernui b/extensions-builtin/sdnext-modernui index 06a28bef2..157bdcac3 160000 --- a/extensions-builtin/sdnext-modernui +++ b/extensions-builtin/sdnext-modernui @@ -1 +1 @@ -Subproject commit 06a28bef26c28e0ac3c4a4864d7635a7228eef12 +Subproject commit 157bdcac33b3a480739a2fc475276d6c7b03791b diff --git a/javascript/gallery.js b/javascript/gallery.js index 53a6d251c..2de1d70a3 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -39,9 +39,10 @@ class GalleryFolder extends HTMLElement { const style = document.createElement('style'); // silly but necessasry since we're inside shadowdom if (window.opts.theme_type === 'Modern') { style.textContent = ` - .gallery-folder { cursor: pointer; padding: 8px 6px 8px 6px; background-color: var(--sd-button-normal-color); border-radius: var(--sd-border-radius); } + .gallery-folder { cursor: pointer; padding: 8px 6px 8px 6px; background-color: var(--sd-button-normal-color); border-radius: var(--sd-border-radius); text-align: left; min-width: 12em;} .gallery-folder:hover { background-color: var(--button-primary-background-fill-hover); } .gallery-folder-selected { background-color: var(--sd-button-selected-color); color: var(--sd-button-selected-text-color); } + .gallery-folder-icon { font-size: 1.2em; color: var(--sd-button-icon-color); margin-right: 1em; filter: drop-shadow(1px 1px 2px black); float: left; } `; } else { style.textContent = ` @@ -53,7 +54,7 @@ class GalleryFolder extends HTMLElement { this.shadow.appendChild(style); const div = document.createElement('div'); div.className = 'gallery-folder'; - div.textContent = `\uf44a ${this.name}`; + div.innerHTML = `\uf03e ${this.name}`; div.addEventListener('click', () => { for (const folder of el.folders.children) { if (folder.name === this.name) folder.shadow.children[1].classList.add('gallery-folder-selected'); diff --git a/modules/postprocess/yolo.py b/modules/postprocess/yolo.py index cb6592244..f1291479c 100644 --- a/modules/postprocess/yolo.py +++ b/modules/postprocess/yolo.py @@ -420,9 +420,9 @@ class YoloRestorer(Detailer): with gr.Row(): classes = gr.Textbox(label="Detailer classes", placeholder="Classes", elem_id=f"{tab}_detailer_classes") with gr.Row(): - prompt = gr.Textbox(label="Detailer prompt", value='', placeholder='Detailer prompt', lines=2, elem_id=f"{tab}_detailer_prompt") + prompt = gr.Textbox(label="Detailer prompt", value='', placeholder='detailer prompt or leave empty to use main prompt', lines=2, elem_id=f"{tab}_detailer_prompt") with gr.Row(): - negative = gr.Textbox(label="Detailer negative prompt", value='', placeholder='Detailer negative prompt', lines=2, elem_id=f"{tab}_detailer_negative") + negative = gr.Textbox(label="Detailer negative prompt", value='', placeholder='detailer prompt or leave empty to use main prompt', lines=2, elem_id=f"{tab}_detailer_negative") with gr.Row(): steps = gr.Slider(label="Detailer steps", elem_id=f"{tab}_detailer_steps", value=10, minimum=0, maximum=99, step=1) strength = gr.Slider(label="Detailer strength", elem_id=f"{tab}_detailer_strength", value=0.3, minimum=0, maximum=1, step=0.01) diff --git a/modules/ui_control.py b/modules/ui_control.py index 779255579..52c6e9800 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -554,13 +554,11 @@ def create_ui(_blocks: gr.Blocks=None): btn_negative_counter.click(fn=call_queue.wrap_queued_call(ui_common.update_token_counter), inputs=[negative], outputs=[negative_counter]) btn_interrogate.click(fn=helpers.interrogate, inputs=[], outputs=[prompt]) - select_fields = [input_mode, input_image, init_image, input_type, input_resize, input_inpaint, input_video, input_batch, input_folder] - select_output = [output_tabs, preview_process, result_txt] select_dict = dict( fn=helpers.select_input, _js="controlInputMode", - inputs=select_fields, - outputs=select_output, + inputs=[input_mode, input_image, init_image, input_type, input_resize, input_inpaint, input_video, input_batch, input_folder], + outputs=[output_tabs, preview_process, result_txt, width_before, height_before], show_progress=True, queue=False, ) diff --git a/modules/ui_control_helpers.py b/modules/ui_control_helpers.py index bd3f4cb75..a04512863 100644 --- a/modules/ui_control_helpers.py +++ b/modules/ui_control_helpers.py @@ -91,11 +91,12 @@ def select_input(input_mode, input_image, init_image, init_type, input_resize, i selected_input = input_folder else: selected_input = None + size = [gr.update(), gr.update()] if selected_input is None: input_source = None busy = False # debug('Control input: none') - return [gr.Tabs.update(), None, ''] + return [gr.Tabs.update(), None, ''] + size input_type = type(selected_input) input_mask = None status = 'Control input | Unknown' @@ -108,6 +109,7 @@ def select_input(input_mode, input_image, init_image, init_type, input_resize, i input_source = [selected_input] input_type = 'PIL.Image' status = f'Control input | Image | Size {selected_input.width}x{selected_input.height} | Mode {selected_input.mode}' + size = [gr.update(value=selected_input.width), gr.update(value=selected_input.height)] res = [gr.Tabs.update(selected='out-gallery'), input_mask, status] elif isinstance(selected_input, dict): # inpaint -> dict image+mask input_mask = selected_input['mask'] @@ -145,7 +147,7 @@ def select_input(input_mode, input_image, init_image, init_type, input_resize, i input_init = [init_image] debug_log(f'Control select input: type={input_type} source={input_source} init={input_init} mask={input_mask} mode={input_mode}') busy = False - return res + return res + size def copy_input(mode_from, mode_to, input_image, input_resize, input_inpaint): diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index a84b30276..24f787259 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -41,6 +41,8 @@ def create_ui(): interrupt.click(fn=lambda: shared.state.interrupt(), inputs=[], outputs=[]) skip = gr.Button('Skip', elem_id=f"{id_part}_skip", variant='secondary') skip.click(fn=lambda: shared.state.skip(), inputs=[], outputs=[]) + pause = gr.Button('Pause', elem_id=f"{id_part}_pause") + pause.click(fn=lambda: shared.state.pause(), _js='checkPaused', inputs=[], outputs=[]) result_images, generation_info, html_info, html_info_formatted, html_log = ui_common.create_output_panel("extras") gr.HTML('File metadata') exif_info = gr.HTML(elem_id="pnginfo_html_info") diff --git a/modules/ui_sections.py b/modules/ui_sections.py index b356d4fde..f6612f6b2 100644 --- a/modules/ui_sections.py +++ b/modules/ui_sections.py @@ -323,15 +323,15 @@ def create_hires_inputs(tab): with gr.Row(elem_id=f"{tab}_refiner_row1"): refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Refiner start', value=0.0, elem_id=f"{tab}_refiner_start") refiner_steps = gr.Slider(minimum=0, maximum=99, step=1, label="Refiner steps", elem_id=f"{tab}_refiner_steps", value=20) - refiner_prompt = gr.Textbox(value='', lines=2, label='Refine prompt', elem_id=f"{tab}_refiner_prompt", elem_classes=["prompt"]) - refiner_negative = gr.Textbox(value='', lines=2, label='Refine negative prompt', elem_id=f"{tab}_refiner_neg_prompt", elem_classes=["prompt"]) + refiner_prompt = gr.Textbox(value='', lines=2, label='Refine prompt', elem_id=f"{tab}_refiner_prompt", elem_classes=["prompt"], placeholder="refine prompt or leave empty to use main prompt") + refiner_negative = gr.Textbox(value='', lines=2, label='Refine negative prompt', elem_id=f"{tab}_refiner_neg_prompt", elem_classes=["prompt"], placeholder="refine negative prompt or leave empty to use main prompt") return enable_hr, hr_sampler_index, denoising_strength, hr_resize_mode, hr_resize_context, hr_upscaler, hr_force, hr_second_pass_steps, hr_scale, hr_resize_x, hr_resize_y, refiner_steps, refiner_start, refiner_prompt, refiner_negative def create_resize_inputs(tab, images, accordion=True, latent=False, non_zero=True, prefix=''): dummy_component = gr.Number(visible=False, value=0) if len(prefix) > 0 and not prefix.startswith(' '): - prefix = f' {prefix}' + prefix = f' {prefix}' if prefix != 'before' else '' with gr.Accordion(open=False, label="Resize", elem_classes=["small-accordion"], elem_id=f"{tab}_resize_group") if accordion else gr.Group(): with gr.Row(): available_upscalers = [x.name for x in shared.sd_upscalers] @@ -357,8 +357,8 @@ def create_resize_inputs(tab, images, accordion=True, latent=False, non_zero=Tru with gr.Row(elem_id=f"{tab}_resize_row_fixed"): with gr.Column(elem_id=f"{tab}_column_fixed1", scale=6): suffix = '_resize' if tab != 'img2img' else '' - width = gr.Slider(minimum=64 if non_zero else 0, maximum=8192, step=8, label=f"Width {prefix}" if non_zero else "Resize width", value=1024 if non_zero else 0, elem_id=f"{tab}{suffix}_width") - height = gr.Slider(minimum=64 if non_zero else 0, maximum=8192, step=8, label=f"Height {prefix}" if non_zero else "Resize height", value=1024 if non_zero else 0, elem_id=f"{tab}{suffix}_height") + width = gr.Slider(minimum=64 if non_zero else 0, maximum=8192, step=8, label=f"Width{prefix}" if non_zero else "Resize width", value=1024 if non_zero else 0, elem_id=f"{tab}{suffix}_width") + height = gr.Slider(minimum=64 if non_zero else 0, maximum=8192, step=8, label=f"Height{prefix}" if non_zero else "Resize height", value=1024 if non_zero else 0, elem_id=f"{tab}{suffix}_height") with gr.Column(elem_id=f"{tab}_column_fixed2", scale=1): ar_list = ['AR'] + [x.strip() for x in shared.opts.aspect_ratios.split(',') if x.strip() != ''] ar_dropdown = gr.Dropdown(show_label=False, interactive=True, choices=ar_list, value=ar_list[0], elem_id=f"{tab}_resize_ar", elem_classes=["ar-dropdown"]) @@ -370,7 +370,7 @@ def create_resize_inputs(tab, images, accordion=True, latent=False, non_zero=Tru el = tab.split('_')[0] detect_image_size_btn.click(fn=lambda w, h, _: (w or gr.update(), h or gr.update()), _js=f'currentImageResolution{el}', inputs=[dummy_component, dummy_component, dummy_component], outputs=[width, height], show_progress=False) with gr.Tab(label="Scale", id=1, elem_id=f"{tab}_scale_tab_scale") as tab_scale_by: - scale_by = gr.Slider(minimum=0.05, maximum=8.0, step=0.05, label=f"Scale {prefix}" if non_zero else "Resize scale", value=1.0, elem_id=f"{tab}_scale") + scale_by = gr.Slider(minimum=0.05, maximum=8.0, step=0.05, label=f"Scale{prefix}" if non_zero else "Resize scale", value=1.0, elem_id=f"{tab}_scale") if images is not None: for component in images: component.change(fn=lambda: None, _js="updateImg2imgResizeToTextAfterChangingImage", inputs=[], outputs=[], show_progress=False)