From bae986e90b276c92d140d32dd22db44fdacfa795 Mon Sep 17 00:00:00 2001 From: Yifan Chen Date: Sat, 12 Sep 2026 00:08:08 -0700 Subject: [PATCH] address selection filter review feedback --- modules/ui_choices.py | 17 ----------------- modules/ui_common.py | 15 +++++++++++++++ modules/ui_definitions.py | 10 ++++------ modules/ui_sections.py | 15 +++++++-------- test/test-ui-choice-filters.py | 7 ++++++- 5 files changed, 32 insertions(+), 32 deletions(-) delete mode 100644 modules/ui_choices.py diff --git a/modules/ui_choices.py b/modules/ui_choices.py deleted file mode 100644 index f5c59a986..000000000 --- a/modules/ui_choices.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Helpers for filtering display-only UI choice lists.""" - - -def filter_ui_choices(choices: list[str], preferences: list[str] | None = None, selected: str | None = None) -> tuple[list[str], bool]: - """Return choices selected for display without changing the underlying catalog. - - Empty or stale preferences leave the complete list visible. A current value is - always retained so loading a saved workflow cannot silently replace it. - """ - available = list(dict.fromkeys(choices)) - preferred = set(preferences or []) - filtered = [choice for choice in available if choice in preferred] - if not filtered: - return available, False - if selected in available and selected not in filtered: - filtered.append(selected) - return filtered, len(filtered) < len(available) diff --git a/modules/ui_common.py b/modules/ui_common.py index ad2612868..000194773 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -18,6 +18,21 @@ debug('Trace: PASTE') warn_once_set = WeakSet() +def filter_ui_choices(choices: list[str], preferences: list[str] | None = None, selected: str | None = None) -> tuple[list[str], bool]: + """Return display choices while preserving the current selection.""" + available = list(dict.fromkeys(choices)) + preferred = set(preferences or []) + filtered = [choice for choice in available if choice in preferred] + if not filtered: + return available, False + if selected in available and selected not in filtered: + filtered.append(selected) + if 'None' in filtered: + filtered.remove('None') + filtered.insert(0, 'None') + return filtered, len(filtered) < len(available) + + def gr_show(visible=True): return {"visible": visible, "__type__": "update"} diff --git a/modules/ui_definitions.py b/modules/ui_definitions.py index 865e55f8e..ed06e2fac 100644 --- a/modules/ui_definitions.py +++ b/modules/ui_definitions.py @@ -596,6 +596,10 @@ def create_settings(cmd_opts): "quicksetting_sep_images": OptionInfo("

Quicksettings

", "", gr.HTML), "quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", gr.Dropdown, lambda: {"multiselect":True, "choices": list(options_templates.keys())}), + "selections_sep_ui": OptionInfo("

Selections

", "", gr.HTML), + "show_samplers": OptionInfo([], "Show samplers in user interface", gr.Dropdown, lambda: {"multiselect": True, "choices": [x.name for x in list_samplers()]}, refresh=list_samplers), + "show_upscalers": OptionInfo([], "Show upscalers in user interface", gr.Dropdown, lambda: {"multiselect": True, "choices": list_upscalers()}, refresh=refresh_upscalers), + "server_sep_ui": OptionInfo("

Startup & Server Options

", "", gr.HTML), "autolaunch": OptionInfo(False, "Autolaunch browser upon startup"), "motd": OptionInfo(False, "Show MOTD"), @@ -774,12 +778,6 @@ def create_settings(cmd_opts): "disable_all_extensions": OptionInfo("none", "Disable all extensions", gr.Radio, {"choices": ["none", "user", "all"]}), })) - # --- Sampler Settings --- - options_templates.update(options_section(('sampler', "Sampler Settings"), { - "show_samplers": OptionInfo([], "Show samplers in user interface", gr.CheckboxGroup, lambda: {"choices": [x.name for x in list_samplers()]}, refresh=list_samplers), - "show_upscalers": OptionInfo([], "Show upscalers in user interface", gr.CheckboxGroup, lambda: {"choices": list_upscalers()}, refresh=refresh_upscalers), - })) - # --- Hidden Options --- options_templates.update( options_section( diff --git a/modules/ui_sections.py b/modules/ui_sections.py index 4212ffce7..19ca8c127 100644 --- a/modules/ui_sections.py +++ b/modules/ui_sections.py @@ -2,7 +2,6 @@ import gradio as gr from modules import shared, modelloader, ui_symbols, ui_common, sd_samplers from modules.logger import log from modules.ui_components import ToolButton -from modules.ui_choices import filter_ui_choices from modules.caption import caption @@ -220,7 +219,7 @@ def sampler_choices(choices=None, selected='Default', same_as_primary=False): sd_samplers.set_samplers() choices = [sampler for sampler in sd_samplers.samplers if sampler.name != 'Same as primary'] names = [choice.name if hasattr(choice, 'name') else choice for choice in choices] - visible, filtered = filter_ui_choices(names, shared.opts.show_samplers, selected) + visible, filtered = ui_common.filter_ui_choices(names, shared.opts.show_samplers, selected) if same_as_primary: visible.insert(0, 'Same as primary') value = selected if selected in visible else visible[0] @@ -229,14 +228,14 @@ def sampler_choices(choices=None, selected='Default', same_as_primary=False): def upscaler_choices(choices, selected=None): """Build display-only upscaler choices without changing available upscalers.""" - return filter_ui_choices(choices, shared.opts.show_upscalers, selected) + return ui_common.filter_ui_choices(choices, shared.opts.show_upscalers, selected) def create_filter_indicator(tabname, kind, filtered): if not filtered: return None - indicator = gr.Button(value=f'{kind} list filtered', elem_id=f'{tabname}_{kind.lower()}_filter_indicator', elem_classes=['filter-indicator']) - indicator.click(fn=None, _js="() => openSettingsSection('sampler')", inputs=[], outputs=[], show_progress='hidden') + indicator = ToolButton(value=ui_symbols.info, tooltip=f'{kind} list filtered', elem_id=f'{tabname}_{kind.lower()}_filter_indicator', elem_classes=['filter-indicator']) + indicator.click(fn=None, _js="() => openSettingsSection('ui')", inputs=[], outputs=[], show_progress='hidden') return indicator @@ -247,8 +246,8 @@ def create_sampler_and_steps_selection(choices, tabname, default_steps:int=20): dropdown_choices, default_value, filtered = sampler_choices(choices) with gr.Row(elem_id=f"{tabname}_sampler_row", elem_classes=['flex-break', 'flexbox']): steps = gr.Slider(minimum=1, maximum=100, step=1, label="Steps", elem_id=f"{tabname}_steps", value=default_steps) - create_filter_indicator(tabname, 'Sampler', filtered) sampler_index = gr.Dropdown(label='Sampling method', elem_id=f"{tabname}_sampling", choices=dropdown_choices, value=default_value, type="value") + create_filter_indicator(tabname, 'Sampler', filtered) return steps, sampler_index @@ -372,8 +371,8 @@ def create_hires_inputs(tab): hr_force = gr.Checkbox(label='Force HiRes', value=False, elem_id=f"{tab}_hr_force") with gr.Row(elem_id=f"{tab}_hires_fix_row2"): dropdown_choices, _default_value, filtered = sampler_choices(selected='Same as primary', same_as_primary=True) - create_filter_indicator(tab, 'Sampler', filtered) hr_sampler_index = gr.Dropdown(label='Refine sampler', elem_id=f"{tab}_sampling_alt", choices=dropdown_choices, value='Same as primary', type="value") + create_filter_indicator(tab, 'Sampler', filtered) with gr.Row(elem_id=f"{tab}_hires_row2"): hr_second_pass_steps = gr.Slider(minimum=0, maximum=99, step=1, label='HiRes steps', elem_id=f"{tab}_steps_alt", value=20) denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Strength', value=0.3, elem_id=f"{tab}_denoising_strength") @@ -398,8 +397,8 @@ def create_resize_inputs(tab, images, accordion=True, latent=False, non_zero=Tru available_upscalers = [x for x in available_upscalers if not x.lower().startswith('latent')] available_upscalers, filtered = upscaler_choices(available_upscalers, available_upscalers[0]) resize_mode = gr.Dropdown(label=f"Mode{prefix}" if non_zero else "Resize mode", elem_id=f"{tab}_resize_mode", choices=shared.resize_modes, type="index", value='Fixed') - create_filter_indicator(tab, 'Upscaler', filtered) resize_name = gr.Dropdown(label=f"Method{prefix}" if non_zero else "Resize method", elem_id=f"{tab}_resize_name", choices=available_upscalers, value=available_upscalers[0], visible=True) + create_filter_indicator(tab, 'Upscaler', filtered) resize_context_choices = ["Add with forward", "Remove with forward", "Add with backward", "Remove with backward"] resize_context = gr.Dropdown(label=f"Context{prefix}", elem_id=f"{tab}_resize_context", choices=resize_context_choices, value=resize_context_choices[0], visible=False) diff --git a/test/test-ui-choice-filters.py b/test/test-ui-choice-filters.py index 7bb975a13..fa5600aff 100644 --- a/test/test-ui-choice-filters.py +++ b/test/test-ui-choice-filters.py @@ -5,7 +5,7 @@ import sys import unittest sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1])) -from modules.ui_choices import filter_ui_choices +from modules.ui_common import filter_ui_choices class TestUiChoiceFilters(unittest.TestCase): @@ -32,6 +32,11 @@ class TestUiChoiceFilters(unittest.TestCase): self.assertEqual(visible, ['Euler', 'DPM++ 2M']) self.assertTrue(filtered) + def test_none_remains_the_first_choice(self): + visible, filtered = filter_ui_choices(['Lanczos', 'None', 'Nearest'], ['Lanczos'], selected='None') + self.assertEqual(visible, ['None', 'Lanczos']) + self.assertTrue(filtered) + if __name__ == '__main__': unittest.main()