diff --git a/extensions-builtin/multidiffusion-upscaler-for-automatic1111 b/extensions-builtin/multidiffusion-upscaler-for-automatic1111 index fbe34db70..eada8e510 160000 --- a/extensions-builtin/multidiffusion-upscaler-for-automatic1111 +++ b/extensions-builtin/multidiffusion-upscaler-for-automatic1111 @@ -1 +1 @@ -Subproject commit fbe34db704736e5745c3a4e855939508eef344ce +Subproject commit eada8e5101417f60850efa15f7454a826da6cf0d diff --git a/automatic.ico b/html/logo.ico similarity index 100% rename from automatic.ico rename to html/logo.ico diff --git a/automatic.png b/html/logo.png similarity index 100% rename from automatic.png rename to html/logo.png diff --git a/automatic.svg b/html/logo.svg similarity index 100% rename from automatic.svg rename to html/logo.svg diff --git a/javascript/notification.js b/javascript/notification.js index 517cb42c1..c9be9e3c3 100644 --- a/javascript/notification.js +++ b/javascript/notification.js @@ -12,7 +12,7 @@ onUiUpdate(function(){ const galleryPreviews = gradioApp().querySelectorAll('div[id^="tab_"][style*="display: block"] div[id$="_results"] .thumbnail-item > img'); if (!galleryPreviews) return; const headImg = galleryPreviews[0]?.src; - if (!headImg || headImg == lastHeadImg || headImg.endsWith('automatic.png')) return; + if (!headImg || headImg == lastHeadImg || headImg.endsWith('logo.png')) return; const audioNotification = gradioApp().querySelector('#audio_notification audio'); if (audioNotification) audioNotification.play(); lastHeadImg = headImg; diff --git a/script.js b/javascript/script.js similarity index 100% rename from script.js rename to javascript/script.js diff --git a/style.css b/javascript/style.css similarity index 93% rename from style.css rename to javascript/style.css index c0fe31146..dee5b3704 100644 --- a/style.css +++ b/javascript/style.css @@ -155,10 +155,37 @@ button.custom-button{ margin-left: -0.75em } -#txtimg_hr_finalres .resolution{ +#img2img_scale_resolution_preview.block{ + display: flex; + align-items: end; +} + +#txtimg_hr_finalres .resolution, #img2img_scale_resolution_preview .resolution{ font-weight: bold; } +div#extras_scale_to_tab div.form{ + flex-direction: row; +} + +#img2img_column_batch{ + align-self: end; + margin-bottom: 0.9em; +} + +#img2img_unused_scale_by_slider{ + visibility: hidden; + width: 0.5em; + max-width: 0.5em; + min-width: 0.5em; +} + +.extra-network-cards{ + height: 725px; + overflow: scroll; + resize: vertical; +} + .inactive{ opacity: 0.5; } diff --git a/javascript/ui.js b/javascript/ui.js index e5781767a..2b7cfaf91 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -321,6 +321,16 @@ function selectCheckpoint(name){ gradioApp().getElementById('change_checkpoint').click() } +function updateImg2imgResizeToTextAfterChangingImage(){ + // At the time this is called from gradio, the image has no yet been replaced. + // There may be a better solution, but this is simple and straightforward so I'm going with it. + setTimeout(function() { + gradioApp().getElementById('img2img_update_resize_to').click() + }, 500); + + return [] +} + function create_theme_element() { el = document.createElement('img'); el.id = 'theme-preview'; diff --git a/modules/shared.py b/modules/shared.py index 012c0af31..b375dc6d4 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -6,6 +6,7 @@ import datetime import urllib.request import gradio as gr import tqdm +import requests from modules import errors, ui_components, shared_items, cmd_args from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # pylint: disable=W0611 import modules.interrogate @@ -35,6 +36,7 @@ restricted_opts = { "outdir_grids", "outdir_txt2img_grids", "outdir_save", + "outdir_init_images" } ui_reorder_categories = [ @@ -301,6 +303,7 @@ options_templates.update(options_section(('saving-images', "Image options"), { "use_original_name_batch": OptionInfo(True, "Use original name for output filename during batch process in extras tab"), "use_upscaler_name_as_suffix": OptionInfo(True, "Use upscaler name as filename suffix in the extras tab"), "save_selected_only": OptionInfo(True, "When using 'Save' button, only save a single selected image"), + "save_init_img": OptionInfo(False, "Save init images when using image processing"), "save_to_dirs": OptionInfo(False, "Save images to a subdirectory"), "grid_save_to_dirs": OptionInfo(False, "Save grids to a subdirectory"), "use_save_to_dirs_for_ui": OptionInfo(False, "When using \"Save\" button, save images to a subdirectory"), @@ -317,6 +320,7 @@ options_templates.update(options_section(('saving-paths', "Image Paths"), { "outdir_txt2img_grids": OptionInfo("outputs/grids", 'Output directory for txt2img grids', component_args=hide_dirs), "outdir_img2img_grids": OptionInfo("outputs/grids", 'Output directory for img2img grids', component_args=hide_dirs), "outdir_save": OptionInfo("outputs/save", "Directory for saving images using the Save button", component_args=hide_dirs), + "outdir_init_images": OptionInfo("outputs/init-images", "Directory for saving init images when using img2img", component_args=hide_dirs), })) options_templates.update(options_section(('cuda', "CUDA Settings"), { @@ -339,10 +343,12 @@ options_templates.update(options_section(('cuda', "CUDA Settings"), { })) options_templates.update(options_section(('upscaling', "Upscaling"), { + "upscaler_for_img2img": OptionInfo("None", "Default upscaler for image resize operations", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers]}), + "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Real-ESRGAN available models", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}), "ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers (0 = no tiling)", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), "ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap in pixels for ESRGAN upscalers", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}), - "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Real-ESRGAN available models", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}), - "upscaler_for_img2img": OptionInfo("None", "Default upscaler for image resize operations", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers]}), + "SCUNET_tile": OptionInfo(256, "Tile size for SCUNET upscalers. 0 = no tiling.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}), + "SCUNET_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for SCUNET upscalers. Low values = visible seam.", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}), "use_old_hires_fix_width_height": OptionInfo(False, "Hires fix uses width & height to set final resolution rather than first pass"), "dont_fix_second_order_samplers_schedule": OptionInfo(False, "Do not fix prompt schedule for second order samplers"), })) @@ -403,6 +409,7 @@ options_templates.update(options_section(('ui', "User interface"), { "font": OptionInfo("", "Font for image grids that have text"), "keyedit_precision_attention": OptionInfo(0.1, "Ctrl+up/down precision when editing (attention:1.1)", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), "keyedit_precision_extra": OptionInfo(0.05, "Ctrl+up/down precision when editing ", gr.Slider, {"minimum": 0.01, "maximum": 0.2, "step": 0.001}), + "keyedit_delimiters": OptionInfo(".,\/!?%^*;:{}=`~()", "Ctrl+up/down word delimiters"), "quicksettings": OptionInfo("sd_model_checkpoint", "Quicksettings list"), "hidden_tabs": OptionInfo([], "Hidden UI tabs", ui_components.DropdownMulti, lambda: {"choices": [x for x in tab_names]}), "ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"), @@ -428,6 +435,7 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters" "eta_ddim": OptionInfo(0.0, "Noise multiplier for DDIM (eta)", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), "ddim_discretize": OptionInfo('uniform', "DDIM discretize img2img", gr.Radio, {"choices": ['uniform', 'quad']}), 's_churn': OptionInfo(0.0, "sigma churn", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + 's_min_uncond': OptionInfo(0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 4.0, "step": 0.01}), 's_tmin': OptionInfo(0.0, "sigma tmin", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), 'eta_noise_seed_delta': OptionInfo(0, "Noise seed delta (eta)", gr.Number, {"precision": 0}), diff --git a/modules/ui.py b/modules/ui.py index 6d8054a3c..b0ad69c2a 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -71,6 +71,9 @@ def send_gradio_gallery_to_image(x): def visit(x, func, path=""): if hasattr(x, 'children'): + if isinstance(x, gr.Tabs) and x.elem_id is not None: + # Tabs element can't have a label, have to use elem_id instead + func(f"{path}/Tabs@{x.elem_id}", x) for c in x.children: visit(c, func, path) elif x.label is not None: @@ -96,6 +99,14 @@ def calc_resolution_hires(enable, width, height, hr_scale, hr_resize_x, hr_resiz return f"resize: from {p.width}x{p.height} to {p.hr_resize_x or p.hr_upscale_to_x}x{p.hr_resize_y or p.hr_upscale_to_y}" +def resize_from_to_html(width, height, scale_by): + target_width = int(width * scale_by) + target_height = int(height * scale_by) + if not target_width or not target_height: + return "no image selected" + return f"resize: from {width}x{height} to {target_width}x{target_height}" + + def apply_styles(prompt, prompt_neg, styles): prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, styles) prompt_neg = shared.prompt_styles.apply_negative_styles_to_prompt(prompt_neg, styles) @@ -140,8 +151,8 @@ def create_seed_inputs(target_interface): with FormRow(elem_id=target_interface + '_seed_row', variant="compact"): seed = gr.Number(label='Seed', value=-1, elem_id=target_interface + '_seed') seed.style(container=False) - random_seed = ToolButton(random_symbol, elem_id=target_interface + '_random_seed') - reuse_seed = ToolButton(reuse_symbol, elem_id=target_interface + '_reuse_seed') + random_seed = ToolButton(random_symbol, elem_id=target_interface + '_random_seed', label='Random seed') + reuse_seed = ToolButton(reuse_symbol, elem_id=target_interface + '_reuse_seed', label='Reuse seed') seed_checkbox = gr.Checkbox(label='Extra', elem_id=target_interface + '_subseed_show', value=False, visible=False) # Ghost checkbox, so it still gets sent. For compatibility with extensions that call txt2img or img2img manually with FormRow(visible=True, elem_id=target_interface + '_subseed_row'): subseed = gr.Number(label='Variation seed', value=-1, elem_id=target_interface + '_subseed') @@ -343,7 +354,7 @@ def create_ui(): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="txt2img_width") height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="txt2img_height") with gr.Column(elem_id="txt2img_dimensions_row", scale=1, elem_classes="dimensions-tools"): - res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn") + res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn", label="Switch dims") with gr.Column(elem_id="txt2img_column_batch"): with FormRow(elem_id="txt2img_row_batch"): batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="txt2img_batch_count") @@ -536,6 +547,7 @@ def create_ui(): copy_image_buttons.append((button, name, elem)) with gr.Tabs(elem_id="mode_img2img"): + img2img_selected_tab = gr.State(0) # pylint: disable=abstract-class-instantiated with gr.TabItem('img2img', id='img2img', elem_id="img2img_img2img_tab") as tab_img2img: init_img = gr.Image(label="Image for img2img", elem_id="img2img_image", show_label=False, source="upload", interactive=True, type="pil", tool="editor", image_mode="RGBA").style(height=480) add_copy_image_controls('img2img', init_img) @@ -578,6 +590,12 @@ def create_ui(): img2img_batch_output_dir = gr.Textbox(label="Output directory", **shared.hide_dirs, elem_id="img2img_batch_output_dir") img2img_batch_inpaint_mask_dir = gr.Textbox(label="Inpaint batch mask directory (required for inpaint batch processing only)", **shared.hide_dirs, elem_id="img2img_batch_inpaint_mask_dir") + img2img_tabs = [tab_img2img, tab_sketch, tab_inpaint, tab_inpaint_color, tab_inpaint_upload, tab_batch] + img2img_image_inputs = [init_img, sketch, init_img_with_mask, inpaint_color_sketch] # pylint: disable=unused-variable + + for i, tab in enumerate(img2img_tabs): + tab.select(fn=lambda tabnum=i: tabnum, inputs=[], outputs=[img2img_selected_tab]) + def copy_image(img): if isinstance(img, dict) and 'image' in img: return img['image'] @@ -606,16 +624,49 @@ def create_ui(): elif category == "dimensions": with FormRow(): - with FormRow(elem_id="img2img_row_size"): - width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="img2img_width") - height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="img2img_height") + with gr.Column(elem_id="img2img_column_size", scale=4): + selected_scale_tab = gr.State(value=0) # pylint: disable=abstract-class-instantiated - with gr.Column(elem_id="img2img_dimensions_row", scale=1, elem_classes="dimensions-tools"): - res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn") + with gr.Tabs(): + with gr.Tab(label="Resize to") as tab_scale_to: + with FormRow(): + with gr.Column(elem_id="img2img_column_size", scale=4): + width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="img2img_width") + height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="img2img_height") + with gr.Column(elem_id="img2img_dimensions_row", scale=1, elem_classes="dimensions-tools"): + res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn") - with FormRow(elem_id="img2img_row_batch"): + with gr.Tab(label="Resize by") as tab_scale_by: + scale_by = gr.Slider(minimum=0.05, maximum=4.0, step=0.05, label="Scale", value=1.0, elem_id="img2img_scale") + + with FormRow(): + scale_by_html = FormHTML(resize_from_to_html(0, 0, 0.0), elem_id="img2img_scale_resolution_preview") + gr.Slider(label="Unused", elem_id="img2img_unused_scale_by_slider") + button_update_resize_to = gr.Button(visible=False, elem_id="img2img_update_resize_to") + + on_change_args = dict( + fn=resize_from_to_html, + _js="currentImg2imgSourceResolution", + inputs=[dummy_component, dummy_component, scale_by], + outputs=scale_by_html, + show_progress=False, + ) + + scale_by.release(**on_change_args) + button_update_resize_to.click(**on_change_args) + + # the code below is meant to update the resolution label after the image in the image selection UI has changed. + # as it is now the event keeps firing continuously for inpaint edits, which ruins the page with constant requests. + # I assume this must be a gradio bug and for now we'll just do it for non-inpaint inputs. + for component in [init_img, sketch]: + component.change(fn=lambda: None, _js="updateImg2imgResizeToTextAfterChangingImage", inputs=[], outputs=[], show_progress=False) + + tab_scale_to.select(fn=lambda: 0, inputs=[], outputs=[selected_scale_tab]) + tab_scale_by.select(fn=lambda: 1, inputs=[], outputs=[selected_scale_tab]) + + with gr.Column(elem_id="img2img_column_batch"): batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="img2img_batch_count") - batch_size = gr.Slider(minimum=1, maximum=32, step=1, label='Batch size', value=1, elem_id="img2img_batch_size") + batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size', value=1, elem_id="img2img_batch_size") elif category == "cfg": with FormGroup(): @@ -664,7 +715,7 @@ def create_ui(): def select_img2img_tab(tab): return gr.update(visible=tab in [2, 3, 4]), gr.update(visible=tab == 3), - for i, elem in enumerate([tab_img2img, tab_sketch, tab_inpaint, tab_inpaint_color, tab_inpaint_upload, tab_batch]): + for i, elem in enumerate(img2img_tabs): elem.select( fn=lambda tab=i: select_img2img_tab(tab), # pylint: disable=cell-var-from-loop inputs=[], @@ -716,10 +767,11 @@ def create_ui(): image_cfg_scale, denoising_strength, seed, - subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, - seed_checkbox, # seed_enable_extras + subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox, + selected_scale_tab, height, width, + scale_by, resize_mode, inpaint_full_res, inpaint_full_res_padding, @@ -859,8 +911,9 @@ def create_ui(): interp_method.change(fn=update_interp_description, inputs=[interp_method], outputs=[interp_description]) with FormRow(): - checkpoint_format = gr.Radio(choices=["ckpt", "safetensors"], value="ckpt", label="Checkpoint format", elem_id="modelmerger_checkpoint_format") + checkpoint_format = gr.Radio(choices=["ckpt", "safetensors"], value="safetensors", label="Checkpoint format", elem_id="modelmerger_checkpoint_format") save_as_half = gr.Checkbox(value=False, label="Save as float16", elem_id="modelmerger_save_as_half") + save_metadata = gr.Checkbox(value=True, label="Save metadata (.safetensors only)", elem_id="modelmerger_save_metadata") with FormRow(): with gr.Column(): @@ -881,7 +934,7 @@ def create_ui(): with gr.Group(elem_id="modelmerger_results_panel"): modelmerger_result = gr.HTML(elem_id="modelmerger_result", show_label=False) - with gr.Tab(label="Create embedding"): + with gr.Tab(label="Create embedding", id="create_embedding"): new_embedding_name = gr.Textbox(label="Name", elem_id="train_new_embedding_name") initialization_text = gr.Textbox(label="Initialization text", value="*", elem_id="train_initialization_text") nvpt = gr.Slider(label="Number of vectors per token", minimum=1, maximum=75, step=1, value=1, elem_id="train_nvpt") @@ -894,7 +947,7 @@ def create_ui(): with gr.Column(): create_embedding = gr.Button(value="Create embedding", variant='primary', elem_id="train_create_embedding") - with gr.Tab(label="Create hypernetwork"): + with gr.Tab(label="Create hypernetwork", id="create_hypernetwork"): new_hypernetwork_name = gr.Textbox(label="Name", elem_id="train_new_hypernetwork_name") new_hypernetwork_sizes = gr.CheckboxGroup(label="Modules", value=["768", "320", "640", "1280"], choices=["768", "1024", "320", "640", "1280"], elem_id="train_new_hypernetwork_sizes") new_hypernetwork_layer_structure = gr.Textbox("1, 2, 1", label="Enter hypernetwork layer structure", placeholder="1st and last digit must be 1. ex:'1, 2, 1'", elem_id="train_new_hypernetwork_layer_structure") @@ -912,7 +965,7 @@ def create_ui(): with gr.Column(): create_hypernetwork = gr.Button(value="Create hypernetwork", variant='primary', elem_id="train_create_hypernetwork") - with gr.Tab(label="Preprocess images"): + with gr.Tab(label="Preprocess images", id="preprocess_images"): process_src = gr.Textbox(label='Source directory', elem_id="train_process_src") process_dst = gr.Textbox(label='Destination directory', elem_id="train_process_dst") process_width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="train_process_width") @@ -920,6 +973,7 @@ def create_ui(): preprocess_txt_action = gr.Dropdown(label='Existing Caption txt Action', value="ignore", choices=["ignore", "copy", "prepend", "append"], elem_id="train_preprocess_txt_action") with gr.Row(): + process_keep_original_size = gr.Checkbox(label='Keep original size', elem_id="train_process_keep_original_size") process_flip = gr.Checkbox(label='Create flipped copies', elem_id="train_process_flip") process_split = gr.Checkbox(label='Split oversized images', elem_id="train_process_split") process_focal_crop = gr.Checkbox(label='Auto focal point crop', elem_id="train_process_focal_crop") @@ -979,7 +1033,7 @@ def create_ui(): def get_textual_inversion_template_names(): return sorted([x for x in textual_inversion.textual_inversion_templates]) - with gr.Tab(label="Train"): + with gr.Tab(label="Train", id="train"): gr.HTML(value="

Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images

") with FormRow(): train_embedding_name = gr.Dropdown(label='Embedding', elem_id="train_embedding", choices=sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys())) @@ -1086,6 +1140,7 @@ def create_ui(): process_width, process_height, preprocess_txt_action, + process_keep_original_size, process_flip, process_split, process_caption, @@ -1297,7 +1352,7 @@ def create_ui(): request_notifications = gr.Button(value='Request browser notifications', elem_id="request_notifications", visible=False) _show_all_pages = gr.Button(value="Show all pages", variant='primary', elem_id="settings_show_all_pages") - with gr.TabItem("Licenses"): + with gr.TabItem("Licenses", id="licenses"): gr.HTML(shared.html("licenses.html"), elem_id="licenses") def unload_sd_weights(): @@ -1370,7 +1425,7 @@ def create_ui(): parameters_copypaste.connect_paste_params_buttons() - with gr.Tabs(elem_id="tabs") as _tabs: + with gr.Tabs(elem_id="tabs") as tabs: for interface, label, ifid in interfaces: if label in shared.opts.hidden_tabs: continue @@ -1393,7 +1448,8 @@ def create_ui(): component = component_dict[k] info = opts.data_labels[k] - component.change( + change_handler = component.release if hasattr(component, 'release') else component.change + change_handler( fn=lambda value, k=k: run_settings_single(value, key=k), inputs=[component], outputs=[component, text_settings], @@ -1452,6 +1508,7 @@ def create_ui(): config_source, bake_in_vae, discard_weights, + save_metadata, ], outputs=[ primary_model_name, @@ -1495,7 +1552,7 @@ def create_ui(): if init_field is not None: init_field(saved_value) - if type(x) in [gr.Slider, gr.Radio, gr.Checkbox, gr.Textbox, gr.Number, gr.Dropdown] and x.visible: + if type(x) in [gr.Slider, gr.Radio, gr.Checkbox, gr.Textbox, gr.Number, gr.Dropdown, ToolButton] and x.visible: apply_field(x, 'visible') if type(x) == gr.Slider: @@ -1525,11 +1582,25 @@ def create_ui(): apply_field(x, 'value', check_dropdown, getattr(x, 'init_field', None)) + def check_tab_id(tab_id): + tab_items = list(filter(lambda e: isinstance(e, gr.TabItem), x.children)) + if type(tab_id) == str: + tab_ids = [t.id for t in tab_items] + return tab_id in tab_ids + elif type(tab_id) == int: + return tab_id >= 0 and tab_id < len(tab_items) + else: + return False + + if type(x) == gr.Tabs: + apply_field(x, 'selected', check_tab_id) + visit(txt2img_interface, loadsave, "txt2img") visit(img2img_interface, loadsave, "img2img") visit(extras_interface, loadsave, "extras") visit(modelmerger_interface, loadsave, "modelmerger") visit(train_interface, loadsave, "train") + loadsave(f"webui/Tabs@{tabs.elem_id}", tabs) if not error_loading and (not os.path.exists(ui_config_file) or settings_count != len(ui_settings)): with open(ui_config_file, "w", encoding="utf8") as file: @@ -1551,7 +1622,7 @@ def webpath(fn): def html_head(): - script_js = os.path.join(script_path, "script.js") + script_js = os.path.join(script_path, "javascript", "script.js") head = f'\n' for script in modules.scripts.list_scripts("javascript", ".js"): head += f'\n' @@ -1573,9 +1644,10 @@ def html_body(): def html_css(): - head = "" def stylesheet(fn): + shared.log.debug(f'Adding stylesheet: {fn}') return f'' + head = stylesheet('javascript/style.css') for cssfile in modules.scripts.list_files_with_name("style.css"): if not os.path.isfile(cssfile): continue diff --git a/modules/ui_common.py b/modules/ui_common.py index 6dd0f323d..7ac745b8d 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -120,7 +120,7 @@ def create_output_panel(tabname, outdir): with gr.Column(variant='panel', elem_id=f"{tabname}_results"): with gr.Group(elem_id=f"{tabname}_gallery_container"): - result_gallery = gr.Gallery(value=['automatic.png'], label='Output', show_label=False, elem_id=f"{tabname}_gallery").style(preview=False, container=False, columns=[1,2,3,4,5,6]) # <576px, <768px, <992px, <1200px, <1400px, >1400px + result_gallery = gr.Gallery(value=['html/logo.png'], label='Output', show_label=False, elem_id=f"{tabname}_gallery").style(preview=False, container=False, columns=[1,2,3,4,5,6]) # <576px, <768px, <992px, <1200px, <1400px, >1400px generation_info = None with gr.Column(): diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index fb7624bff..1c84e3dc7 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -4,12 +4,14 @@ import time import shutil import errno import html +from datetime import datetime import git import gradio as gr from modules import extensions, shared, paths, errors from modules.call_queue import wrap_gradio_gpu_call available_extensions = {"extensions": []} +STYLE_PRIMARY = ' style="color: var(--primary-400)"' def check_access(): @@ -71,6 +73,16 @@ def check_updates(_id_task, disable_list): return extension_table(), "" +def make_commit_link(commit_hash, remote, text=None): + if text is None: + text = commit_hash[:8] + if remote.startswith("https://github.com/"): + href = os.path.join(remote, "commit", commit_hash) + return f'{text}' + else: + return text + + def extension_table(): code = f""" @@ -98,14 +110,18 @@ def extension_table(): style = "" if shared.opts.disable_all_extensions == "extra" and not ext.is_builtin or shared.opts.disable_all_extensions == "all": - style = ' style="color: var(--primary-400)"' + style = STYLE_PRIMARY + + version_link = ext.version + if ext.commit_hash and ext.remote: + version_link = make_commit_link(ext.commit_hash, ext.remote, ext.version) code += f""" - + {ext_status} """ @@ -126,7 +142,7 @@ def normalize_git_url(url): return url -def install_extension_from_url(dirname, url): +def install_extension_from_url(dirname, url, branch_name=None): check_access() assert url, 'No URL specified' @@ -147,10 +163,17 @@ def install_extension_from_url(dirname, url): try: shutil.rmtree(tmpdir, True) - with git.Repo.clone_from(url, tmpdir) as repo: - repo.remote().fetch() - for submodule in repo.submodules: - submodule.update() + if not branch_name: + # if no branch is specified, use the default branch + with git.Repo.clone_from(url, tmpdir) as repo: + repo.remote().fetch() + for submodule in repo.submodules: + submodule.update() + else: + with git.Repo.clone_from(url, tmpdir, branch=branch_name) as repo: + repo.remote().fetch() + for submodule in repo.submodules: + submodule.update() try: os.rename(tmpdir, target_dir) except OSError as err: @@ -287,7 +310,7 @@ def create_ui(): with gr.Blocks(analytics_enabled=False) as ui: with gr.Tabs(elem_id="tabs_extensions"): - with gr.TabItem("Installed"): + with gr.TabItem("Installed", id="installed"): with gr.Row(elem_id="extensions_installed_top"): apply = gr.Button(value="Apply (restart required)", variant="primary") @@ -320,7 +343,7 @@ def create_ui(): outputs=[extensions_table, info], ) - with gr.TabItem("Available"): + with gr.TabItem("Available", id="available"): with gr.Row(): refresh_available_extensions_button = gr.Button(value="Load from:", variant="primary") available_extensions_index = gr.Text(value="https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui-extensions/master/index.json", label="Extension index URL").style(container=False) @@ -367,15 +390,16 @@ def create_ui(): outputs=[available_extensions_table, install_result] ) - with gr.TabItem("Install from URL"): + with gr.TabItem("Install from URL", id="install_from_url"): install_url = gr.Text(label="URL for extension's git repository") + 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") install_result = gr.HTML(elem_id="extension_install_result") install_button.click( fn=modules.ui.wrap_gradio_call(install_extension_from_url, extra_outputs=[gr.update()]), - inputs=[install_dirname, install_url], + inputs=[install_dirname, install_url, install_branch], outputs=[extensions_table, install_result], ) diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index c5ddbfae5..80c5d940e 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -11,13 +11,13 @@ def create_ui(): with gr.Column(variant='compact'): with gr.Tabs(elem_id="mode_extras"): - with gr.TabItem('Process Image', elem_id="extras_single_tab") as tab_single: + with gr.TabItem('Single Image', id="single_image", elem_id="extras_single_tab") as tab_single: extras_image = gr.Image(label="Source", source="upload", interactive=True, type="pil", elem_id="extras_image") - with gr.TabItem('Process Batch', elem_id="extras_batch_process_tab") as tab_batch: - image_batch = gr.File(label="Batch Process", file_count="multiple", interactive=True, type="file", elem_id="extras_image_batch") + with gr.TabItem('Process Batch', id="batch_process", elem_id="extras_batch_process_tab") as tab_batch: + image_batch = gr.Files(label="Batch Process", interactive=True, elem_id="extras_image_batch") - with gr.TabItem('Process Folder', elem_id="extras_batch_directory_tab") as tab_batch_dir: + with gr.TabItem('Process Folder', id="batch_from_directory", elem_id="extras_batch_directory_tab") as tab_batch_dir: extras_batch_input_dir = gr.Textbox(label="Input directory", **shared.hide_dirs, placeholder="A directory on the same machine where the server is running.", elem_id="extras_batch_input_dir") extras_batch_output_dir = gr.Textbox(label="Output directory", **shared.hide_dirs, placeholder="Leave blank to save images to the default path.", elem_id="extras_batch_output_dir") show_extras_results = gr.Checkbox(label='Show result images', value=True, elem_id="extras_show_extras_results") diff --git a/webui.py b/webui.py index 5309aa7df..841048237 100644 --- a/webui.py +++ b/webui.py @@ -223,7 +223,7 @@ def start_ui(): inbrowser=cmd_opts.autolaunch, prevent_thread_lock=True, show_api=True, - favicon_path='automatic.ico', + favicon_path='html/logo.ico', ) shared.demo.server.wants_restart = False setup_middleware(app, cmd_opts)
{html.escape(ext.name)} {"system" if ext.is_builtin else 'user'} {remote}{ext.version}{version_link}