diff --git a/modules/scripts.py b/modules/scripts.py index dca8b3f64..f378744ee 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -35,6 +35,7 @@ class Script: group = None infotext_fields = None paste_field_names = None + section = None def title(self): """this function should return the title of the script. This is what will be displayed in the dropdown menu.""" @@ -332,7 +333,6 @@ class ScriptRunner: self.paste_field_names.clear() self.script_load_ctr = 0 self.is_img2img = is_img2img - self.scripts.clear() self.alwayson_scripts.clear() self.selectable_scripts.clear() @@ -355,6 +355,22 @@ class ScriptRunner: except Exception as e: log.error(f'Script initialize: {path} {e}') + def setup_ui_for_section(self, section, scriptlist=None): + if scriptlist is None: + scriptlist = self.alwayson_scripts + for script in scriptlist: + if script.alwayson and script.section != section: + continue + if script.create_group: + with gr.Group(visible=script.alwayson) as group: + self.create_script_ui(script) + script.group = group + else: + self.create_script_ui(script) + + def prepare_ui(self): + self.inputs = [None] + def setup_ui(self): import modules.api.models as api_models self.titles = [wrap_call(script.title, script.filename, "title") or f"{script.filename} [error]" for script in self.selectable_scripts] diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index 103d7a93c..d538a0da9 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -34,6 +34,11 @@ def find_sampler_config(name): return config +def visible_sampler_names(): + samplers = [x for x in all_samplers if x.name in shared.opts.show_samplers] if len(shared.opts.show_samplers) > 0 else all_samplers + return samplers + + def create_sampler(name, model): if name == 'Default' and hasattr(model, 'scheduler'): config = {k: v for k, v in model.scheduler.config.items() if not k.startswith('_')} @@ -64,7 +69,7 @@ def create_sampler(name, model): def set_samplers(): global samplers # pylint: disable=global-statement global samplers_for_img2img # pylint: disable=global-statement - samplers = [x for x in all_samplers if x.name in shared.opts.show_samplers] if len(shared.opts.show_samplers) > 0 else all_samplers + samplers = visible_sampler_names() samplers_for_img2img = [x for x in samplers if x.name != "PLMS"] samplers_map.clear() for sampler in all_samplers: diff --git a/modules/shared.py b/modules/shared.py index 9c0b8058e..f4ebea262 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -425,6 +425,7 @@ options_templates.update(options_section(('ui', "User Interface"), { "gradio_theme": OptionInfo("black-teal", "UI theme", gr.Dropdown, lambda: {"choices": theme.list_themes()}, refresh=theme.refresh_themes), "theme_style": OptionInfo("Auto", "Theme mode", gr.Radio, {"choices": ["Auto", "Dark", "Light"]}), "tooltips": OptionInfo("UI Tooltips", "UI tooltips", gr.Radio, {"choices": ["None", "Browser default", "UI tooltips"], "visible": False}), + "gallery_height": OptionInfo("", "Gallery height", gr.Textbox), "compact_view": OptionInfo(False, "Compact view"), "return_grid": OptionInfo(True, "Show grid in results"), "return_mask": OptionInfo(False, "For inpainting, include the greyscale mask in results"), diff --git a/modules/ui.py b/modules/ui.py index b7edcdaac..3477a2802 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -35,6 +35,15 @@ mimetypes.init() mimetypes.add_type('application/javascript', '.js') log = modules.shared.log ui_system_tabs = None +switch_values_symbol = symbols.switch +detect_image_size_symbol = symbols.detect +paste_symbol = symbols.paste +clear_prompt_symbol = symbols.clear +restore_progress_symbol = symbols.apply +folder_symbol = symbols.folder +extra_networks_symbol = symbols.networks +apply_style_symbol = symbols.apply +save_style_symbol = symbols.save if not cmd_opts.share and not cmd_opts.listen: @@ -353,7 +362,7 @@ def get_value_for_setting(key): def ordered_ui_categories(): - return [] # dummy + return ['dimensions', 'sampler', 'seed', 'denoising', 'cfg', 'checkboxes', 'accordions', 'override_settings', 'scripts'] # TODO: a1111 compatibility item, not implemented def create_override_settings_dropdown(tabname, row): # pylint: disable=unused-argument diff --git a/modules/ui_common.py b/modules/ui_common.py index eee96b085..ad00dcb63 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -12,6 +12,9 @@ import modules.images import modules.script_callbacks +folder_symbol = symbols.folder + + def update_generation_info(generation_info, html_info, img_index): try: generation_info = json.loads(generation_info) @@ -178,7 +181,7 @@ def create_output_panel(tabname): with gr.Column(variant='panel', elem_id=f"{tabname}_results"): with gr.Group(elem_id=f"{tabname}_gallery_container"): # columns are for <576px, <768px, <992px, <1200px, <1400px, >1400px - result_gallery = gr.Gallery(value=[], label='Output', show_label=False, show_download_button=True, allow_preview=True, elem_id=f"{tabname}_gallery", container=False, preview=True, columns=5, object_fit='scale-down') + result_gallery = gr.Gallery(value=[], label='Output', show_label=False, show_download_button=True, allow_preview=True, elem_id=f"{tabname}_gallery", container=False, preview=True, columns=5, object_fit='scale-down', height=shared.opts.gallery_height or None) with gr.Column(elem_id=f"{tabname}_footer", elem_classes="gallery_footer"): dummy_component = gr.Label(visible=False) diff --git a/modules/ui_components.py b/modules/ui_components.py index 6d2186b92..0e5862765 100644 --- a/modules/ui_components.py +++ b/modules/ui_components.py @@ -71,3 +71,60 @@ class DropdownEditable(FormComponent, gr.Dropdown): def get_block_name(self): return "dropdown" + + +class InputAccordion(gr.Checkbox): + """A gr.Accordion that can be used as an input - returns True if open, False if closed. + Actaully just a hidden checkbox, but creates an accordion that follows and is followed by the state of the checkbox. + """ + global_index = 0 + + def __init__(self, value, **kwargs): + self.accordion_id = kwargs.get('elem_id') + if self.accordion_id is None: + self.accordion_id = f"input-accordion-{InputAccordion.global_index}" + InputAccordion.global_index += 1 + kwargs_checkbox = {**kwargs, "elem_id": f"{self.accordion_id}-checkbox", "visible": False} + super().__init__(value, **kwargs_checkbox) + self.change(fn=None, _js='function(checked){ inputAccordionChecked("' + self.accordion_id + '", checked); }', inputs=[self]) + kwargs_accordion = { + **kwargs, + "elem_id": self.accordion_id, + "label": kwargs.get('label', 'Accordion'), + "elem_classes": ['input-accordion'], + "open": value, + } + self.accordion = gr.Accordion(**kwargs_accordion) + + def extra(self): + """Allows you to put something into the label of the accordion. + Use it like this: + ``` + with InputAccordion(False, label="Accordion") as acc: + with acc.extra(): + FormHTML(value="hello", min_width=0) + ... + ``` + """ + return gr.Column(elem_id=self.accordion_id + '-extra', elem_classes='input-accordion-extra', min_width=0) + + def __enter__(self): + self.accordion.__enter__() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.accordion.__exit__(exc_type, exc_val, exc_tb) + + def get_block_name(self): + return "checkbox" + + +class ResizeHandleRow(gr.Row): + """Same as gr.Row but fits inside gradio forms""" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.elem_classes.append("resize-handle-row") + + def get_block_name(self): + return "row" diff --git a/modules/ui_prompt_styles.py b/modules/ui_prompt_styles.py new file mode 100644 index 000000000..9f8a43c7c --- /dev/null +++ b/modules/ui_prompt_styles.py @@ -0,0 +1,106 @@ +# TODO: a1111 compatibility item, not used + +import gradio as gr + +from modules import shared, ui_common, ui_components, styles + +styles_edit_symbol = '\U0001f58c\uFE0F' # 🖌️ +styles_materialize_symbol = '\U0001f4cb' # 📋 + + +def select_style(name): + style = shared.prompt_styles.styles.get(name) + existing = style is not None + empty = not name + prompt = style.prompt if style else gr.update() + negative_prompt = style.negative_prompt if style else gr.update() + return prompt, negative_prompt, gr.update(visible=existing), gr.update(visible=not empty) + + +def save_style(name, prompt, negative_prompt): + if not name: + return gr.update(visible=False) + style = styles.PromptStyle(name, prompt, negative_prompt) + shared.prompt_styles.styles[style.name] = style + shared.prompt_styles.save_styles(shared.styles_filename) + return gr.update(visible=True) + + +def delete_style(name): + if name == "": + return + shared.prompt_styles.styles.pop(name, None) + shared.prompt_styles.save_styles(shared.styles_filename) + return '', '', '' + + +def materialize_styles(prompt, negative_prompt, styles): + prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, styles) + negative_prompt = shared.prompt_styles.apply_negative_styles_to_prompt(negative_prompt, styles) + return [gr.Textbox.update(value=prompt), gr.Textbox.update(value=negative_prompt), gr.Dropdown.update(value=[])] + + +def refresh_styles(): + return gr.update(choices=list(shared.prompt_styles.styles)), gr.update(choices=list(shared.prompt_styles.styles)) + + +class UiPromptStyles: + def __init__(self, tabname, main_ui_prompt, main_ui_negative_prompt): + self.dropdown = gr.Dropdown(label="Styles", elem_id=f"{tabname}_styles", choices=[style.name for style in shared.prompt_styles.styles.values()], value=[], multiselect=True) + + """ + def __init__(self, tabname, main_ui_prompt, main_ui_negative_prompt): + self.tabname = tabname + + with gr.Row(elem_id=f"{tabname}_styles_row"): + self.dropdown = gr.Dropdown(label="Styles", show_label=False, elem_id=f"{tabname}_styles", choices=list(shared.prompt_styles.styles), value=[], multiselect=True, tooltip="Styles") + edit_button = ui_components.ToolButton(value=styles_edit_symbol, elem_id=f"{tabname}_styles_edit_button", tooltip="Edit styles") + + with gr.Box(elem_id=f"{tabname}_styles_dialog", elem_classes="popup-dialog") as styles_dialog: + with gr.Row(): + self.selection = gr.Dropdown(label="Styles", elem_id=f"{tabname}_styles_edit_select", choices=list(shared.prompt_styles.styles), value=[], allow_custom_value=True, info="Styles allow you to add custom text to prompt. Use the {prompt} token in style text, and it will be replaced with user's prompt when applying style. Otherwise, style's text will be added to the end of the prompt.") + ui_common.create_refresh_button([self.dropdown, self.selection], shared.prompt_styles.reload, lambda: {"choices": list(shared.prompt_styles.styles)}, f"refresh_{tabname}_styles") + self.materialize = ui_components.ToolButton(value=styles_materialize_symbol, elem_id=f"{tabname}_style_apply", tooltip="Apply all selected styles from the style selction dropdown in main UI to the prompt.") + + with gr.Row(): + self.prompt = gr.Textbox(label="Prompt", show_label=True, elem_id=f"{tabname}_edit_style_prompt", lines=3) + + with gr.Row(): + self.neg_prompt = gr.Textbox(label="Negative prompt", show_label=True, elem_id=f"{tabname}_edit_style_neg_prompt", lines=3) + + with gr.Row(): + self.save = gr.Button('Save', variant='primary', elem_id=f'{tabname}_edit_style_save', visible=False) + self.delete = gr.Button('Delete', variant='primary', elem_id=f'{tabname}_edit_style_delete', visible=False) + self.close = gr.Button('Close', variant='secondary', elem_id=f'{tabname}_edit_style_close') + + self.selection.change( + fn=select_style, + inputs=[self.selection], + outputs=[self.prompt, self.neg_prompt, self.delete, self.save], + show_progress=False, + ) + + self.save.click( + fn=save_style, + inputs=[self.selection, self.prompt, self.neg_prompt], + outputs=[self.delete], + show_progress=False, + ).then(refresh_styles, outputs=[self.dropdown, self.selection], show_progress=False) + + self.delete.click( + fn=delete_style, + _js='function(name){ if(name == "") return ""; return confirm("Delete style " + name + "?") ? name : ""; }', + inputs=[self.selection], + outputs=[self.selection, self.prompt, self.neg_prompt], + show_progress=False, + ).then(refresh_styles, outputs=[self.dropdown, self.selection], show_progress=False) + + self.materialize.click( + fn=materialize_styles, + inputs=[main_ui_prompt, main_ui_negative_prompt, self.dropdown], + outputs=[main_ui_prompt, main_ui_negative_prompt, self.dropdown], + show_progress=False, + ).then(fn=None, _js="function(){update_"+tabname+"_tokens(); closePopup();}", show_progress=False) + + ui_common.setup_dialog(button_show=edit_button, dialog=styles_dialog, button_close=self.close) + """