diff --git a/modules/config.py b/modules/config.py index 8609b415..1a67747b 100644 --- a/modules/config.py +++ b/modules/config.py @@ -267,7 +267,7 @@ temp_path_cleanup_on_launch = get_config_item_or_set_default( ) default_base_model_name = default_model = get_config_item_or_set_default( key='default_model', - default_value='model.safetensors', + default_value='None', validator=lambda x: isinstance(x, str), expected_type=str ) diff --git a/modules/default_pipeline.py b/modules/default_pipeline.py index 494644d6..0ccb0796 100644 --- a/modules/default_pipeline.py +++ b/modules/default_pipeline.py @@ -267,12 +267,15 @@ def refresh_everything(refiner_model_name, base_model_name, loras, return -refresh_everything( - refiner_model_name=modules.config.default_refiner_model_name, - base_model_name=modules.config.default_base_model_name, - loras=get_enabled_loras(modules.config.default_loras), - vae_name=modules.config.default_vae, -) +if modules.config.default_base_model_name != 'None': + refresh_everything( + refiner_model_name=modules.config.default_refiner_model_name, + base_model_name=modules.config.default_base_model_name, + loras=get_enabled_loras(modules.config.default_loras), + vae_name=modules.config.default_vae, + ) +else: + print('[Startup] Skipping model load (default_base_model_name is "None").') @torch.no_grad() diff --git a/modules/flags.py b/modules/flags.py index 05c29a23..a56104f2 100644 --- a/modules/flags.py +++ b/modules/flags.py @@ -103,7 +103,7 @@ sdxl_aspect_ratios = [ '896*1152', '896*1088', '960*1088', '960*1024', '1024*1024', '1024*960', '1088*960', '1088*896', '1152*896', '1152*832', '1216*832', '1280*768', '1344*768', '1344*704', '1408*704', '1472*704', '1536*640', '1600*640', - '1664*576', '1728*576' + '1664*576', '1728*576', '1080*1920', '1920*1080' ] diff --git a/presets/default.json b/presets/default.json index e9aa2b62..d5caf231 100644 --- a/presets/default.json +++ b/presets/default.json @@ -1,60 +1,13 @@ { - "default_model": "juggernautXL_v8Rundiffusion.safetensors", - "default_refiner": "None", "default_refiner_switch": 0.5, - "default_loras": [ - [ - true, - "sd_xl_offset_example-lora_1.0.safetensors", - 0.1 - ], - [ - true, - "None", - 1.0 - ], - [ - true, - "None", - 1.0 - ], - [ - true, - "None", - 1.0 - ], - [ - true, - "None", - 1.0 - ] - ], "default_cfg_scale": 4.0, "default_sample_sharpness": 2.0, "default_sampler": "dpmpp_2m_sde_gpu", "default_scheduler": "karras", - "default_performance": "Speed", + "default_performance": "Quality", "default_prompt": "", "default_prompt_negative": "", - "default_styles": [ - "Fooocus V2", - "Fooocus Enhance", - "Fooocus Sharp" - ], - "default_aspect_ratio": "1152*896", - "default_overwrite_step": -1, - "checkpoint_downloads": { - "juggernautXL_v8Rundiffusion.safetensors": "https://huggingface.co/lllyasviel/fav_models/resolve/main/fav/juggernautXL_v8Rundiffusion.safetensors" - }, - "embeddings_downloads": {}, - "lora_downloads": { - "sd_xl_offset_example-lora_1.0.safetensors": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors" - }, - "previous_default_models": [ - "juggernautXL_version8Rundiffusion.safetensors", - "juggernautXL_version7Rundiffusion.safetensors", - "juggernautXL_v7Rundiffusion.safetensors", - "juggernautXL_version6Rundiffusion.safetensors", - "juggernautXL_v6Rundiffusion.safetensors" - ] + "default_styles": [], + "default_aspect_ratio": "1080*1920", + "default_overwrite_step": -1 } \ No newline at end of file diff --git a/webui.py b/webui.py index b8159d85..df7bc072 100644 --- a/webui.py +++ b/webui.py @@ -24,6 +24,11 @@ from modules.ui_gradio_extensions import reload_javascript from modules.auth import auth_enabled, check_auth from modules.util import is_json +import webui_others +import os +from urllib.parse import urlparse, unquote +from modules.model_loader import load_file_from_url + def get_task(*args): args = list(args) args.pop(0) @@ -556,7 +561,7 @@ with shared.gradio_root: outputs=enhance_input_panel, queue=False, show_progress=False, _js=switch_js) with gr.Column(scale=1, visible=modules.config.default_advanced_checkbox) as advanced_column: - with gr.Tab(label='Settings'): + with gr.Tab(label='Settings') as settings_tab: if not args_manager.args.disable_preset_selection: preset_selection = gr.Dropdown(label='Preset', choices=modules.config.available_presets, @@ -651,10 +656,10 @@ with shared.gradio_root: show_progress=False).then( lambda: None, _js='()=>{refresh_style_localization();}') - with gr.Tab(label='Models'): + with gr.Tab(label='Models') as model_tab: with gr.Group(): with gr.Row(): - base_model = gr.Dropdown(label='Base Model (SDXL only)', choices=modules.config.model_filenames, value=modules.config.default_base_model_name, show_label=True) + base_model = gr.Dropdown(label='Base Model (SDXL only)', choices=['None'] + modules.config.model_filenames, value=modules.config.default_base_model_name, show_label=True) refiner_model = gr.Dropdown(label='Refiner (SDXL or SD 1.5)', choices=['None'] + modules.config.model_filenames, value=modules.config.default_refiner_model_name, show_label=True) refiner_switch = gr.Slider(label='Refiner Switch At', minimum=0.1, maximum=1.0, step=0.0001, @@ -870,7 +875,7 @@ with shared.gradio_root: def refresh_files_clicked(): modules.config.update_files() - results = [gr.update(choices=modules.config.model_filenames)] + results = [gr.update(choices=['None'] + modules.config.model_filenames)] results += [gr.update(choices=['None'] + modules.config.model_filenames)] results += [gr.update(choices=[flags.default_vae] + modules.config.vae_filenames)] if not args_manager.args.disable_preset_selection: @@ -885,6 +890,43 @@ with shared.gradio_root: refresh_files_output += [preset_selection] refresh_files.click(refresh_files_clicked, [], refresh_files_output + lora_ctrls, queue=False, show_progress=False) + + advanced_checkbox.select( + fn=refresh_files_clicked, + inputs=[], + outputs=refresh_files_output + lora_ctrls, + queue=False, + show_progress=False + ) + + settings_tab.select( + fn=refresh_files_clicked, + inputs=[], + outputs=refresh_files_output + lora_ctrls, + queue=False, + show_progress=False + ) + + model_tab.select( + fn=refresh_files_clicked, + inputs=[], + outputs=refresh_files_output + lora_ctrls, + queue=False, + show_progress=False + ) + + webui_others.build_others_tab( + prompt=prompt, + negative_prompt=negative_prompt, + aspect_ratio=aspect_ratios_selection, + performance=performance_selection, + styles=style_selections, + base_model=base_model, + refiner_model=refiner_model, + refiner_switch=refiner_switch, + image_number=image_number, + lora_ctrls=lora_ctrls + ) state_is_generating = gr.State(False) @@ -1125,4 +1167,4 @@ shared.gradio_root.launch( auth=check_auth if (args_manager.args.share or args_manager.args.listen) and auth_enabled else None, allowed_paths=[modules.config.path_outputs], blocked_paths=[constants.AUTH_FILENAME] -) +) \ No newline at end of file diff --git a/webui_others.py b/webui_others.py new file mode 100644 index 00000000..0927541b --- /dev/null +++ b/webui_others.py @@ -0,0 +1,280 @@ +# webui_others.py +import os +import shutil +import requests +from urllib.parse import urlparse, unquote +import gradio as gr +import modules.config +from modules.model_loader import load_file_from_url + +def build_others_tab( + prompt, + negative_prompt, + aspect_ratio, + performance, + styles, + base_model, + refiner_model, + refiner_switch, + image_number, + lora_ctrls +): + with gr.Tab(label="Others") as others_tab: + with gr.Tab(label="Download"): + file_input_path = gr.Textbox( + label="File Path or URL", + placeholder="Enter full path to file or downloadable URL", + lines=1, + ) + + destination_folder = gr.Dropdown( + label="Target Folder", + choices=[ + modules.config.paths_checkpoints[0], + modules.config.paths_loras[0], + modules.config.path_embeddings, + modules.config.path_vae, + ], + value=modules.config.paths_checkpoints[0], + ) + + download_result_text = gr.Textbox( + label="Download Status", interactive=False + ) + download_file_button = gr.Button( + value="\u2b07 Download", + variant="secondary", + elem_classes="refresh_button", + ) + + def perform_download(file_url_or_path, target_directory): + try: + if isinstance(target_directory, tuple): + target_directory = target_directory[1] + + if file_url_or_path.startswith(("http://", "https://")): + response = requests.get(file_url_or_path, stream=True) + response.raise_for_status() + content_disposition = response.headers.get( + "Content-Disposition", "" + ) + if "filename=" in content_disposition: + filename = content_disposition.split("filename=")[ + -1 + ].strip('"') + else: + parsed_url = urlparse(file_url_or_path) + filename = unquote(os.path.basename(parsed_url.path)) + downloaded_path = load_file_from_url( + file_url_or_path, + model_dir=target_directory, + progress=True, + file_name=filename, + ) + return f"\u2705 Downloaded to: {downloaded_path}" + + if os.path.isfile(file_url_or_path): + filename = os.path.basename(file_url_or_path) + destination_path = os.path.join(target_directory, filename) + shutil.copy(file_url_or_path, destination_path) + return f"\u2705 Copied to: {destination_path}" + + return "\u274c Error: File not found or invalid input." + + except Exception as e: + return f"\u274c Failed: {str(e)}" + + download_file_button.click( + fn=perform_download, + inputs=[file_input_path, destination_folder], + outputs=[download_result_text], + ) + + with gr.Tab(label="Delete"): + delete_folder_dropdown = gr.Dropdown( + label="Select Folder", + choices=[ + modules.config.paths_checkpoints[0], + modules.config.paths_loras[0], + modules.config.path_embeddings, + modules.config.path_vae, + modules.config.get_dir_or_set_default("path_presets", "../presets"), + ], + value=modules.config.paths_checkpoints[0], + ) + + file_list_dropdown = gr.Dropdown( + label="Select File to Delete", choices=[], multiselect=True + ) + delete_button = gr.Button( + value="\U0001f5d1 Delete Selected File(s)", variant="stop" + ) + delete_status = gr.Textbox( + visible=True, interactive=False, label="Delete Status" + ) + + def update_file_list(folder): + try: + files = [ + f + for f in os.listdir(folder) + if os.path.isfile(os.path.join(folder, f)) + ] + return gr.update(choices=files, value=[]) + except Exception as e: + return gr.update(choices=[], value=[]) + + def delete_selected_files(folder, selected_files): + deleted = [] + errors = [] + + for fname in selected_files: + try: + file_path = os.path.join(folder, fname) + if os.path.isfile(file_path): + os.remove(file_path) + deleted.append(fname) + else: + errors.append(fname) + except Exception as e: + errors.append(f"{fname} (error: {e})") + + status = "" + if deleted: + status += f"\u2705 Deleted: {', '.join(deleted)}. " + if errors: + status += f"\u274c Failed: {', '.join(errors)}" + if not deleted and not errors: + status = "\u26a0\ufe0f No files selected." + + try: + files = [ + f + for f in os.listdir(folder) + if os.path.isfile(os.path.join(folder, f)) + ] + except Exception: + files = [] + + return status.strip(), gr.update(choices=files, value=[]) + + delete_folder_dropdown.change( + update_file_list, + inputs=[delete_folder_dropdown], + outputs=[file_list_dropdown], + ) + delete_button.click( + delete_selected_files, + inputs=[delete_folder_dropdown, file_list_dropdown], + outputs=[delete_status, file_list_dropdown], + ) + + with gr.Tab(label="Backup/Restore"): + with gr.Tab(label="Preset"): + with gr.Row(): + backup_name = gr.Textbox(label="Backup Filename (no extension)", placeholder="e.g. my_config_backup") + backup_button = gr.Button(value="\u2B06 Backup Settings") + backup_file = gr.File(label="Download .json", interactive=False) + + def clean_aspect_ratio(value): + if not value: + return None + raw = value.split(" ")[0] + return raw.replace("×", "*") # convert Unicode × to ASCII * + + def backup_selected_settings( + filename, + prompt, negative_prompt, aspect_ratio, performance, styles, + base_model, refiner_model, refiner_switch, image_number, + *lora_ctrls + ): + import json + if not filename: + return None + + config = { + "default_prompt": prompt, + "default_prompt_negative": negative_prompt, + "default_aspect_ratio": clean_aspect_ratio(aspect_ratio), + "default_performance": performance, + "default_styles": styles, + "default_model": base_model, + "default_refiner": refiner_model, + "default_refiner_switch": refiner_switch, + "default_image_number": image_number + } + + loras = [] + for i in range(0, len(lora_ctrls), 3): + enabled = lora_ctrls[i] + model = lora_ctrls[i + 1] + weight = lora_ctrls[i + 2] + if model and model != "None": + loras.append([enabled, model, weight]) + + if loras: + config["default_loras"] = loras + + config = {k: v for k, v in config.items() if v not in [None, "", [], "None"]} + + out_path = os.path.join("outputs", f"{filename}.json") + with open(out_path, "w") as f: + json.dump(config, f, indent=2) + + return out_path + + backup_button.click( + fn=backup_selected_settings, + inputs=[ + backup_name, + prompt, + negative_prompt, + aspect_ratio, + performance, + styles, + base_model, + refiner_model, + refiner_switch, + image_number, + ] + lora_ctrls, + outputs=[backup_file], + ) + + with gr.Tab(label="Restore"): + with gr.Row(): + restore_file = gr.File(label="Upload Config File (.json)", file_types=[".json"]) + restore_button = gr.Button(value="\u267B️ Restore to Presets Folder") + restore_status = gr.Textbox(label="Status", interactive=False) + + def restore_config_file(file_obj): + try: + if file_obj is None: + return "\u26A0 No file selected." + filename = os.path.basename(file_obj.name) + presets_dir = os.path.join("presets") + os.makedirs(presets_dir, exist_ok=True) + destination = os.path.join(presets_dir, filename) + shutil.copy(file_obj.name, destination) + return f"\u2705 Saved to presets/{filename}" + except Exception as e: + return f"\u274C Failed to restore: {e}" + + restore_button.click( + fn=restore_config_file, + inputs=[restore_file], + outputs=[restore_status] + ) + + gr.Markdown( + "You can backup your current settings and restore them later. " + "This is useful for saving configurations or sharing with others." + ) + + + others_tab.select( + fn=update_file_list, + inputs=[delete_folder_dropdown], + outputs=[file_list_dropdown], + queue=False, + show_progress=False, + )