Commit ·
31a9ae5
1
Parent(s): c7ced54
Upload 3 files
Browse files- nr/extra_options_section.py +48 -0
- nr/scripts.py +679 -0
- nr/ui_settings.py +296 -0
nr/extra_options_section.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from modules import scripts, shared, ui_components, ui_settings
|
| 3 |
+
from modules.ui_components import FormColumn
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class ExtraOptionsSection(scripts.Script):
|
| 7 |
+
section = "extra_options"
|
| 8 |
+
|
| 9 |
+
def __init__(self):
|
| 10 |
+
self.comps = None
|
| 11 |
+
self.setting_names = None
|
| 12 |
+
|
| 13 |
+
def title(self):
|
| 14 |
+
return "Extra options"
|
| 15 |
+
|
| 16 |
+
def show(self, is_img2img):
|
| 17 |
+
return scripts.AlwaysVisible
|
| 18 |
+
|
| 19 |
+
def ui(self, is_img2img):
|
| 20 |
+
self.comps = []
|
| 21 |
+
self.setting_names = []
|
| 22 |
+
|
| 23 |
+
with gr.Blocks() as interface:
|
| 24 |
+
with gr.Accordion("Options", open=False) if shared.opts.extra_options_accordion and shared.opts.extra_options else gr.Group(), gr.Row():
|
| 25 |
+
for setting_name in shared.opts.extra_options:
|
| 26 |
+
with FormColumn():
|
| 27 |
+
comp = ui_settings.create_setting_component(setting_name)
|
| 28 |
+
|
| 29 |
+
self.comps.append(comp)
|
| 30 |
+
self.setting_names.append(setting_name)
|
| 31 |
+
|
| 32 |
+
def get_settings_values():
|
| 33 |
+
return [ui_settings.get_value_for_setting(key) for key in self.setting_names]
|
| 34 |
+
|
| 35 |
+
interface.load(fn=get_settings_values, inputs=[], outputs=self.comps, queue=False, show_progress=False)
|
| 36 |
+
|
| 37 |
+
return self.comps
|
| 38 |
+
|
| 39 |
+
def before_process(self, p, *args):
|
| 40 |
+
for name, value in zip(self.setting_names, args):
|
| 41 |
+
if name not in p.override_settings:
|
| 42 |
+
p.override_settings[name] = value
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
shared.options_templates.update(shared.options_section(('ui', "User interface"), {
|
| 46 |
+
"extra_options": shared.OptionInfo([], "Options in main UI", ui_components.DropdownMulti, lambda: {"choices": list(shared.opts.data_labels.keys())}).js("info", "settingsHintsShowQuicksettings").info("setting entries that also appear in txt2img/img2img interfaces").needs_restart(),
|
| 47 |
+
"extra_options_accordion": shared.OptionInfo(False, "Place options in main UI into an accordion")
|
| 48 |
+
}))
|
nr/scripts.py
ADDED
|
@@ -0,0 +1,679 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
import sys
|
| 4 |
+
import inspect
|
| 5 |
+
from collections import namedtuple
|
| 6 |
+
|
| 7 |
+
import gradio as gr
|
| 8 |
+
|
| 9 |
+
from modules import shared, paths, script_callbacks, extensions, script_loading, scripts_postprocessing, errors, timer
|
| 10 |
+
|
| 11 |
+
AlwaysVisible = object()
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class PostprocessImageArgs:
|
| 15 |
+
def __init__(self, image):
|
| 16 |
+
self.image = image
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class PostprocessBatchListArgs:
|
| 20 |
+
def __init__(self, images):
|
| 21 |
+
self.images = images
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class Script:
|
| 25 |
+
name = None
|
| 26 |
+
"""script's internal name derived from title"""
|
| 27 |
+
|
| 28 |
+
section = None
|
| 29 |
+
"""name of UI section that the script's controls will be placed into"""
|
| 30 |
+
|
| 31 |
+
filename = None
|
| 32 |
+
args_from = None
|
| 33 |
+
args_to = None
|
| 34 |
+
alwayson = False
|
| 35 |
+
|
| 36 |
+
is_txt2img = False
|
| 37 |
+
is_img2img = False
|
| 38 |
+
|
| 39 |
+
group = None
|
| 40 |
+
"""A gr.Group component that has all script's UI inside it"""
|
| 41 |
+
|
| 42 |
+
infotext_fields = None
|
| 43 |
+
"""if set in ui(), this is a list of pairs of gradio component + text; the text will be used when
|
| 44 |
+
parsing infotext to set the value for the component; see ui.py's txt2img_paste_fields for an example
|
| 45 |
+
"""
|
| 46 |
+
|
| 47 |
+
paste_field_names = None
|
| 48 |
+
"""if set in ui(), this is a list of names of infotext fields; the fields will be sent through the
|
| 49 |
+
various "Send to <X>" buttons when clicked
|
| 50 |
+
"""
|
| 51 |
+
|
| 52 |
+
api_info = None
|
| 53 |
+
"""Generated value of type modules.api.models.ScriptInfo with information about the script for API"""
|
| 54 |
+
|
| 55 |
+
def title(self):
|
| 56 |
+
"""this function should return the title of the script. This is what will be displayed in the dropdown menu."""
|
| 57 |
+
|
| 58 |
+
raise NotImplementedError()
|
| 59 |
+
|
| 60 |
+
def ui(self, is_img2img):
|
| 61 |
+
"""this function should create gradio UI elements. See https://gradio.app/docs/#components
|
| 62 |
+
The return value should be an array of all components that are used in processing.
|
| 63 |
+
Values of those returned components will be passed to run() and process() functions.
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
pass
|
| 67 |
+
|
| 68 |
+
def show(self, is_img2img):
|
| 69 |
+
"""
|
| 70 |
+
is_img2img is True if this function is called for the img2img interface, and Fasle otherwise
|
| 71 |
+
|
| 72 |
+
This function should return:
|
| 73 |
+
- False if the script should not be shown in UI at all
|
| 74 |
+
- True if the script should be shown in UI if it's selected in the scripts dropdown
|
| 75 |
+
- script.AlwaysVisible if the script should be shown in UI at all times
|
| 76 |
+
"""
|
| 77 |
+
|
| 78 |
+
return True
|
| 79 |
+
|
| 80 |
+
def run(self, p, *args):
|
| 81 |
+
"""
|
| 82 |
+
This function is called if the script has been selected in the script dropdown.
|
| 83 |
+
It must do all processing and return the Processed object with results, same as
|
| 84 |
+
one returned by processing.process_images.
|
| 85 |
+
|
| 86 |
+
Usually the processing is done by calling the processing.process_images function.
|
| 87 |
+
|
| 88 |
+
args contains all values returned by components from ui()
|
| 89 |
+
"""
|
| 90 |
+
|
| 91 |
+
pass
|
| 92 |
+
|
| 93 |
+
def before_process(self, p, *args):
|
| 94 |
+
"""
|
| 95 |
+
This function is called very early before processing begins for AlwaysVisible scripts.
|
| 96 |
+
You can modify the processing object (p) here, inject hooks, etc.
|
| 97 |
+
args contains all values returned by components from ui()
|
| 98 |
+
"""
|
| 99 |
+
|
| 100 |
+
pass
|
| 101 |
+
|
| 102 |
+
def process(self, p, *args):
|
| 103 |
+
"""
|
| 104 |
+
This function is called before processing begins for AlwaysVisible scripts.
|
| 105 |
+
You can modify the processing object (p) here, inject hooks, etc.
|
| 106 |
+
args contains all values returned by components from ui()
|
| 107 |
+
"""
|
| 108 |
+
|
| 109 |
+
pass
|
| 110 |
+
|
| 111 |
+
def before_process_batch(self, p, *args, **kwargs):
|
| 112 |
+
"""
|
| 113 |
+
Called before extra networks are parsed from the prompt, so you can add
|
| 114 |
+
new extra network keywords to the prompt with this callback.
|
| 115 |
+
|
| 116 |
+
**kwargs will have those items:
|
| 117 |
+
- batch_number - index of current batch, from 0 to number of batches-1
|
| 118 |
+
- prompts - list of prompts for current batch; you can change contents of this list but changing the number of entries will likely break things
|
| 119 |
+
- seeds - list of seeds for current batch
|
| 120 |
+
- subseeds - list of subseeds for current batch
|
| 121 |
+
"""
|
| 122 |
+
|
| 123 |
+
pass
|
| 124 |
+
|
| 125 |
+
def after_extra_networks_activate(self, p, *args, **kwargs):
|
| 126 |
+
"""
|
| 127 |
+
Called after extra networks activation, before conds calculation
|
| 128 |
+
allow modification of the network after extra networks activation been applied
|
| 129 |
+
won't be call if p.disable_extra_networks
|
| 130 |
+
|
| 131 |
+
**kwargs will have those items:
|
| 132 |
+
- batch_number - index of current batch, from 0 to number of batches-1
|
| 133 |
+
- prompts - list of prompts for current batch; you can change contents of this list but changing the number of entries will likely break things
|
| 134 |
+
- seeds - list of seeds for current batch
|
| 135 |
+
- subseeds - list of subseeds for current batch
|
| 136 |
+
- extra_network_data - list of ExtraNetworkParams for current stage
|
| 137 |
+
"""
|
| 138 |
+
pass
|
| 139 |
+
|
| 140 |
+
def process_batch(self, p, *args, **kwargs):
|
| 141 |
+
"""
|
| 142 |
+
Same as process(), but called for every batch.
|
| 143 |
+
|
| 144 |
+
**kwargs will have those items:
|
| 145 |
+
- batch_number - index of current batch, from 0 to number of batches-1
|
| 146 |
+
- prompts - list of prompts for current batch; you can change contents of this list but changing the number of entries will likely break things
|
| 147 |
+
- seeds - list of seeds for current batch
|
| 148 |
+
- subseeds - list of subseeds for current batch
|
| 149 |
+
"""
|
| 150 |
+
|
| 151 |
+
pass
|
| 152 |
+
|
| 153 |
+
def postprocess_batch(self, p, *args, **kwargs):
|
| 154 |
+
"""
|
| 155 |
+
Same as process_batch(), but called for every batch after it has been generated.
|
| 156 |
+
|
| 157 |
+
**kwargs will have same items as process_batch, and also:
|
| 158 |
+
- batch_number - index of current batch, from 0 to number of batches-1
|
| 159 |
+
- images - torch tensor with all generated images, with values ranging from 0 to 1;
|
| 160 |
+
"""
|
| 161 |
+
|
| 162 |
+
pass
|
| 163 |
+
|
| 164 |
+
def postprocess_batch_list(self, p, pp: PostprocessBatchListArgs, *args, **kwargs):
|
| 165 |
+
"""
|
| 166 |
+
Same as postprocess_batch(), but receives batch images as a list of 3D tensors instead of a 4D tensor.
|
| 167 |
+
This is useful when you want to update the entire batch instead of individual images.
|
| 168 |
+
|
| 169 |
+
You can modify the postprocessing object (pp) to update the images in the batch, remove images, add images, etc.
|
| 170 |
+
If the number of images is different from the batch size when returning,
|
| 171 |
+
then the script has the responsibility to also update the following attributes in the processing object (p):
|
| 172 |
+
- p.prompts
|
| 173 |
+
- p.negative_prompts
|
| 174 |
+
- p.seeds
|
| 175 |
+
- p.subseeds
|
| 176 |
+
|
| 177 |
+
**kwargs will have same items as process_batch, and also:
|
| 178 |
+
- batch_number - index of current batch, from 0 to number of batches-1
|
| 179 |
+
"""
|
| 180 |
+
|
| 181 |
+
pass
|
| 182 |
+
|
| 183 |
+
def postprocess_image(self, p, pp: PostprocessImageArgs, *args):
|
| 184 |
+
"""
|
| 185 |
+
Called for every image after it has been generated.
|
| 186 |
+
"""
|
| 187 |
+
|
| 188 |
+
pass
|
| 189 |
+
|
| 190 |
+
def postprocess(self, p, processed, *args):
|
| 191 |
+
"""
|
| 192 |
+
This function is called after processing ends for AlwaysVisible scripts.
|
| 193 |
+
args contains all values returned by components from ui()
|
| 194 |
+
"""
|
| 195 |
+
|
| 196 |
+
pass
|
| 197 |
+
|
| 198 |
+
def before_component(self, component, **kwargs):
|
| 199 |
+
"""
|
| 200 |
+
Called before a component is created.
|
| 201 |
+
Use elem_id/label fields of kwargs to figure out which component it is.
|
| 202 |
+
This can be useful to inject your own components somewhere in the middle of vanilla UI.
|
| 203 |
+
You can return created components in the ui() function to add them to the list of arguments for your processing functions
|
| 204 |
+
"""
|
| 205 |
+
|
| 206 |
+
pass
|
| 207 |
+
|
| 208 |
+
def after_component(self, component, **kwargs):
|
| 209 |
+
"""
|
| 210 |
+
Called after a component is created. Same as above.
|
| 211 |
+
"""
|
| 212 |
+
|
| 213 |
+
pass
|
| 214 |
+
|
| 215 |
+
def describe(self):
|
| 216 |
+
"""unused"""
|
| 217 |
+
return ""
|
| 218 |
+
|
| 219 |
+
def elem_id(self, item_id):
|
| 220 |
+
"""helper function to generate id for a HTML element, constructs final id out of script name, tab and user-supplied item_id"""
|
| 221 |
+
|
| 222 |
+
need_tabname = self.show(True) == self.show(False)
|
| 223 |
+
tabkind = 'img2img' if self.is_img2img else 'txt2txt'
|
| 224 |
+
tabname = f"{tabkind}_" if need_tabname else ""
|
| 225 |
+
title = re.sub(r'[^a-z_0-9]', '', re.sub(r'\s', '_', self.title().lower()))
|
| 226 |
+
|
| 227 |
+
return f'script_{tabname}{title}_{item_id}'
|
| 228 |
+
|
| 229 |
+
def before_hr(self, p, *args):
|
| 230 |
+
"""
|
| 231 |
+
This function is called before hires fix start.
|
| 232 |
+
"""
|
| 233 |
+
pass
|
| 234 |
+
|
| 235 |
+
current_basedir = paths.script_path
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def basedir():
|
| 239 |
+
"""returns the base directory for the current script. For scripts in the main scripts directory,
|
| 240 |
+
this is the main directory (where webui.py resides), and for scripts in extensions directory
|
| 241 |
+
(ie extensions/aesthetic/script/aesthetic.py), this is extension's directory (extensions/aesthetic)
|
| 242 |
+
"""
|
| 243 |
+
return current_basedir
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
ScriptFile = namedtuple("ScriptFile", ["basedir", "filename", "path"])
|
| 247 |
+
|
| 248 |
+
scripts_data = []
|
| 249 |
+
postprocessing_scripts_data = []
|
| 250 |
+
ScriptClassData = namedtuple("ScriptClassData", ["script_class", "path", "basedir", "module"])
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def list_scripts(scriptdirname, extension):
|
| 254 |
+
scripts_list = []
|
| 255 |
+
|
| 256 |
+
basedir = os.path.join(paths.script_path, scriptdirname)
|
| 257 |
+
if os.path.exists(basedir):
|
| 258 |
+
for filename in sorted(os.listdir(basedir)):
|
| 259 |
+
scripts_list.append(ScriptFile(paths.script_path, filename, os.path.join(basedir, filename)))
|
| 260 |
+
|
| 261 |
+
for ext in extensions.active():
|
| 262 |
+
scripts_list += ext.list_files(scriptdirname, extension)
|
| 263 |
+
|
| 264 |
+
scripts_list = [x for x in scripts_list if os.path.splitext(x.path)[1].lower() == extension and os.path.isfile(x.path)]
|
| 265 |
+
|
| 266 |
+
return scripts_list
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def list_files_with_name(filename):
|
| 270 |
+
res = []
|
| 271 |
+
|
| 272 |
+
dirs = [paths.script_path] + [ext.path for ext in extensions.active()]
|
| 273 |
+
|
| 274 |
+
for dirpath in dirs:
|
| 275 |
+
if not os.path.isdir(dirpath):
|
| 276 |
+
continue
|
| 277 |
+
|
| 278 |
+
path = os.path.join(dirpath, filename)
|
| 279 |
+
if os.path.isfile(path):
|
| 280 |
+
res.append(path)
|
| 281 |
+
|
| 282 |
+
return res
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def load_scripts():
|
| 286 |
+
global current_basedir
|
| 287 |
+
scripts_data.clear()
|
| 288 |
+
postprocessing_scripts_data.clear()
|
| 289 |
+
script_callbacks.clear_callbacks()
|
| 290 |
+
|
| 291 |
+
scripts_list = list_scripts("scripts", ".py")
|
| 292 |
+
|
| 293 |
+
syspath = sys.path
|
| 294 |
+
|
| 295 |
+
def register_scripts_from_module(module):
|
| 296 |
+
for script_class in module.__dict__.values():
|
| 297 |
+
if not inspect.isclass(script_class):
|
| 298 |
+
continue
|
| 299 |
+
|
| 300 |
+
if issubclass(script_class, Script):
|
| 301 |
+
scripts_data.append(ScriptClassData(script_class, scriptfile.path, scriptfile.basedir, module))
|
| 302 |
+
elif issubclass(script_class, scripts_postprocessing.ScriptPostprocessing):
|
| 303 |
+
postprocessing_scripts_data.append(ScriptClassData(script_class, scriptfile.path, scriptfile.basedir, module))
|
| 304 |
+
|
| 305 |
+
def orderby(basedir):
|
| 306 |
+
# 1st webui, 2nd extensions-builtin, 3rd extensions
|
| 307 |
+
priority = {os.path.join(paths.script_path, "extensions-builtin"):1, paths.script_path:0}
|
| 308 |
+
for key in priority:
|
| 309 |
+
if basedir.startswith(key):
|
| 310 |
+
return priority[key]
|
| 311 |
+
return 9999
|
| 312 |
+
|
| 313 |
+
for scriptfile in sorted(scripts_list, key=lambda x: [orderby(x.basedir), x]):
|
| 314 |
+
try:
|
| 315 |
+
if scriptfile.basedir != paths.script_path:
|
| 316 |
+
sys.path = [scriptfile.basedir] + sys.path
|
| 317 |
+
current_basedir = scriptfile.basedir
|
| 318 |
+
|
| 319 |
+
script_module = script_loading.load_module(scriptfile.path)
|
| 320 |
+
register_scripts_from_module(script_module)
|
| 321 |
+
|
| 322 |
+
except Exception:
|
| 323 |
+
errors.report(f"Error loading script: {scriptfile.filename}", exc_info=True)
|
| 324 |
+
|
| 325 |
+
finally:
|
| 326 |
+
sys.path = syspath
|
| 327 |
+
current_basedir = paths.script_path
|
| 328 |
+
timer.startup_timer.record(scriptfile.filename)
|
| 329 |
+
|
| 330 |
+
global scripts_txt2img, scripts_img2img, scripts_postproc
|
| 331 |
+
|
| 332 |
+
scripts_txt2img = ScriptRunner()
|
| 333 |
+
scripts_img2img = ScriptRunner()
|
| 334 |
+
scripts_postproc = scripts_postprocessing.ScriptPostprocessingRunner()
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
def wrap_call(func, filename, funcname, *args, default=None, **kwargs):
|
| 338 |
+
try:
|
| 339 |
+
return func(*args, **kwargs)
|
| 340 |
+
except Exception:
|
| 341 |
+
errors.report(f"Error calling: {filename}/{funcname}", exc_info=True)
|
| 342 |
+
|
| 343 |
+
return default
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
class ScriptRunner:
|
| 347 |
+
def __init__(self):
|
| 348 |
+
self.scripts = []
|
| 349 |
+
self.selectable_scripts = []
|
| 350 |
+
self.alwayson_scripts = []
|
| 351 |
+
self.titles = []
|
| 352 |
+
self.infotext_fields = []
|
| 353 |
+
self.paste_field_names = []
|
| 354 |
+
self.inputs = [None]
|
| 355 |
+
|
| 356 |
+
def initialize_scripts(self, is_img2img):
|
| 357 |
+
from modules import scripts_auto_postprocessing
|
| 358 |
+
|
| 359 |
+
self.scripts.clear()
|
| 360 |
+
self.alwayson_scripts.clear()
|
| 361 |
+
self.selectable_scripts.clear()
|
| 362 |
+
|
| 363 |
+
auto_processing_scripts = scripts_auto_postprocessing.create_auto_preprocessing_script_data()
|
| 364 |
+
|
| 365 |
+
for script_data in auto_processing_scripts + scripts_data:
|
| 366 |
+
script = script_data.script_class()
|
| 367 |
+
script.filename = script_data.path
|
| 368 |
+
script.is_txt2img = not is_img2img
|
| 369 |
+
script.is_img2img = is_img2img
|
| 370 |
+
|
| 371 |
+
visibility = script.show(script.is_img2img)
|
| 372 |
+
|
| 373 |
+
if visibility == AlwaysVisible:
|
| 374 |
+
self.scripts.append(script)
|
| 375 |
+
self.alwayson_scripts.append(script)
|
| 376 |
+
script.alwayson = True
|
| 377 |
+
|
| 378 |
+
elif visibility:
|
| 379 |
+
self.scripts.append(script)
|
| 380 |
+
self.selectable_scripts.append(script)
|
| 381 |
+
|
| 382 |
+
def create_script_ui(self, script):
|
| 383 |
+
import modules.api.models as api_models
|
| 384 |
+
|
| 385 |
+
script.args_from = len(self.inputs)
|
| 386 |
+
script.args_to = len(self.inputs)
|
| 387 |
+
|
| 388 |
+
controls = wrap_call(script.ui, script.filename, "ui", script.is_img2img)
|
| 389 |
+
|
| 390 |
+
if controls is None:
|
| 391 |
+
return
|
| 392 |
+
|
| 393 |
+
script.name = wrap_call(script.title, script.filename, "title", default=script.filename).lower()
|
| 394 |
+
api_args = []
|
| 395 |
+
|
| 396 |
+
for control in controls:
|
| 397 |
+
control.custom_script_source = os.path.basename(script.filename)
|
| 398 |
+
|
| 399 |
+
arg_info = api_models.ScriptArg(label=control.label or "")
|
| 400 |
+
|
| 401 |
+
for field in ("value", "minimum", "maximum", "step", "choices"):
|
| 402 |
+
v = getattr(control, field, None)
|
| 403 |
+
if v is not None:
|
| 404 |
+
setattr(arg_info, field, v)
|
| 405 |
+
|
| 406 |
+
api_args.append(arg_info)
|
| 407 |
+
|
| 408 |
+
script.api_info = api_models.ScriptInfo(
|
| 409 |
+
name=script.name,
|
| 410 |
+
is_img2img=script.is_img2img,
|
| 411 |
+
is_alwayson=script.alwayson,
|
| 412 |
+
args=api_args,
|
| 413 |
+
)
|
| 414 |
+
|
| 415 |
+
if script.infotext_fields is not None:
|
| 416 |
+
self.infotext_fields += script.infotext_fields
|
| 417 |
+
|
| 418 |
+
if script.paste_field_names is not None:
|
| 419 |
+
self.paste_field_names += script.paste_field_names
|
| 420 |
+
|
| 421 |
+
self.inputs += controls
|
| 422 |
+
script.args_to = len(self.inputs)
|
| 423 |
+
|
| 424 |
+
def setup_ui_for_section(self, section, scriptlist=None):
|
| 425 |
+
if scriptlist is None:
|
| 426 |
+
scriptlist = self.alwayson_scripts
|
| 427 |
+
|
| 428 |
+
for script in scriptlist:
|
| 429 |
+
if script.alwayson and script.section != section:
|
| 430 |
+
continue
|
| 431 |
+
|
| 432 |
+
with gr.Group(visible=script.alwayson) as group:
|
| 433 |
+
self.create_script_ui(script)
|
| 434 |
+
|
| 435 |
+
script.group = group
|
| 436 |
+
|
| 437 |
+
def prepare_ui(self):
|
| 438 |
+
self.inputs = [None]
|
| 439 |
+
|
| 440 |
+
def setup_ui(self):
|
| 441 |
+
self.titles = [wrap_call(script.title, script.filename, "title") or f"{script.filename} [error]" for script in self.selectable_scripts]
|
| 442 |
+
|
| 443 |
+
self.setup_ui_for_section(None)
|
| 444 |
+
|
| 445 |
+
dropdown = gr.Dropdown(label="Script", elem_id="script_list", choices=["None"] + self.titles, value="None", type="index")
|
| 446 |
+
self.inputs[0] = dropdown
|
| 447 |
+
|
| 448 |
+
self.setup_ui_for_section(None, self.selectable_scripts)
|
| 449 |
+
|
| 450 |
+
def select_script(script_index):
|
| 451 |
+
selected_script = self.selectable_scripts[script_index - 1] if script_index>0 else None
|
| 452 |
+
|
| 453 |
+
return [gr.update(visible=selected_script == s) for s in self.selectable_scripts]
|
| 454 |
+
|
| 455 |
+
def init_field(title):
|
| 456 |
+
"""called when an initial value is set from ui-config.json to show script's UI components"""
|
| 457 |
+
|
| 458 |
+
if title == 'None':
|
| 459 |
+
return
|
| 460 |
+
|
| 461 |
+
script_index = self.titles.index(title)
|
| 462 |
+
self.selectable_scripts[script_index].group.visible = True
|
| 463 |
+
|
| 464 |
+
dropdown.init_field = init_field
|
| 465 |
+
|
| 466 |
+
dropdown.change(
|
| 467 |
+
fn=select_script,
|
| 468 |
+
inputs=[dropdown],
|
| 469 |
+
outputs=[script.group for script in self.selectable_scripts]
|
| 470 |
+
)
|
| 471 |
+
|
| 472 |
+
self.script_load_ctr = 0
|
| 473 |
+
|
| 474 |
+
def onload_script_visibility(params):
|
| 475 |
+
title = params.get('Script', None)
|
| 476 |
+
if title:
|
| 477 |
+
title_index = self.titles.index(title)
|
| 478 |
+
visibility = title_index == self.script_load_ctr
|
| 479 |
+
self.script_load_ctr = (self.script_load_ctr + 1) % len(self.titles)
|
| 480 |
+
return gr.update(visible=visibility)
|
| 481 |
+
else:
|
| 482 |
+
return gr.update(visible=False)
|
| 483 |
+
|
| 484 |
+
self.infotext_fields.append((dropdown, lambda x: gr.update(value=x.get('Script', 'None'))))
|
| 485 |
+
self.infotext_fields.extend([(script.group, onload_script_visibility) for script in self.selectable_scripts])
|
| 486 |
+
|
| 487 |
+
return self.inputs
|
| 488 |
+
|
| 489 |
+
def run(self, p, *args):
|
| 490 |
+
script_index = args[0]
|
| 491 |
+
|
| 492 |
+
if script_index == 0:
|
| 493 |
+
return None
|
| 494 |
+
|
| 495 |
+
script = self.selectable_scripts[script_index-1]
|
| 496 |
+
|
| 497 |
+
if script is None:
|
| 498 |
+
return None
|
| 499 |
+
|
| 500 |
+
script_args = args[script.args_from:script.args_to]
|
| 501 |
+
processed = script.run(p, *script_args)
|
| 502 |
+
|
| 503 |
+
shared.total_tqdm.clear()
|
| 504 |
+
|
| 505 |
+
return processed
|
| 506 |
+
|
| 507 |
+
def before_process(self, p):
|
| 508 |
+
for script in self.alwayson_scripts:
|
| 509 |
+
try:
|
| 510 |
+
script_args = p.script_args[script.args_from:script.args_to]
|
| 511 |
+
script.before_process(p, *script_args)
|
| 512 |
+
except Exception:
|
| 513 |
+
errors.report(f"Error running before_process: {script.filename}", exc_info=True)
|
| 514 |
+
|
| 515 |
+
def process(self, p):
|
| 516 |
+
for script in self.alwayson_scripts:
|
| 517 |
+
try:
|
| 518 |
+
script_args = p.script_args[script.args_from:script.args_to]
|
| 519 |
+
script.process(p, *script_args)
|
| 520 |
+
except Exception:
|
| 521 |
+
errors.report(f"Error running process: {script.filename}", exc_info=True)
|
| 522 |
+
|
| 523 |
+
def before_process_batch(self, p, **kwargs):
|
| 524 |
+
for script in self.alwayson_scripts:
|
| 525 |
+
try:
|
| 526 |
+
script_args = p.script_args[script.args_from:script.args_to]
|
| 527 |
+
script.before_process_batch(p, *script_args, **kwargs)
|
| 528 |
+
except Exception:
|
| 529 |
+
errors.report(f"Error running before_process_batch: {script.filename}", exc_info=True)
|
| 530 |
+
|
| 531 |
+
def after_extra_networks_activate(self, p, **kwargs):
|
| 532 |
+
for script in self.alwayson_scripts:
|
| 533 |
+
try:
|
| 534 |
+
script_args = p.script_args[script.args_from:script.args_to]
|
| 535 |
+
script.after_extra_networks_activate(p, *script_args, **kwargs)
|
| 536 |
+
except Exception:
|
| 537 |
+
errors.report(f"Error running after_extra_networks_activate: {script.filename}", exc_info=True)
|
| 538 |
+
|
| 539 |
+
def process_batch(self, p, **kwargs):
|
| 540 |
+
for script in self.alwayson_scripts:
|
| 541 |
+
try:
|
| 542 |
+
script_args = p.script_args[script.args_from:script.args_to]
|
| 543 |
+
script.process_batch(p, *script_args, **kwargs)
|
| 544 |
+
except Exception:
|
| 545 |
+
errors.report(f"Error running process_batch: {script.filename}", exc_info=True)
|
| 546 |
+
|
| 547 |
+
def postprocess(self, p, processed):
|
| 548 |
+
for script in self.alwayson_scripts:
|
| 549 |
+
try:
|
| 550 |
+
script_args = p.script_args[script.args_from:script.args_to]
|
| 551 |
+
script.postprocess(p, processed, *script_args)
|
| 552 |
+
except Exception:
|
| 553 |
+
errors.report(f"Error running postprocess: {script.filename}", exc_info=True)
|
| 554 |
+
|
| 555 |
+
def postprocess_batch(self, p, images, **kwargs):
|
| 556 |
+
for script in self.alwayson_scripts:
|
| 557 |
+
try:
|
| 558 |
+
script_args = p.script_args[script.args_from:script.args_to]
|
| 559 |
+
script.postprocess_batch(p, *script_args, images=images, **kwargs)
|
| 560 |
+
except Exception:
|
| 561 |
+
errors.report(f"Error running postprocess_batch: {script.filename}", exc_info=True)
|
| 562 |
+
|
| 563 |
+
def postprocess_batch_list(self, p, pp: PostprocessBatchListArgs, **kwargs):
|
| 564 |
+
for script in self.alwayson_scripts:
|
| 565 |
+
try:
|
| 566 |
+
script_args = p.script_args[script.args_from:script.args_to]
|
| 567 |
+
script.postprocess_batch_list(p, pp, *script_args, **kwargs)
|
| 568 |
+
except Exception:
|
| 569 |
+
errors.report(f"Error running postprocess_batch_list: {script.filename}", exc_info=True)
|
| 570 |
+
|
| 571 |
+
def postprocess_image(self, p, pp: PostprocessImageArgs):
|
| 572 |
+
for script in self.alwayson_scripts:
|
| 573 |
+
try:
|
| 574 |
+
script_args = p.script_args[script.args_from:script.args_to]
|
| 575 |
+
script.postprocess_image(p, pp, *script_args)
|
| 576 |
+
except Exception:
|
| 577 |
+
errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True)
|
| 578 |
+
|
| 579 |
+
def before_component(self, component, **kwargs):
|
| 580 |
+
for script in self.scripts:
|
| 581 |
+
try:
|
| 582 |
+
script.before_component(component, **kwargs)
|
| 583 |
+
except Exception:
|
| 584 |
+
errors.report(f"Error running before_component: {script.filename}", exc_info=True)
|
| 585 |
+
|
| 586 |
+
def after_component(self, component, **kwargs):
|
| 587 |
+
for script in self.scripts:
|
| 588 |
+
try:
|
| 589 |
+
script.after_component(component, **kwargs)
|
| 590 |
+
except Exception:
|
| 591 |
+
errors.report(f"Error running after_component: {script.filename}", exc_info=True)
|
| 592 |
+
|
| 593 |
+
def reload_sources(self, cache):
|
| 594 |
+
for si, script in list(enumerate(self.scripts)):
|
| 595 |
+
args_from = script.args_from
|
| 596 |
+
args_to = script.args_to
|
| 597 |
+
filename = script.filename
|
| 598 |
+
|
| 599 |
+
module = cache.get(filename, None)
|
| 600 |
+
if module is None:
|
| 601 |
+
module = script_loading.load_module(script.filename)
|
| 602 |
+
cache[filename] = module
|
| 603 |
+
|
| 604 |
+
for script_class in module.__dict__.values():
|
| 605 |
+
if type(script_class) == type and issubclass(script_class, Script):
|
| 606 |
+
self.scripts[si] = script_class()
|
| 607 |
+
self.scripts[si].filename = filename
|
| 608 |
+
self.scripts[si].args_from = args_from
|
| 609 |
+
self.scripts[si].args_to = args_to
|
| 610 |
+
|
| 611 |
+
|
| 612 |
+
def before_hr(self, p):
|
| 613 |
+
for script in self.alwayson_scripts:
|
| 614 |
+
try:
|
| 615 |
+
script_args = p.script_args[script.args_from:script.args_to]
|
| 616 |
+
script.before_hr(p, *script_args)
|
| 617 |
+
except Exception:
|
| 618 |
+
errors.report(f"Error running before_hr: {script.filename}", exc_info=True)
|
| 619 |
+
|
| 620 |
+
|
| 621 |
+
scripts_txt2img: ScriptRunner = None
|
| 622 |
+
scripts_img2img: ScriptRunner = None
|
| 623 |
+
scripts_postproc: scripts_postprocessing.ScriptPostprocessingRunner = None
|
| 624 |
+
scripts_current: ScriptRunner = None
|
| 625 |
+
|
| 626 |
+
|
| 627 |
+
def reload_script_body_only():
|
| 628 |
+
cache = {}
|
| 629 |
+
scripts_txt2img.reload_sources(cache)
|
| 630 |
+
scripts_img2img.reload_sources(cache)
|
| 631 |
+
|
| 632 |
+
|
| 633 |
+
reload_scripts = load_scripts # compatibility alias
|
| 634 |
+
|
| 635 |
+
|
| 636 |
+
def add_classes_to_gradio_component(comp):
|
| 637 |
+
"""
|
| 638 |
+
this adds gradio-* to the component for css styling (ie gradio-button to gr.Button), as well as some others
|
| 639 |
+
"""
|
| 640 |
+
|
| 641 |
+
comp.elem_classes = [f"gradio-{comp.get_block_name()}", *(comp.elem_classes or [])]
|
| 642 |
+
|
| 643 |
+
if getattr(comp, 'multiselect', False):
|
| 644 |
+
comp.elem_classes.append('multiselect')
|
| 645 |
+
|
| 646 |
+
|
| 647 |
+
|
| 648 |
+
def IOComponent_init(self, *args, **kwargs):
|
| 649 |
+
if scripts_current is not None:
|
| 650 |
+
scripts_current.before_component(self, **kwargs)
|
| 651 |
+
|
| 652 |
+
script_callbacks.before_component_callback(self, **kwargs)
|
| 653 |
+
|
| 654 |
+
res = original_IOComponent_init(self, *args, **kwargs)
|
| 655 |
+
|
| 656 |
+
add_classes_to_gradio_component(self)
|
| 657 |
+
|
| 658 |
+
script_callbacks.after_component_callback(self, **kwargs)
|
| 659 |
+
|
| 660 |
+
if scripts_current is not None:
|
| 661 |
+
scripts_current.after_component(self, **kwargs)
|
| 662 |
+
|
| 663 |
+
return res
|
| 664 |
+
|
| 665 |
+
|
| 666 |
+
original_IOComponent_init = gr.components.IOComponent.__init__
|
| 667 |
+
gr.components.IOComponent.__init__ = IOComponent_init
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
def BlockContext_init(self, *args, **kwargs):
|
| 671 |
+
res = original_BlockContext_init(self, *args, **kwargs)
|
| 672 |
+
|
| 673 |
+
add_classes_to_gradio_component(self)
|
| 674 |
+
|
| 675 |
+
return res
|
| 676 |
+
|
| 677 |
+
|
| 678 |
+
original_BlockContext_init = gr.blocks.BlockContext.__init__
|
| 679 |
+
gr.blocks.BlockContext.__init__ = BlockContext_init
|
nr/ui_settings.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
|
| 3 |
+
from modules import ui_common, shared, script_callbacks, scripts, sd_models, sysinfo
|
| 4 |
+
from modules.call_queue import wrap_gradio_call
|
| 5 |
+
from modules.shared import opts
|
| 6 |
+
from modules.ui_components import FormRow
|
| 7 |
+
from modules.ui_gradio_extensions import reload_javascript
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def get_value_for_setting(key):
|
| 11 |
+
value = getattr(opts, key)
|
| 12 |
+
|
| 13 |
+
info = opts.data_labels[key]
|
| 14 |
+
args = info.component_args() if callable(info.component_args) else info.component_args or {}
|
| 15 |
+
args = {k: v for k, v in args.items() if k not in {'precision'}}
|
| 16 |
+
|
| 17 |
+
return gr.update(value=value, **args)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def create_setting_component(key, is_quicksettings=False):
|
| 21 |
+
def fun():
|
| 22 |
+
return opts.data[key] if key in opts.data else opts.data_labels[key].default
|
| 23 |
+
|
| 24 |
+
info = opts.data_labels[key]
|
| 25 |
+
t = type(info.default)
|
| 26 |
+
|
| 27 |
+
args = info.component_args() if callable(info.component_args) else info.component_args
|
| 28 |
+
|
| 29 |
+
if info.component is not None:
|
| 30 |
+
comp = info.component
|
| 31 |
+
elif t == str:
|
| 32 |
+
comp = gr.Textbox
|
| 33 |
+
elif t == int:
|
| 34 |
+
comp = gr.Number
|
| 35 |
+
elif t == bool:
|
| 36 |
+
comp = gr.Checkbox
|
| 37 |
+
else:
|
| 38 |
+
raise Exception(f'bad options item type: {t} for key {key}')
|
| 39 |
+
|
| 40 |
+
elem_id = f"setting_{key}"
|
| 41 |
+
|
| 42 |
+
if info.refresh is not None:
|
| 43 |
+
if is_quicksettings:
|
| 44 |
+
res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {}))
|
| 45 |
+
ui_common.create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}")
|
| 46 |
+
else:
|
| 47 |
+
with FormRow():
|
| 48 |
+
res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {}))
|
| 49 |
+
ui_common.create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}")
|
| 50 |
+
else:
|
| 51 |
+
res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {}))
|
| 52 |
+
|
| 53 |
+
return res
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class UiSettings:
|
| 57 |
+
submit = None
|
| 58 |
+
result = None
|
| 59 |
+
interface = None
|
| 60 |
+
components = None
|
| 61 |
+
component_dict = None
|
| 62 |
+
dummy_component = None
|
| 63 |
+
quicksettings_list = None
|
| 64 |
+
quicksettings_names = None
|
| 65 |
+
text_settings = None
|
| 66 |
+
|
| 67 |
+
def run_settings(self, *args):
|
| 68 |
+
changed = []
|
| 69 |
+
|
| 70 |
+
for key, value, comp in zip(opts.data_labels.keys(), args, self.components):
|
| 71 |
+
assert comp == self.dummy_component or opts.same_type(value, opts.data_labels[key].default), f"Bad value for setting {key}: {value}; expecting {type(opts.data_labels[key].default).__name__}"
|
| 72 |
+
|
| 73 |
+
for key, value, comp in zip(opts.data_labels.keys(), args, self.components):
|
| 74 |
+
if comp == self.dummy_component:
|
| 75 |
+
continue
|
| 76 |
+
|
| 77 |
+
if opts.set(key, value):
|
| 78 |
+
changed.append(key)
|
| 79 |
+
|
| 80 |
+
try:
|
| 81 |
+
opts.save(shared.config_filename)
|
| 82 |
+
except RuntimeError:
|
| 83 |
+
return opts.dumpjson(), f'{len(changed)} settings changed without save: {", ".join(changed)}.'
|
| 84 |
+
return opts.dumpjson(), f'{len(changed)} settings changed{": " if changed else ""}{", ".join(changed)}.'
|
| 85 |
+
|
| 86 |
+
def run_settings_single(self, value, key):
|
| 87 |
+
if not opts.same_type(value, opts.data_labels[key].default):
|
| 88 |
+
return gr.update(visible=True), opts.dumpjson()
|
| 89 |
+
|
| 90 |
+
if not opts.set(key, value):
|
| 91 |
+
return gr.update(value=getattr(opts, key)), opts.dumpjson()
|
| 92 |
+
|
| 93 |
+
opts.save(shared.config_filename)
|
| 94 |
+
|
| 95 |
+
return get_value_for_setting(key), opts.dumpjson()
|
| 96 |
+
|
| 97 |
+
def create_ui(self, loadsave, dummy_component):
|
| 98 |
+
self.components = []
|
| 99 |
+
self.component_dict = {}
|
| 100 |
+
self.dummy_component = dummy_component
|
| 101 |
+
|
| 102 |
+
shared.settings_components = self.component_dict
|
| 103 |
+
|
| 104 |
+
script_callbacks.ui_settings_callback()
|
| 105 |
+
opts.reorder()
|
| 106 |
+
|
| 107 |
+
with gr.Blocks(analytics_enabled=False) as settings_interface:
|
| 108 |
+
with gr.Row():
|
| 109 |
+
with gr.Column(scale=6):
|
| 110 |
+
self.submit = gr.Button(value="Apply settings", variant='primary', elem_id="settings_submit")
|
| 111 |
+
with gr.Column():
|
| 112 |
+
restart_gradio = gr.Button(value='Reload UI', variant='primary', elem_id="settings_restart_gradio")
|
| 113 |
+
|
| 114 |
+
self.result = gr.HTML(elem_id="settings_result")
|
| 115 |
+
|
| 116 |
+
self.quicksettings_names = opts.quicksettings_list
|
| 117 |
+
self.quicksettings_names = {x: i for i, x in enumerate(self.quicksettings_names) if x != 'quicksettings'}
|
| 118 |
+
|
| 119 |
+
self.quicksettings_list = []
|
| 120 |
+
|
| 121 |
+
previous_section = None
|
| 122 |
+
current_tab = None
|
| 123 |
+
current_row = None
|
| 124 |
+
with gr.Tabs(elem_id="settings"):
|
| 125 |
+
for i, (k, item) in enumerate(opts.data_labels.items()):
|
| 126 |
+
section_must_be_skipped = item.section[0] is None
|
| 127 |
+
|
| 128 |
+
if previous_section != item.section and not section_must_be_skipped:
|
| 129 |
+
elem_id, text = item.section
|
| 130 |
+
|
| 131 |
+
if current_tab is not None:
|
| 132 |
+
current_row.__exit__()
|
| 133 |
+
current_tab.__exit__()
|
| 134 |
+
|
| 135 |
+
gr.Group()
|
| 136 |
+
current_tab = gr.TabItem(elem_id=f"settings_{elem_id}", label=text)
|
| 137 |
+
current_tab.__enter__()
|
| 138 |
+
current_row = gr.Column(variant='compact')
|
| 139 |
+
current_row.__enter__()
|
| 140 |
+
|
| 141 |
+
previous_section = item.section
|
| 142 |
+
|
| 143 |
+
if k in self.quicksettings_names and not shared.cmd_opts.freeze_settings:
|
| 144 |
+
self.quicksettings_list.append((i, k, item))
|
| 145 |
+
self.components.append(dummy_component)
|
| 146 |
+
elif section_must_be_skipped:
|
| 147 |
+
self.components.append(dummy_component)
|
| 148 |
+
else:
|
| 149 |
+
component = create_setting_component(k)
|
| 150 |
+
self.component_dict[k] = component
|
| 151 |
+
self.components.append(component)
|
| 152 |
+
|
| 153 |
+
if current_tab is not None:
|
| 154 |
+
current_row.__exit__()
|
| 155 |
+
current_tab.__exit__()
|
| 156 |
+
|
| 157 |
+
with gr.TabItem("Defaults", id="defaults", elem_id="settings_tab_defaults"):
|
| 158 |
+
loadsave.create_ui()
|
| 159 |
+
|
| 160 |
+
with gr.TabItem("Sysinfo", id="sysinfo", elem_id="settings_tab_sysinfo"):
|
| 161 |
+
gr.HTML('<a href="./internal/sysinfo-download" class="sysinfo_big_link" download>Download system info</a><br /><a href="./internal/sysinfo">(or open as text in a new page)</a>', elem_id="sysinfo_download")
|
| 162 |
+
|
| 163 |
+
with gr.Row():
|
| 164 |
+
with gr.Column(scale=1):
|
| 165 |
+
sysinfo_check_file = gr.File(label="Check system info for validity", type='binary')
|
| 166 |
+
with gr.Column(scale=1):
|
| 167 |
+
sysinfo_check_output = gr.HTML("", elem_id="sysinfo_validity")
|
| 168 |
+
with gr.Column(scale=100):
|
| 169 |
+
pass
|
| 170 |
+
|
| 171 |
+
with gr.TabItem("Actions", id="actions", elem_id="settings_tab_actions"):
|
| 172 |
+
request_notifications = gr.Button(value='Request browser notifications', elem_id="request_notifications")
|
| 173 |
+
download_localization = gr.Button(value='Download localization template', elem_id="download_localization")
|
| 174 |
+
reload_script_bodies = gr.Button(value='Reload custom script bodies (No ui updates, No restart)', variant='secondary', elem_id="settings_reload_script_bodies")
|
| 175 |
+
with gr.Row():
|
| 176 |
+
unload_sd_model = gr.Button(value='Unload SD checkpoint to free VRAM', elem_id="sett_unload_sd_model")
|
| 177 |
+
reload_sd_model = gr.Button(value='Reload the last SD checkpoint back into VRAM', elem_id="sett_reload_sd_model")
|
| 178 |
+
|
| 179 |
+
with gr.TabItem("Licenses", id="licenses", elem_id="settings_tab_licenses"):
|
| 180 |
+
gr.HTML(shared.html("licenses.html"), elem_id="licenses")
|
| 181 |
+
|
| 182 |
+
gr.Button(value="Show all pages", elem_id="settings_show_all_pages")
|
| 183 |
+
|
| 184 |
+
self.text_settings = gr.Textbox(elem_id="settings_json", value=lambda: opts.dumpjson(), visible=False)
|
| 185 |
+
|
| 186 |
+
unload_sd_model.click(
|
| 187 |
+
fn=sd_models.unload_model_weights,
|
| 188 |
+
inputs=[],
|
| 189 |
+
outputs=[]
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
reload_sd_model.click(
|
| 193 |
+
fn=sd_models.reload_model_weights,
|
| 194 |
+
inputs=[],
|
| 195 |
+
outputs=[]
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
request_notifications.click(
|
| 199 |
+
fn=lambda: None,
|
| 200 |
+
inputs=[],
|
| 201 |
+
outputs=[],
|
| 202 |
+
_js='function(){}'
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
download_localization.click(
|
| 206 |
+
fn=lambda: None,
|
| 207 |
+
inputs=[],
|
| 208 |
+
outputs=[],
|
| 209 |
+
_js='download_localization'
|
| 210 |
+
)
|
| 211 |
+
|
| 212 |
+
def reload_scripts():
|
| 213 |
+
scripts.reload_script_body_only()
|
| 214 |
+
reload_javascript() # need to refresh the html page
|
| 215 |
+
|
| 216 |
+
reload_script_bodies.click(
|
| 217 |
+
fn=reload_scripts,
|
| 218 |
+
inputs=[],
|
| 219 |
+
outputs=[]
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
restart_gradio.click(
|
| 223 |
+
fn=shared.state.request_restart,
|
| 224 |
+
_js='restart_reload',
|
| 225 |
+
inputs=[],
|
| 226 |
+
outputs=[],
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
def check_file(x):
|
| 230 |
+
if x is None:
|
| 231 |
+
return ''
|
| 232 |
+
|
| 233 |
+
if sysinfo.check(x.decode('utf8', errors='ignore')):
|
| 234 |
+
return 'Valid'
|
| 235 |
+
|
| 236 |
+
return 'Invalid'
|
| 237 |
+
|
| 238 |
+
sysinfo_check_file.change(
|
| 239 |
+
fn=check_file,
|
| 240 |
+
inputs=[sysinfo_check_file],
|
| 241 |
+
outputs=[sysinfo_check_output],
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
self.interface = settings_interface
|
| 245 |
+
|
| 246 |
+
def add_quicksettings(self):
|
| 247 |
+
with gr.Row(elem_id="quicksettings", variant="compact"):
|
| 248 |
+
for _i, k, _item in sorted(self.quicksettings_list, key=lambda x: self.quicksettings_names.get(x[1], x[0])):
|
| 249 |
+
component = create_setting_component(k, is_quicksettings=True)
|
| 250 |
+
self.component_dict[k] = component
|
| 251 |
+
|
| 252 |
+
def add_functionality(self, demo):
|
| 253 |
+
self.submit.click(
|
| 254 |
+
fn=wrap_gradio_call(lambda *args: self.run_settings(*args), extra_outputs=[gr.update()]),
|
| 255 |
+
inputs=self.components,
|
| 256 |
+
outputs=[self.text_settings, self.result],
|
| 257 |
+
)
|
| 258 |
+
|
| 259 |
+
for _i, k, _item in self.quicksettings_list:
|
| 260 |
+
component = self.component_dict[k]
|
| 261 |
+
info = opts.data_labels[k]
|
| 262 |
+
|
| 263 |
+
if isinstance(component, gr.Textbox):
|
| 264 |
+
methods = [component.submit, component.blur]
|
| 265 |
+
elif hasattr(component, 'release'):
|
| 266 |
+
methods = [component.release]
|
| 267 |
+
else:
|
| 268 |
+
methods = [component.change]
|
| 269 |
+
|
| 270 |
+
for method in methods:
|
| 271 |
+
method(
|
| 272 |
+
fn=lambda value, k=k: self.run_settings_single(value, key=k),
|
| 273 |
+
inputs=[component],
|
| 274 |
+
outputs=[component, self.text_settings],
|
| 275 |
+
show_progress=info.refresh is not None,
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
button_set_checkpoint = gr.Button('Change checkpoint', elem_id='change_checkpoint', visible=False)
|
| 279 |
+
button_set_checkpoint.click(
|
| 280 |
+
fn=lambda value, _: self.run_settings_single(value, key='sd_model_checkpoint'),
|
| 281 |
+
_js="function(v){ var res = desiredCheckpointName; desiredCheckpointName = ''; return [res || v, null]; }",
|
| 282 |
+
inputs=[self.component_dict['sd_model_checkpoint'], self.dummy_component],
|
| 283 |
+
outputs=[self.component_dict['sd_model_checkpoint'], self.text_settings],
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
component_keys = [k for k in opts.data_labels.keys() if k in self.component_dict]
|
| 287 |
+
|
| 288 |
+
def get_settings_values():
|
| 289 |
+
return [get_value_for_setting(key) for key in component_keys]
|
| 290 |
+
|
| 291 |
+
demo.load(
|
| 292 |
+
fn=get_settings_values,
|
| 293 |
+
inputs=[],
|
| 294 |
+
outputs=[self.component_dict[k] for k in component_keys],
|
| 295 |
+
queue=False,
|
| 296 |
+
)
|