Transformers
Inference Endpoints
vorstcavry commited on
Commit
31a9ae5
1 Parent(s): c7ced54

Upload 3 files

Browse files
Files changed (3) hide show
  1. nr/extra_options_section.py +48 -0
  2. nr/scripts.py +679 -0
  3. 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
+ )