FashGate commited on
Commit
3ceecfc
·
1 Parent(s): f188184

Upload ui.py

Browse files
Files changed (1) hide show
  1. ui.py +1621 -0
ui.py ADDED
@@ -0,0 +1,1621 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+ import json
3
+ import mimetypes
4
+ import os
5
+ import sys
6
+ from functools import reduce
7
+ import warnings
8
+
9
+ import gradio as gr
10
+ import gradio.utils
11
+ import numpy as np
12
+ from PIL import Image, PngImagePlugin # noqa: F401
13
+ from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call
14
+
15
+ from modules import sd_hijack, sd_models, script_callbacks, ui_extensions, deepbooru, sd_vae, extra_networks, ui_common, ui_postprocessing, progress, ui_loadsave, errors, shared_items, ui_settings, timer, sysinfo
16
+ from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML
17
+ from modules.paths import script_path
18
+ from modules.ui_common import create_refresh_button
19
+ from modules.ui_gradio_extensions import reload_javascript
20
+
21
+
22
+ from modules.shared import opts, cmd_opts
23
+
24
+ import modules.codeformer_model
25
+ import modules.generation_parameters_copypaste as parameters_copypaste
26
+ import modules.gfpgan_model
27
+ import modules.hypernetworks.ui
28
+ import modules.scripts
29
+ import modules.shared as shared
30
+ import modules.styles
31
+ import modules.textual_inversion.ui
32
+ from modules import prompt_parser
33
+ from modules.sd_hijack import model_hijack
34
+ from modules.sd_samplers import samplers, samplers_for_img2img
35
+ from modules.textual_inversion import textual_inversion
36
+ import modules.hypernetworks.ui
37
+ from modules.generation_parameters_copypaste import image_from_url_text
38
+ import modules.extras
39
+
40
+ create_setting_component = ui_settings.create_setting_component
41
+
42
+ warnings.filterwarnings("default" if opts.show_warnings else "ignore", category=UserWarning)
43
+
44
+ # this is a fix for Windows users. Without it, javascript files will be served with text/html content-type and the browser will not show any UI
45
+ mimetypes.init()
46
+ mimetypes.add_type('application/javascript', '.js')
47
+
48
+ if not cmd_opts.share and not cmd_opts.listen:
49
+ # fix gradio phoning home
50
+ gradio.utils.version_check = lambda: None
51
+ gradio.utils.get_local_ip_address = lambda: '127.0.0.1'
52
+
53
+ if cmd_opts.ngrok is not None:
54
+ import modules.ngrok as ngrok
55
+ print('ngrok authtoken detected, trying to connect...')
56
+ ngrok.connect(
57
+ cmd_opts.ngrok,
58
+ cmd_opts.port if cmd_opts.port is not None else 7860,
59
+ cmd_opts.ngrok_options
60
+ )
61
+
62
+
63
+ def gr_show(visible=True):
64
+ return {"visible": visible, "__type__": "update"}
65
+
66
+
67
+ sample_img2img = "assets/stable-samples/img2img/sketch-mountains-input.jpg"
68
+ sample_img2img = sample_img2img if os.path.exists(sample_img2img) else None
69
+
70
+ # Using constants for these since the variation selector isn't visible.
71
+ # Important that they exactly match script.js for tooltip to work.
72
+ random_symbol = '\U0001f3b2\ufe0f' # 🎲️
73
+ reuse_symbol = '\u267b\ufe0f' # ♻️
74
+ paste_symbol = '\u2199\ufe0f' # ↙
75
+ refresh_symbol = '\U0001f504' # 🔄
76
+ save_style_symbol = '\U0001f4be' # 💾
77
+ apply_style_symbol = '\U0001f4cb' # 📋
78
+ clear_prompt_symbol = '\U0001f5d1\ufe0f' # 🗑️
79
+ extra_networks_symbol = '\U0001F3B4' # 🎴
80
+ switch_values_symbol = '\U000021C5' # ⇅
81
+ restore_progress_symbol = '\U0001F300' # 🌀
82
+ detect_image_size_symbol = '\U0001F4D0' # 📐
83
+ up_down_symbol = '\u2195\ufe0f' # ↕️
84
+
85
+
86
+ plaintext_to_html = ui_common.plaintext_to_html
87
+
88
+
89
+ def send_gradio_gallery_to_image(x):
90
+ if len(x) == 0:
91
+ return None
92
+ return image_from_url_text(x[0])
93
+
94
+
95
+ def add_style(name: str, prompt: str, negative_prompt: str):
96
+ if name is None:
97
+ return [gr_show() for x in range(4)]
98
+
99
+ style = modules.styles.PromptStyle(name, prompt, negative_prompt)
100
+ shared.prompt_styles.styles[style.name] = style
101
+ # Save all loaded prompt styles: this allows us to update the storage format in the future more easily, because we
102
+ # reserialize all styles every time we save them
103
+ shared.prompt_styles.save_styles(shared.styles_filename)
104
+
105
+ return [gr.Dropdown.update(visible=True, choices=list(shared.prompt_styles.styles)) for _ in range(2)]
106
+
107
+
108
+ def calc_resolution_hires(enable, width, height, hr_scale, hr_resize_x, hr_resize_y):
109
+ from modules import processing, devices
110
+
111
+ if not enable:
112
+ return ""
113
+
114
+ p = processing.StableDiffusionProcessingTxt2Img(width=width, height=height, enable_hr=True, hr_scale=hr_scale, hr_resize_x=hr_resize_x, hr_resize_y=hr_resize_y)
115
+
116
+ with devices.autocast():
117
+ p.init([""], [0], [0])
118
+
119
+ return f"resize: from <span class='resolution'>{p.width}x{p.height}</span> to <span class='resolution'>{p.hr_resize_x or p.hr_upscale_to_x}x{p.hr_resize_y or p.hr_upscale_to_y}</span>"
120
+
121
+
122
+ def resize_from_to_html(width, height, scale_by):
123
+ target_width = int(width * scale_by)
124
+ target_height = int(height * scale_by)
125
+
126
+ if not target_width or not target_height:
127
+ return "no image selected"
128
+
129
+ return f"resize: from <span class='resolution'>{width}x{height}</span> to <span class='resolution'>{target_width}x{target_height}</span>"
130
+
131
+
132
+ def apply_styles(prompt, prompt_neg, styles):
133
+ prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, styles)
134
+ prompt_neg = shared.prompt_styles.apply_negative_styles_to_prompt(prompt_neg, styles)
135
+
136
+ return [gr.Textbox.update(value=prompt), gr.Textbox.update(value=prompt_neg), gr.Dropdown.update(value=[])]
137
+
138
+
139
+ def process_interrogate(interrogation_function, mode, ii_input_dir, ii_output_dir, *ii_singles):
140
+ if mode in {0, 1, 3, 4}:
141
+ return [interrogation_function(ii_singles[mode]), None]
142
+ elif mode == 2:
143
+ return [interrogation_function(ii_singles[mode]["image"]), None]
144
+ elif mode == 5:
145
+ assert not shared.cmd_opts.hide_ui_dir_config, "Launched with --hide-ui-dir-config, batch img2img disabled"
146
+ images = shared.listfiles(ii_input_dir)
147
+ print(f"Will process {len(images)} images.")
148
+ if ii_output_dir != "":
149
+ os.makedirs(ii_output_dir, exist_ok=True)
150
+ else:
151
+ ii_output_dir = ii_input_dir
152
+
153
+ for image in images:
154
+ img = Image.open(image)
155
+ filename = os.path.basename(image)
156
+ left, _ = os.path.splitext(filename)
157
+ print(interrogation_function(img), file=open(os.path.join(ii_output_dir, f"{left}.txt"), 'a', encoding='utf-8'))
158
+
159
+ return [gr.update(), None]
160
+
161
+
162
+ def interrogate(image):
163
+ prompt = shared.interrogator.interrogate(image.convert("RGB"))
164
+ return gr.update() if prompt is None else prompt
165
+
166
+
167
+ def interrogate_deepbooru(image):
168
+ prompt = deepbooru.model.tag(image)
169
+ return gr.update() if prompt is None else prompt
170
+
171
+
172
+ def create_seed_inputs(target_interface):
173
+ with FormRow(elem_id=f"{target_interface}_seed_row", variant="compact"):
174
+ seed = (gr.Textbox if cmd_opts.use_textbox_seed else gr.Number)(label='Seed', value=-1, elem_id=f"{target_interface}_seed")
175
+ seed.style(container=False)
176
+ random_seed = ToolButton(random_symbol, elem_id=f"{target_interface}_random_seed", label='Random seed')
177
+ reuse_seed = ToolButton(reuse_symbol, elem_id=f"{target_interface}_reuse_seed", label='Reuse seed')
178
+
179
+ seed_checkbox = gr.Checkbox(label='Extra', elem_id=f"{target_interface}_subseed_show", value=False)
180
+
181
+ # Components to show/hide based on the 'Extra' checkbox
182
+ seed_extras = []
183
+
184
+ with FormRow(visible=False, elem_id=f"{target_interface}_subseed_row") as seed_extra_row_1:
185
+ seed_extras.append(seed_extra_row_1)
186
+ subseed = gr.Number(label='Variation seed', value=-1, elem_id=f"{target_interface}_subseed")
187
+ subseed.style(container=False)
188
+ random_subseed = ToolButton(random_symbol, elem_id=f"{target_interface}_random_subseed")
189
+ reuse_subseed = ToolButton(reuse_symbol, elem_id=f"{target_interface}_reuse_subseed")
190
+ subseed_strength = gr.Slider(label='Variation strength', value=0.0, minimum=0, maximum=1, step=0.01, elem_id=f"{target_interface}_subseed_strength")
191
+
192
+ with FormRow(visible=False) as seed_extra_row_2:
193
+ seed_extras.append(seed_extra_row_2)
194
+ seed_resize_from_w = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize seed from width", value=0, elem_id=f"{target_interface}_seed_resize_from_w")
195
+ seed_resize_from_h = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize seed from height", value=0, elem_id=f"{target_interface}_seed_resize_from_h")
196
+
197
+ random_seed.click(fn=None, _js="function(){setRandomSeed('" + target_interface + "_seed')}", show_progress=False, inputs=[], outputs=[])
198
+ random_subseed.click(fn=None, _js="function(){setRandomSeed('" + target_interface + "_subseed')}", show_progress=False, inputs=[], outputs=[])
199
+
200
+ def change_visibility(show):
201
+ return {comp: gr_show(show) for comp in seed_extras}
202
+
203
+ seed_checkbox.change(change_visibility, show_progress=False, inputs=[seed_checkbox], outputs=seed_extras)
204
+
205
+ return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox
206
+
207
+
208
+
209
+ def connect_clear_prompt(button):
210
+ """Given clear button, prompt, and token_counter objects, setup clear prompt button click event"""
211
+ button.click(
212
+ _js="clear_prompt",
213
+ fn=None,
214
+ inputs=[],
215
+ outputs=[],
216
+ )
217
+
218
+
219
+ def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: gr.Textbox, dummy_component, is_subseed):
220
+ """ Connects a 'reuse (sub)seed' button's click event so that it copies last used
221
+ (sub)seed value from generation info the to the seed field. If copying subseed and subseed strength
222
+ was 0, i.e. no variation seed was used, it copies the normal seed value instead."""
223
+ def copy_seed(gen_info_string: str, index):
224
+ res = -1
225
+
226
+ try:
227
+ gen_info = json.loads(gen_info_string)
228
+ index -= gen_info.get('index_of_first_image', 0)
229
+
230
+ if is_subseed and gen_info.get('subseed_strength', 0) > 0:
231
+ all_subseeds = gen_info.get('all_subseeds', [-1])
232
+ res = all_subseeds[index if 0 <= index < len(all_subseeds) else 0]
233
+ else:
234
+ all_seeds = gen_info.get('all_seeds', [-1])
235
+ res = all_seeds[index if 0 <= index < len(all_seeds) else 0]
236
+
237
+ except json.decoder.JSONDecodeError:
238
+ if gen_info_string:
239
+ errors.report(f"Error parsing JSON generation info: {gen_info_string}")
240
+
241
+ return [res, gr_show(False)]
242
+
243
+ reuse_seed.click(
244
+ fn=copy_seed,
245
+ _js="(x, y) => [x, selected_gallery_index()]",
246
+ show_progress=False,
247
+ inputs=[generation_info, dummy_component],
248
+ outputs=[seed, dummy_component]
249
+ )
250
+
251
+
252
+ def update_token_counter(text, steps):
253
+ try:
254
+ text, _ = extra_networks.parse_prompt(text)
255
+
256
+ _, prompt_flat_list, _ = prompt_parser.get_multicond_prompt_list([text])
257
+ prompt_schedules = prompt_parser.get_learned_conditioning_prompt_schedules(prompt_flat_list, steps)
258
+
259
+ except Exception:
260
+ # a parsing error can happen here during typing, and we don't want to bother the user with
261
+ # messages related to it in console
262
+ prompt_schedules = [[[steps, text]]]
263
+
264
+ flat_prompts = reduce(lambda list1, list2: list1+list2, prompt_schedules)
265
+ prompts = [prompt_text for step, prompt_text in flat_prompts]
266
+ token_count, max_length = max([model_hijack.get_prompt_lengths(prompt) for prompt in prompts], key=lambda args: args[0])
267
+ return f"<span class='gr-box gr-text-input'>{token_count}/{max_length}</span>"
268
+
269
+
270
+ def create_toprow(is_img2img):
271
+ id_part = "img2img" if is_img2img else "txt2img"
272
+
273
+ with gr.Row(elem_id=f"{id_part}_toprow", variant="compact"):
274
+ with gr.Column(elem_id=f"{id_part}_prompt_container", scale=6):
275
+ with gr.Row():
276
+ with gr.Column(scale=80):
277
+ with gr.Row():
278
+ prompt = gr.Textbox(label="Prompt", elem_id=f"{id_part}_prompt", show_label=False, lines=3, placeholder="Prompt (press Ctrl+Enter or Alt+Enter to generate)", elem_classes=["prompt"])
279
+
280
+ with gr.Row():
281
+ with gr.Column(scale=80):
282
+ with gr.Row():
283
+ negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"{id_part}_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt (press Ctrl+Enter or Alt+Enter to generate)", elem_classes=["prompt"])
284
+
285
+ button_interrogate = None
286
+ button_deepbooru = None
287
+ if is_img2img:
288
+ with gr.Column(scale=1, elem_classes="interrogate-col"):
289
+ button_interrogate = gr.Button('Interrogate\nCLIP', elem_id="interrogate")
290
+ button_deepbooru = gr.Button('Interrogate\nDeepBooru', elem_id="deepbooru")
291
+
292
+ with gr.Column(scale=1, elem_id=f"{id_part}_actions_column"):
293
+ with gr.Row(elem_id=f"{id_part}_generate_box", elem_classes="generate-box"):
294
+ interrupt = gr.Button('Interrupt', elem_id=f"{id_part}_interrupt", elem_classes="generate-box-interrupt")
295
+ skip = gr.Button('Skip', elem_id=f"{id_part}_skip", elem_classes="generate-box-skip")
296
+ submit = gr.Button('Generate', elem_id=f"{id_part}_generate", variant='primary')
297
+
298
+ skip.click(
299
+ fn=lambda: shared.state.skip(),
300
+ inputs=[],
301
+ outputs=[],
302
+ )
303
+
304
+ interrupt.click(
305
+ fn=lambda: shared.state.interrupt(),
306
+ inputs=[],
307
+ outputs=[],
308
+ )
309
+
310
+ with gr.Row(elem_id=f"{id_part}_tools"):
311
+ paste = ToolButton(value=paste_symbol, elem_id="paste")
312
+ clear_prompt_button = ToolButton(value=clear_prompt_symbol, elem_id=f"{id_part}_clear_prompt")
313
+ extra_networks_button = ToolButton(value=extra_networks_symbol, elem_id=f"{id_part}_extra_networks")
314
+ prompt_style_apply = ToolButton(value=apply_style_symbol, elem_id=f"{id_part}_style_apply")
315
+ save_style = ToolButton(value=save_style_symbol, elem_id=f"{id_part}_style_create")
316
+ restore_progress_button = ToolButton(value=restore_progress_symbol, elem_id=f"{id_part}_restore_progress", visible=False)
317
+
318
+ token_counter = gr.HTML(value="<span>0/75</span>", elem_id=f"{id_part}_token_counter", elem_classes=["token-counter"])
319
+ token_button = gr.Button(visible=False, elem_id=f"{id_part}_token_button")
320
+ negative_token_counter = gr.HTML(value="<span>0/75</span>", elem_id=f"{id_part}_negative_token_counter", elem_classes=["token-counter"])
321
+ negative_token_button = gr.Button(visible=False, elem_id=f"{id_part}_negative_token_button")
322
+
323
+ clear_prompt_button.click(
324
+ fn=lambda *x: x,
325
+ _js="confirm_clear_prompt",
326
+ inputs=[prompt, negative_prompt],
327
+ outputs=[prompt, negative_prompt],
328
+ )
329
+
330
+ with gr.Row(elem_id=f"{id_part}_styles_row"):
331
+ prompt_styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[k for k, v in shared.prompt_styles.styles.items()], value=[], multiselect=True)
332
+ create_refresh_button(prompt_styles, shared.prompt_styles.reload, lambda: {"choices": [k for k, v in shared.prompt_styles.styles.items()]}, f"refresh_{id_part}_styles")
333
+
334
+ return prompt, prompt_styles, negative_prompt, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, extra_networks_button, token_counter, token_button, negative_token_counter, negative_token_button, restore_progress_button
335
+
336
+
337
+ def setup_progressbar(*args, **kwargs):
338
+ pass
339
+
340
+
341
+ def apply_setting(key, value):
342
+ if value is None:
343
+ return gr.update()
344
+
345
+ if shared.cmd_opts.freeze_settings:
346
+ return gr.update()
347
+
348
+ # dont allow model to be swapped when model hash exists in prompt
349
+ if key == "sd_model_checkpoint" and opts.disable_weights_auto_swap:
350
+ return gr.update()
351
+
352
+ if key == "sd_model_checkpoint":
353
+ ckpt_info = sd_models.get_closet_checkpoint_match(value)
354
+
355
+ if ckpt_info is not None:
356
+ value = ckpt_info.title
357
+ else:
358
+ return gr.update()
359
+
360
+ comp_args = opts.data_labels[key].component_args
361
+ if comp_args and isinstance(comp_args, dict) and comp_args.get('visible') is False:
362
+ return
363
+
364
+ valtype = type(opts.data_labels[key].default)
365
+ oldval = opts.data.get(key, None)
366
+ opts.data[key] = valtype(value) if valtype != type(None) else value
367
+ if oldval != value and opts.data_labels[key].onchange is not None:
368
+ opts.data_labels[key].onchange()
369
+
370
+ opts.save(shared.config_filename)
371
+ return getattr(opts, key)
372
+
373
+
374
+ def create_output_panel(tabname, outdir):
375
+ return ui_common.create_output_panel(tabname, outdir)
376
+
377
+
378
+ def create_sampler_and_steps_selection(choices, tabname):
379
+ if opts.samplers_in_dropdown:
380
+ with FormRow(elem_id=f"sampler_selection_{tabname}"):
381
+ sampler_index = gr.Dropdown(label='Sampling method', elem_id=f"{tabname}_sampling", choices=[x.name for x in choices], value=choices[0].name, type="index")
382
+ steps = gr.Slider(minimum=1, maximum=150, step=1, elem_id=f"{tabname}_steps", label="Sampling steps", value=20)
383
+ else:
384
+ with FormGroup(elem_id=f"sampler_selection_{tabname}"):
385
+ steps = gr.Slider(minimum=1, maximum=150, step=1, elem_id=f"{tabname}_steps", label="Sampling steps", value=20)
386
+ sampler_index = gr.Radio(label='Sampling method', elem_id=f"{tabname}_sampling", choices=[x.name for x in choices], value=choices[0].name, type="index")
387
+
388
+ return steps, sampler_index
389
+
390
+
391
+ def ordered_ui_categories():
392
+ user_order = {x.strip(): i * 2 + 1 for i, x in enumerate(shared.opts.ui_reorder_list)}
393
+
394
+ for _, category in sorted(enumerate(shared_items.ui_reorder_categories()), key=lambda x: user_order.get(x[1], x[0] * 2 + 0)):
395
+ yield category
396
+
397
+
398
+ def create_override_settings_dropdown(tabname, row):
399
+ dropdown = gr.Dropdown([], label="Override settings", visible=False, elem_id=f"{tabname}_override_settings", multiselect=True)
400
+
401
+ dropdown.change(
402
+ fn=lambda x: gr.Dropdown.update(visible=bool(x)),
403
+ inputs=[dropdown],
404
+ outputs=[dropdown],
405
+ )
406
+
407
+ return dropdown
408
+
409
+
410
+ def create_ui():
411
+ import modules.img2img
412
+ import modules.txt2img
413
+
414
+ reload_javascript()
415
+
416
+ parameters_copypaste.reset()
417
+
418
+ modules.scripts.scripts_current = modules.scripts.scripts_txt2img
419
+ modules.scripts.scripts_txt2img.initialize_scripts(is_img2img=False)
420
+
421
+ with gr.Blocks(analytics_enabled=False) as txt2img_interface:
422
+ txt2img_prompt, txt2img_prompt_styles, txt2img_negative_prompt, submit, _, _, txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, extra_networks_button, token_counter, token_button, negative_token_counter, negative_token_button, restore_progress_button = create_toprow(is_img2img=False)
423
+
424
+ dummy_component = gr.Label(visible=False)
425
+ txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="binary", visible=False)
426
+
427
+ with FormRow(variant='compact', elem_id="txt2img_extra_networks", visible=False) as extra_networks:
428
+ from modules import ui_extra_networks
429
+ extra_networks_ui = ui_extra_networks.create_ui(extra_networks, extra_networks_button, 'txt2img')
430
+
431
+ with gr.Row().style(equal_height=False):
432
+ with gr.Column(variant='compact', elem_id="txt2img_settings"):
433
+ modules.scripts.scripts_txt2img.prepare_ui()
434
+
435
+ for category in ordered_ui_categories():
436
+ if category == "sampler":
437
+ steps, sampler_index = create_sampler_and_steps_selection(samplers, "txt2img")
438
+
439
+ elif category == "dimensions":
440
+ with FormRow():
441
+ with gr.Column(elem_id="txt2img_column_size", scale=4):
442
+ width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="txt2img_width")
443
+ height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="txt2img_height")
444
+
445
+ with gr.Column(elem_id="txt2img_dimensions_row", scale=1, elem_classes="dimensions-tools"):
446
+ res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="txt2img_res_switch_btn", label="Switch dims")
447
+
448
+ if opts.dimensions_and_batch_together:
449
+ with gr.Column(elem_id="txt2img_column_batch"):
450
+ batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="txt2img_batch_count")
451
+ batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size', value=1, elem_id="txt2img_batch_size")
452
+
453
+ elif category == "cfg":
454
+ cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=7.0, elem_id="txt2img_cfg_scale")
455
+
456
+ elif category == "seed":
457
+ seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox = create_seed_inputs('txt2img')
458
+
459
+ elif category == "checkboxes":
460
+ with FormRow(elem_classes="checkboxes-row", variant="compact"):
461
+ restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(shared.face_restorers) > 1, elem_id="txt2img_restore_faces")
462
+ tiling = gr.Checkbox(label='Tiling', value=False, elem_id="txt2img_tiling")
463
+ enable_hr = gr.Checkbox(label='Hires. fix', value=False, elem_id="txt2img_enable_hr")
464
+ hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False)
465
+
466
+ elif category == "hires_fix":
467
+ with FormGroup(visible=False, elem_id="txt2img_hires_fix") as hr_options:
468
+ with FormRow(elem_id="txt2img_hires_fix_row1", variant="compact"):
469
+ hr_upscaler = gr.Dropdown(label="Upscaler", elem_id="txt2img_hr_upscaler", choices=[*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]], value=shared.latent_upscale_default_mode)
470
+ hr_second_pass_steps = gr.Slider(minimum=0, maximum=150, step=1, label='Hires steps', value=0, elem_id="txt2img_hires_steps")
471
+ denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising strength', value=0.7, elem_id="txt2img_denoising_strength")
472
+
473
+ with FormRow(elem_id="txt2img_hires_fix_row2", variant="compact"):
474
+ hr_scale = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label="Upscale by", value=2.0, elem_id="txt2img_hr_scale")
475
+ hr_resize_x = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize width to", value=0, elem_id="txt2img_hr_resize_x")
476
+ hr_resize_y = gr.Slider(minimum=0, maximum=2048, step=8, label="Resize height to", value=0, elem_id="txt2img_hr_resize_y")
477
+
478
+ with FormRow(elem_id="txt2img_hires_fix_row3", variant="compact", visible=opts.hires_fix_show_sampler) as hr_sampler_container:
479
+ hr_sampler_index = gr.Dropdown(label='Hires sampling method', elem_id="hr_sampler", choices=["Use same sampler"] + [x.name for x in samplers_for_img2img], value="Use same sampler", type="index")
480
+
481
+ with FormRow(elem_id="txt2img_hires_fix_row4", variant="compact", visible=opts.hires_fix_show_prompts) as hr_prompts_container:
482
+ with gr.Column(scale=80):
483
+ with gr.Row():
484
+ hr_prompt = gr.Textbox(label="Hires prompt", elem_id="hires_prompt", show_label=False, lines=3, placeholder="Prompt for hires fix pass.\nLeave empty to use the same prompt as in first pass.", elem_classes=["prompt"])
485
+ with gr.Column(scale=80):
486
+ with gr.Row():
487
+ hr_negative_prompt = gr.Textbox(label="Hires negative prompt", elem_id="hires_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt for hires fix pass.\nLeave empty to use the same negative prompt as in first pass.", elem_classes=["prompt"])
488
+
489
+ elif category == "batch":
490
+ if not opts.dimensions_and_batch_together:
491
+ with FormRow(elem_id="txt2img_column_batch"):
492
+ batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="txt2img_batch_count")
493
+ batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size', value=1, elem_id="txt2img_batch_size")
494
+
495
+ elif category == "override_settings":
496
+ with FormRow(elem_id="txt2img_override_settings_row") as row:
497
+ override_settings = create_override_settings_dropdown('txt2img', row)
498
+
499
+ elif category == "scripts":
500
+ with FormGroup(elem_id="txt2img_script_container"):
501
+ custom_inputs = modules.scripts.scripts_txt2img.setup_ui()
502
+
503
+ else:
504
+ modules.scripts.scripts_txt2img.setup_ui_for_section(category)
505
+
506
+ hr_resolution_preview_inputs = [enable_hr, width, height, hr_scale, hr_resize_x, hr_resize_y]
507
+
508
+ for component in hr_resolution_preview_inputs:
509
+ event = component.release if isinstance(component, gr.Slider) else component.change
510
+
511
+ event(
512
+ fn=calc_resolution_hires,
513
+ inputs=hr_resolution_preview_inputs,
514
+ outputs=[hr_final_resolution],
515
+ show_progress=False,
516
+ )
517
+ event(
518
+ None,
519
+ _js="onCalcResolutionHires",
520
+ inputs=hr_resolution_preview_inputs,
521
+ outputs=[],
522
+ show_progress=False,
523
+ )
524
+
525
+ txt2img_gallery, generation_info, html_info, html_log = create_output_panel("txt2img", opts.outdir_txt2img_samples)
526
+
527
+ connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False)
528
+ connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True)
529
+
530
+ txt2img_args = dict(
531
+ fn=wrap_gradio_gpu_call(modules.txt2img.txt2img, extra_outputs=[None, '', '']),
532
+ _js="submit",
533
+ inputs=[
534
+ dummy_component,
535
+ txt2img_prompt,
536
+ txt2img_negative_prompt,
537
+ txt2img_prompt_styles,
538
+ steps,
539
+ sampler_index,
540
+ restore_faces,
541
+ tiling,
542
+ batch_count,
543
+ batch_size,
544
+ cfg_scale,
545
+ seed,
546
+ subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox,
547
+ height,
548
+ width,
549
+ enable_hr,
550
+ denoising_strength,
551
+ hr_scale,
552
+ hr_upscaler,
553
+ hr_second_pass_steps,
554
+ hr_resize_x,
555
+ hr_resize_y,
556
+ hr_sampler_index,
557
+ hr_prompt,
558
+ hr_negative_prompt,
559
+ override_settings,
560
+
561
+ ] + custom_inputs,
562
+
563
+ outputs=[
564
+ txt2img_gallery,
565
+ generation_info,
566
+ html_info,
567
+ html_log,
568
+ ],
569
+ show_progress=False,
570
+ )
571
+
572
+ txt2img_prompt.submit(**txt2img_args)
573
+ submit.click(**txt2img_args)
574
+
575
+ res_switch_btn.click(fn=None, _js="function(){switchWidthHeight('txt2img')}", inputs=None, outputs=None, show_progress=False)
576
+
577
+ restore_progress_button.click(
578
+ fn=progress.restore_progress,
579
+ _js="restoreProgressTxt2img",
580
+ inputs=[dummy_component],
581
+ outputs=[
582
+ txt2img_gallery,
583
+ generation_info,
584
+ html_info,
585
+ html_log,
586
+ ],
587
+ show_progress=False,
588
+ )
589
+
590
+ txt_prompt_img.change(
591
+ fn=modules.images.image_data,
592
+ inputs=[
593
+ txt_prompt_img
594
+ ],
595
+ outputs=[
596
+ txt2img_prompt,
597
+ txt_prompt_img
598
+ ],
599
+ show_progress=False,
600
+ )
601
+
602
+ enable_hr.change(
603
+ fn=lambda x: gr_show(x),
604
+ inputs=[enable_hr],
605
+ outputs=[hr_options],
606
+ show_progress = False,
607
+ )
608
+
609
+ txt2img_paste_fields = [
610
+ (txt2img_prompt, "Prompt"),
611
+ (txt2img_negative_prompt, "Negative prompt"),
612
+ (steps, "Steps"),
613
+ (sampler_index, "Sampler"),
614
+ (restore_faces, "Face restoration"),
615
+ (cfg_scale, "CFG scale"),
616
+ (seed, "Seed"),
617
+ (width, "Size-1"),
618
+ (height, "Size-2"),
619
+ (batch_size, "Batch size"),
620
+ (subseed, "Variation seed"),
621
+ (subseed_strength, "Variation seed strength"),
622
+ (seed_resize_from_w, "Seed resize from-1"),
623
+ (seed_resize_from_h, "Seed resize from-2"),
624
+ (txt2img_prompt_styles, lambda d: d["Styles array"] if isinstance(d.get("Styles array"), list) else gr.update()),
625
+ (denoising_strength, "Denoising strength"),
626
+ (enable_hr, lambda d: "Denoising strength" in d),
627
+ (hr_options, lambda d: gr.Row.update(visible="Denoising strength" in d)),
628
+ (hr_scale, "Hires upscale"),
629
+ (hr_upscaler, "Hires upscaler"),
630
+ (hr_second_pass_steps, "Hires steps"),
631
+ (hr_resize_x, "Hires resize-1"),
632
+ (hr_resize_y, "Hires resize-2"),
633
+ (hr_sampler_index, "Hires sampler"),
634
+ (hr_sampler_container, lambda d: gr.update(visible=True) if d.get("Hires sampler", "Use same sampler") != "Use same sampler" else gr.update()),
635
+ (hr_prompt, "Hires prompt"),
636
+ (hr_negative_prompt, "Hires negative prompt"),
637
+ (hr_prompts_container, lambda d: gr.update(visible=True) if d.get("Hires prompt", "") != "" or d.get("Hires negative prompt", "") != "" else gr.update()),
638
+ *modules.scripts.scripts_txt2img.infotext_fields
639
+ ]
640
+ parameters_copypaste.add_paste_fields("txt2img", None, txt2img_paste_fields, override_settings)
641
+ parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(
642
+ paste_button=txt2img_paste, tabname="txt2img", source_text_component=txt2img_prompt, source_image_component=None,
643
+ ))
644
+
645
+ txt2img_preview_params = [
646
+ txt2img_prompt,
647
+ txt2img_negative_prompt,
648
+ steps,
649
+ sampler_index,
650
+ cfg_scale,
651
+ seed,
652
+ width,
653
+ height,
654
+ ]
655
+
656
+ token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_prompt, steps], outputs=[token_counter])
657
+ negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_negative_prompt, steps], outputs=[negative_token_counter])
658
+
659
+ ui_extra_networks.setup_ui(extra_networks_ui, txt2img_gallery)
660
+
661
+ modules.scripts.scripts_current = modules.scripts.scripts_img2img
662
+ modules.scripts.scripts_img2img.initialize_scripts(is_img2img=True)
663
+
664
+ with gr.Blocks(analytics_enabled=False) as img2img_interface:
665
+ img2img_prompt, img2img_prompt_styles, img2img_negative_prompt, submit, img2img_interrogate, img2img_deepbooru, img2img_prompt_style_apply, img2img_save_style, img2img_paste, extra_networks_button, token_counter, token_button, negative_token_counter, negative_token_button, restore_progress_button = create_toprow(is_img2img=True)
666
+
667
+ img2img_prompt_img = gr.File(label="", elem_id="img2img_prompt_image", file_count="single", type="binary", visible=False)
668
+
669
+ with FormRow(variant='compact', elem_id="img2img_extra_networks", visible=False) as extra_networks:
670
+ from modules import ui_extra_networks
671
+ extra_networks_ui_img2img = ui_extra_networks.create_ui(extra_networks, extra_networks_button, 'img2img')
672
+
673
+ with FormRow().style(equal_height=False):
674
+ with gr.Column(variant='compact', elem_id="img2img_settings"):
675
+ copy_image_buttons = []
676
+ copy_image_destinations = {}
677
+
678
+ def add_copy_image_controls(tab_name, elem):
679
+ with gr.Row(variant="compact", elem_id=f"img2img_copy_to_{tab_name}"):
680
+ gr.HTML("Copy image to: ", elem_id=f"img2img_label_copy_to_{tab_name}")
681
+
682
+ for title, name in zip(['img2img', 'sketch', 'inpaint', 'inpaint sketch'], ['img2img', 'sketch', 'inpaint', 'inpaint_sketch']):
683
+ if name == tab_name:
684
+ gr.Button(title, interactive=False)
685
+ copy_image_destinations[name] = elem
686
+ continue
687
+
688
+ button = gr.Button(title)
689
+ copy_image_buttons.append((button, name, elem))
690
+
691
+ with gr.Tabs(elem_id="mode_img2img"):
692
+ img2img_selected_tab = gr.State(0)
693
+
694
+ with gr.TabItem('img2img', id='img2img', elem_id="img2img_img2img_tab") as tab_img2img:
695
+ init_img = gr.Image(label="Image for img2img", elem_id="img2img_image", show_label=False, source="upload", interactive=True, type="pil", tool="editor", image_mode="RGBA").style(height=opts.img2img_editor_height)
696
+ add_copy_image_controls('img2img', init_img)
697
+
698
+ with gr.TabItem('Sketch', id='img2img_sketch', elem_id="img2img_img2img_sketch_tab") as tab_sketch:
699
+ sketch = gr.Image(label="Image for img2img", elem_id="img2img_sketch", show_label=False, source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA").style(height=opts.img2img_editor_height)
700
+ add_copy_image_controls('sketch', sketch)
701
+
702
+ with gr.TabItem('Inpaint', id='inpaint', elem_id="img2img_inpaint_tab") as tab_inpaint:
703
+ init_img_with_mask = gr.Image(label="Image for inpainting with mask", show_label=False, elem_id="img2maskimg", source="upload", interactive=True, type="pil", tool="sketch", image_mode="RGBA").style(height=opts.img2img_editor_height)
704
+ add_copy_image_controls('inpaint', init_img_with_mask)
705
+
706
+ with gr.TabItem('Inpaint sketch', id='inpaint_sketch', elem_id="img2img_inpaint_sketch_tab") as tab_inpaint_color:
707
+ inpaint_color_sketch = gr.Image(label="Color sketch inpainting", show_label=False, elem_id="inpaint_sketch", source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA").style(height=opts.img2img_editor_height)
708
+ inpaint_color_sketch_orig = gr.State(None)
709
+ add_copy_image_controls('inpaint_sketch', inpaint_color_sketch)
710
+
711
+ def update_orig(image, state):
712
+ if image is not None:
713
+ same_size = state is not None and state.size == image.size
714
+ has_exact_match = np.any(np.all(np.array(image) == np.array(state), axis=-1))
715
+ edited = same_size and has_exact_match
716
+ return image if not edited or state is None else state
717
+
718
+ inpaint_color_sketch.change(update_orig, [inpaint_color_sketch, inpaint_color_sketch_orig], inpaint_color_sketch_orig)
719
+
720
+ with gr.TabItem('Inpaint upload', id='inpaint_upload', elem_id="img2img_inpaint_upload_tab") as tab_inpaint_upload:
721
+ init_img_inpaint = gr.Image(label="Image for img2img", show_label=False, source="upload", interactive=True, type="pil", elem_id="img_inpaint_base")
722
+ init_mask_inpaint = gr.Image(label="Mask", source="upload", interactive=True, type="pil", elem_id="img_inpaint_mask")
723
+
724
+ with gr.TabItem('Batch', id='batch', elem_id="img2img_batch_tab") as tab_batch:
725
+ hidden = '<br>Disabled when launched with --hide-ui-dir-config.' if shared.cmd_opts.hide_ui_dir_config else ''
726
+ gr.HTML(
727
+ "<p style='padding-bottom: 1em;' class=\"text-gray-500\">Process images in a directory on the same machine where the server is running." +
728
+ "<br>Use an empty output directory to save pictures normally instead of writing to the output directory." +
729
+ f"<br>Add inpaint batch mask directory to enable inpaint batch processing."
730
+ f"{hidden}</p>"
731
+ )
732
+ img2img_batch_input_dir = gr.Textbox(label="Input directory", **shared.hide_dirs, elem_id="img2img_batch_input_dir")
733
+ img2img_batch_output_dir = gr.Textbox(label="Output directory", **shared.hide_dirs, elem_id="img2img_batch_output_dir")
734
+ img2img_batch_inpaint_mask_dir = gr.Textbox(label="Inpaint batch mask directory (required for inpaint batch processing only)", **shared.hide_dirs, elem_id="img2img_batch_inpaint_mask_dir")
735
+ with gr.Accordion("PNG info", open=False):
736
+ img2img_batch_use_png_info = gr.Checkbox(label="Append png info to prompts", **shared.hide_dirs, elem_id="img2img_batch_use_png_info")
737
+ img2img_batch_png_info_dir = gr.Textbox(label="PNG info directory", **shared.hide_dirs, placeholder="Leave empty to use input directory", elem_id="img2img_batch_png_info_dir")
738
+ img2img_batch_png_info_props = gr.CheckboxGroup(["Prompt", "Negative prompt", "Seed", "CFG scale", "Sampler", "Steps"], label="Parameters to take from png info", info="Prompts from png info will be appended to prompts set in ui.")
739
+
740
+ img2img_tabs = [tab_img2img, tab_sketch, tab_inpaint, tab_inpaint_color, tab_inpaint_upload, tab_batch]
741
+
742
+ for i, tab in enumerate(img2img_tabs):
743
+ tab.select(fn=lambda tabnum=i: tabnum, inputs=[], outputs=[img2img_selected_tab])
744
+
745
+ def copy_image(img):
746
+ if isinstance(img, dict) and 'image' in img:
747
+ return img['image']
748
+
749
+ return img
750
+
751
+ for button, name, elem in copy_image_buttons:
752
+ button.click(
753
+ fn=copy_image,
754
+ inputs=[elem],
755
+ outputs=[copy_image_destinations[name]],
756
+ )
757
+ button.click(
758
+ fn=lambda: None,
759
+ _js=f"switch_to_{name.replace(' ', '_')}",
760
+ inputs=[],
761
+ outputs=[],
762
+ )
763
+
764
+ with FormRow():
765
+ resize_mode = gr.Radio(label="Resize mode", elem_id="resize_mode", choices=["Just resize", "Crop and resize", "Resize and fill", "Just resize (latent upscale)"], type="index", value="Just resize")
766
+
767
+ modules.scripts.scripts_img2img.prepare_ui()
768
+
769
+ for category in ordered_ui_categories():
770
+ if category == "sampler":
771
+ steps, sampler_index = create_sampler_and_steps_selection(samplers_for_img2img, "img2img")
772
+
773
+ elif category == "dimensions":
774
+ with FormRow():
775
+ with gr.Column(elem_id="img2img_column_size", scale=4):
776
+ selected_scale_tab = gr.State(value=0)
777
+
778
+ with gr.Tabs():
779
+ with gr.Tab(label="Resize to", elem_id="img2img_tab_resize_to") as tab_scale_to:
780
+ with FormRow():
781
+ with gr.Column(elem_id="img2img_column_size", scale=4):
782
+ width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="img2img_width")
783
+ height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="img2img_height")
784
+ with gr.Column(elem_id="img2img_dimensions_row", scale=1, elem_classes="dimensions-tools"):
785
+ res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn")
786
+ detect_image_size_btn = ToolButton(value=detect_image_size_symbol, elem_id="img2img_detect_image_size_btn")
787
+
788
+ with gr.Tab(label="Resize by", elem_id="img2img_tab_resize_by") as tab_scale_by:
789
+ scale_by = gr.Slider(minimum=0.05, maximum=4.0, step=0.05, label="Scale", value=1.0, elem_id="img2img_scale")
790
+
791
+ with FormRow():
792
+ scale_by_html = FormHTML(resize_from_to_html(0, 0, 0.0), elem_id="img2img_scale_resolution_preview")
793
+ gr.Slider(label="Unused", elem_id="img2img_unused_scale_by_slider")
794
+ button_update_resize_to = gr.Button(visible=False, elem_id="img2img_update_resize_to")
795
+
796
+ on_change_args = dict(
797
+ fn=resize_from_to_html,
798
+ _js="currentImg2imgSourceResolution",
799
+ inputs=[dummy_component, dummy_component, scale_by],
800
+ outputs=scale_by_html,
801
+ show_progress=False,
802
+ )
803
+
804
+ scale_by.release(**on_change_args)
805
+ button_update_resize_to.click(**on_change_args)
806
+
807
+ # the code below is meant to update the resolution label after the image in the image selection UI has changed.
808
+ # as it is now the event keeps firing continuously for inpaint edits, which ruins the page with constant requests.
809
+ # I assume this must be a gradio bug and for now we'll just do it for non-inpaint inputs.
810
+ for component in [init_img, sketch]:
811
+ component.change(fn=lambda: None, _js="updateImg2imgResizeToTextAfterChangingImage", inputs=[], outputs=[], show_progress=False)
812
+
813
+ tab_scale_to.select(fn=lambda: 0, inputs=[], outputs=[selected_scale_tab])
814
+ tab_scale_by.select(fn=lambda: 1, inputs=[], outputs=[selected_scale_tab])
815
+
816
+ if opts.dimensions_and_batch_together:
817
+ with gr.Column(elem_id="img2img_column_batch"):
818
+ batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="img2img_batch_count")
819
+ batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size', value=1, elem_id="img2img_batch_size")
820
+
821
+ elif category == "cfg":
822
+ with FormGroup():
823
+ with FormRow():
824
+ cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=7.0, elem_id="img2img_cfg_scale")
825
+ image_cfg_scale = gr.Slider(minimum=0, maximum=3.0, step=0.05, label='Image CFG Scale', value=1.5, elem_id="img2img_image_cfg_scale", visible=False)
826
+ denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising strength', value=0.75, elem_id="img2img_denoising_strength")
827
+
828
+ elif category == "seed":
829
+ seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox = create_seed_inputs('img2img')
830
+
831
+ elif category == "checkboxes":
832
+ with FormRow(elem_classes="checkboxes-row", variant="compact"):
833
+ restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(shared.face_restorers) > 1, elem_id="img2img_restore_faces")
834
+ tiling = gr.Checkbox(label='Tiling', value=False, elem_id="img2img_tiling")
835
+
836
+ elif category == "batch":
837
+ if not opts.dimensions_and_batch_together:
838
+ with FormRow(elem_id="img2img_column_batch"):
839
+ batch_count = gr.Slider(minimum=1, step=1, label='Batch count', value=1, elem_id="img2img_batch_count")
840
+ batch_size = gr.Slider(minimum=1, maximum=8, step=1, label='Batch size', value=1, elem_id="img2img_batch_size")
841
+
842
+ elif category == "override_settings":
843
+ with FormRow(elem_id="img2img_override_settings_row") as row:
844
+ override_settings = create_override_settings_dropdown('img2img', row)
845
+
846
+ elif category == "scripts":
847
+ with FormGroup(elem_id="img2img_script_container"):
848
+ custom_inputs = modules.scripts.scripts_img2img.setup_ui()
849
+
850
+ elif category == "inpaint":
851
+ with FormGroup(elem_id="inpaint_controls", visible=False) as inpaint_controls:
852
+ with FormRow():
853
+ mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=4, elem_id="img2img_mask_blur")
854
+ mask_alpha = gr.Slider(label="Mask transparency", visible=False, elem_id="img2img_mask_alpha")
855
+
856
+ with FormRow():
857
+ inpainting_mask_invert = gr.Radio(label='Mask mode', choices=['Inpaint masked', 'Inpaint not masked'], value='Inpaint masked', type="index", elem_id="img2img_mask_mode")
858
+
859
+ with FormRow():
860
+ inpainting_fill = gr.Radio(label='Masked content', choices=['fill', 'original', 'latent noise', 'latent nothing'], value='original', type="index", elem_id="img2img_inpainting_fill")
861
+
862
+ with FormRow():
863
+ with gr.Column():
864
+ inpaint_full_res = gr.Radio(label="Inpaint area", choices=["Whole picture", "Only masked"], type="index", value="Whole picture", elem_id="img2img_inpaint_full_res")
865
+
866
+ with gr.Column(scale=4):
867
+ inpaint_full_res_padding = gr.Slider(label='Only masked padding, pixels', minimum=0, maximum=256, step=4, value=32, elem_id="img2img_inpaint_full_res_padding")
868
+
869
+ def select_img2img_tab(tab):
870
+ return gr.update(visible=tab in [2, 3, 4]), gr.update(visible=tab == 3),
871
+
872
+ for i, elem in enumerate(img2img_tabs):
873
+ elem.select(
874
+ fn=lambda tab=i: select_img2img_tab(tab),
875
+ inputs=[],
876
+ outputs=[inpaint_controls, mask_alpha],
877
+ )
878
+ else:
879
+ modules.scripts.scripts_img2img.setup_ui_for_section(category)
880
+
881
+ img2img_gallery, generation_info, html_info, html_log = create_output_panel("img2img", opts.outdir_img2img_samples)
882
+
883
+ connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False)
884
+ connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True)
885
+
886
+ img2img_prompt_img.change(
887
+ fn=modules.images.image_data,
888
+ inputs=[
889
+ img2img_prompt_img
890
+ ],
891
+ outputs=[
892
+ img2img_prompt,
893
+ img2img_prompt_img
894
+ ],
895
+ show_progress=False,
896
+ )
897
+
898
+ img2img_args = dict(
899
+ fn=wrap_gradio_gpu_call(modules.img2img.img2img, extra_outputs=[None, '', '']),
900
+ _js="submit_img2img",
901
+ inputs=[
902
+ dummy_component,
903
+ dummy_component,
904
+ img2img_prompt,
905
+ img2img_negative_prompt,
906
+ img2img_prompt_styles,
907
+ init_img,
908
+ sketch,
909
+ init_img_with_mask,
910
+ inpaint_color_sketch,
911
+ inpaint_color_sketch_orig,
912
+ init_img_inpaint,
913
+ init_mask_inpaint,
914
+ steps,
915
+ sampler_index,
916
+ mask_blur,
917
+ mask_alpha,
918
+ inpainting_fill,
919
+ restore_faces,
920
+ tiling,
921
+ batch_count,
922
+ batch_size,
923
+ cfg_scale,
924
+ image_cfg_scale,
925
+ denoising_strength,
926
+ seed,
927
+ subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox,
928
+ selected_scale_tab,
929
+ height,
930
+ width,
931
+ scale_by,
932
+ resize_mode,
933
+ inpaint_full_res,
934
+ inpaint_full_res_padding,
935
+ inpainting_mask_invert,
936
+ img2img_batch_input_dir,
937
+ img2img_batch_output_dir,
938
+ img2img_batch_inpaint_mask_dir,
939
+ override_settings,
940
+ img2img_batch_use_png_info,
941
+ img2img_batch_png_info_props,
942
+ img2img_batch_png_info_dir,
943
+ ] + custom_inputs,
944
+ outputs=[
945
+ img2img_gallery,
946
+ generation_info,
947
+ html_info,
948
+ html_log,
949
+ ],
950
+ show_progress=False,
951
+ )
952
+
953
+ interrogate_args = dict(
954
+ _js="get_img2img_tab_index",
955
+ inputs=[
956
+ dummy_component,
957
+ img2img_batch_input_dir,
958
+ img2img_batch_output_dir,
959
+ init_img,
960
+ sketch,
961
+ init_img_with_mask,
962
+ inpaint_color_sketch,
963
+ init_img_inpaint,
964
+ ],
965
+ outputs=[img2img_prompt, dummy_component],
966
+ )
967
+
968
+ img2img_prompt.submit(**img2img_args)
969
+ submit.click(**img2img_args)
970
+
971
+ res_switch_btn.click(fn=None, _js="function(){switchWidthHeight('img2img')}", inputs=None, outputs=None, show_progress=False)
972
+
973
+ detect_image_size_btn.click(
974
+ fn=lambda w, h, _: (w or gr.update(), h or gr.update()),
975
+ _js="currentImg2imgSourceResolution",
976
+ inputs=[dummy_component, dummy_component, dummy_component],
977
+ outputs=[width, height],
978
+ show_progress=False,
979
+ )
980
+
981
+ restore_progress_button.click(
982
+ fn=progress.restore_progress,
983
+ _js="restoreProgressImg2img",
984
+ inputs=[dummy_component],
985
+ outputs=[
986
+ img2img_gallery,
987
+ generation_info,
988
+ html_info,
989
+ html_log,
990
+ ],
991
+ show_progress=False,
992
+ )
993
+
994
+ img2img_interrogate.click(
995
+ fn=lambda *args: process_interrogate(interrogate, *args),
996
+ **interrogate_args,
997
+ )
998
+
999
+ img2img_deepbooru.click(
1000
+ fn=lambda *args: process_interrogate(interrogate_deepbooru, *args),
1001
+ **interrogate_args,
1002
+ )
1003
+
1004
+ prompts = [(txt2img_prompt, txt2img_negative_prompt), (img2img_prompt, img2img_negative_prompt)]
1005
+ style_dropdowns = [txt2img_prompt_styles, img2img_prompt_styles]
1006
+ style_js_funcs = ["update_txt2img_tokens", "update_img2img_tokens"]
1007
+
1008
+ for button, (prompt, negative_prompt) in zip([txt2img_save_style, img2img_save_style], prompts):
1009
+ button.click(
1010
+ fn=add_style,
1011
+ _js="ask_for_style_name",
1012
+ # Have to pass empty dummy component here, because the JavaScript and Python function have to accept
1013
+ # the same number of parameters, but we only know the style-name after the JavaScript prompt
1014
+ inputs=[dummy_component, prompt, negative_prompt],
1015
+ outputs=[txt2img_prompt_styles, img2img_prompt_styles],
1016
+ )
1017
+
1018
+ for button, (prompt, negative_prompt), styles, js_func in zip([txt2img_prompt_style_apply, img2img_prompt_style_apply], prompts, style_dropdowns, style_js_funcs):
1019
+ button.click(
1020
+ fn=apply_styles,
1021
+ _js=js_func,
1022
+ inputs=[prompt, negative_prompt, styles],
1023
+ outputs=[prompt, negative_prompt, styles],
1024
+ )
1025
+
1026
+ token_button.click(fn=update_token_counter, inputs=[img2img_prompt, steps], outputs=[token_counter])
1027
+ negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[img2img_negative_prompt, steps], outputs=[negative_token_counter])
1028
+
1029
+ ui_extra_networks.setup_ui(extra_networks_ui_img2img, img2img_gallery)
1030
+
1031
+ img2img_paste_fields = [
1032
+ (img2img_prompt, "Prompt"),
1033
+ (img2img_negative_prompt, "Negative prompt"),
1034
+ (steps, "Steps"),
1035
+ (sampler_index, "Sampler"),
1036
+ (restore_faces, "Face restoration"),
1037
+ (cfg_scale, "CFG scale"),
1038
+ (image_cfg_scale, "Image CFG scale"),
1039
+ (seed, "Seed"),
1040
+ (width, "Size-1"),
1041
+ (height, "Size-2"),
1042
+ (batch_size, "Batch size"),
1043
+ (subseed, "Variation seed"),
1044
+ (subseed_strength, "Variation seed strength"),
1045
+ (seed_resize_from_w, "Seed resize from-1"),
1046
+ (seed_resize_from_h, "Seed resize from-2"),
1047
+ (img2img_prompt_styles, lambda d: d["Styles array"] if isinstance(d.get("Styles array"), list) else gr.update()),
1048
+ (denoising_strength, "Denoising strength"),
1049
+ (mask_blur, "Mask blur"),
1050
+ *modules.scripts.scripts_img2img.infotext_fields
1051
+ ]
1052
+ parameters_copypaste.add_paste_fields("img2img", init_img, img2img_paste_fields, override_settings)
1053
+ parameters_copypaste.add_paste_fields("inpaint", init_img_with_mask, img2img_paste_fields, override_settings)
1054
+ parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(
1055
+ paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None,
1056
+ ))
1057
+
1058
+ modules.scripts.scripts_current = None
1059
+
1060
+ with gr.Blocks(analytics_enabled=False) as extras_interface:
1061
+ ui_postprocessing.create_ui()
1062
+
1063
+ with gr.Blocks(analytics_enabled=False) as pnginfo_interface:
1064
+ with gr.Row().style(equal_height=False):
1065
+ with gr.Column(variant='panel'):
1066
+ image = gr.Image(elem_id="pnginfo_image", label="Source", source="upload", interactive=True, type="pil")
1067
+
1068
+ with gr.Column(variant='panel'):
1069
+ html = gr.HTML()
1070
+ generation_info = gr.Textbox(visible=False, elem_id="pnginfo_generation_info")
1071
+ html2 = gr.HTML()
1072
+ with gr.Row():
1073
+ buttons = parameters_copypaste.create_buttons(["txt2img", "img2img", "inpaint", "extras"])
1074
+
1075
+ for tabname, button in buttons.items():
1076
+ parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(
1077
+ paste_button=button, tabname=tabname, source_text_component=generation_info, source_image_component=image,
1078
+ ))
1079
+
1080
+ image.change(
1081
+ fn=wrap_gradio_call(modules.extras.run_pnginfo),
1082
+ inputs=[image],
1083
+ outputs=[html, generation_info, html2],
1084
+ )
1085
+
1086
+ def update_interp_description(value):
1087
+ interp_description_css = "<p style='margin-bottom: 2.5em'>{}</p>"
1088
+ interp_descriptions = {
1089
+ "No interpolation": interp_description_css.format("No interpolation will be used. Requires one model; A. Allows for format conversion and VAE baking."),
1090
+ "Weighted sum": interp_description_css.format("A weighted sum will be used for interpolation. Requires two models; A and B. The result is calculated as A * (1 - M) + B * M"),
1091
+ "Add difference": interp_description_css.format("The difference between the last two models will be added to the first. Requires three models; A, B and C. The result is calculated as A + (B - C) * M")
1092
+ }
1093
+ return interp_descriptions[value]
1094
+
1095
+ with gr.Blocks(analytics_enabled=False) as modelmerger_interface:
1096
+ with gr.Row().style(equal_height=False):
1097
+ with gr.Column(variant='compact'):
1098
+ interp_description = gr.HTML(value=update_interp_description("Weighted sum"), elem_id="modelmerger_interp_description")
1099
+
1100
+ with FormRow(elem_id="modelmerger_models"):
1101
+ primary_model_name = gr.Dropdown(modules.sd_models.checkpoint_tiles(), elem_id="modelmerger_primary_model_name", label="Primary model (A)")
1102
+ create_refresh_button(primary_model_name, modules.sd_models.list_models, lambda: {"choices": modules.sd_models.checkpoint_tiles()}, "refresh_checkpoint_A")
1103
+
1104
+ secondary_model_name = gr.Dropdown(modules.sd_models.checkpoint_tiles(), elem_id="modelmerger_secondary_model_name", label="Secondary model (B)")
1105
+ create_refresh_button(secondary_model_name, modules.sd_models.list_models, lambda: {"choices": modules.sd_models.checkpoint_tiles()}, "refresh_checkpoint_B")
1106
+
1107
+ tertiary_model_name = gr.Dropdown(modules.sd_models.checkpoint_tiles(), elem_id="modelmerger_tertiary_model_name", label="Tertiary model (C)")
1108
+ create_refresh_button(tertiary_model_name, modules.sd_models.list_models, lambda: {"choices": modules.sd_models.checkpoint_tiles()}, "refresh_checkpoint_C")
1109
+
1110
+ custom_name = gr.Textbox(label="Custom Name (Optional)", elem_id="modelmerger_custom_name")
1111
+ interp_amount = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Multiplier (M) - set to 0 to get model A', value=0.3, elem_id="modelmerger_interp_amount")
1112
+ interp_method = gr.Radio(choices=["No interpolation", "Weighted sum", "Add difference"], value="Weighted sum", label="Interpolation Method", elem_id="modelmerger_interp_method")
1113
+ interp_method.change(fn=update_interp_description, inputs=[interp_method], outputs=[interp_description])
1114
+
1115
+ with FormRow():
1116
+ checkpoint_format = gr.Radio(choices=["ckpt", "safetensors"], value="safetensors", label="Checkpoint format", elem_id="modelmerger_checkpoint_format")
1117
+ save_as_half = gr.Checkbox(value=False, label="Save as float16", elem_id="modelmerger_save_as_half")
1118
+ save_metadata = gr.Checkbox(value=True, label="Save metadata (.safetensors only)", elem_id="modelmerger_save_metadata")
1119
+
1120
+ with FormRow():
1121
+ with gr.Column():
1122
+ config_source = gr.Radio(choices=["A, B or C", "B", "C", "Don't"], value="A, B or C", label="Copy config from", type="index", elem_id="modelmerger_config_method")
1123
+
1124
+ with gr.Column():
1125
+ with FormRow():
1126
+ bake_in_vae = gr.Dropdown(choices=["None"] + list(sd_vae.vae_dict), value="None", label="Bake in VAE", elem_id="modelmerger_bake_in_vae")
1127
+ create_refresh_button(bake_in_vae, sd_vae.refresh_vae_list, lambda: {"choices": ["None"] + list(sd_vae.vae_dict)}, "modelmerger_refresh_bake_in_vae")
1128
+
1129
+ with FormRow():
1130
+ discard_weights = gr.Textbox(value="", label="Discard weights with matching name", elem_id="modelmerger_discard_weights")
1131
+
1132
+ with gr.Row():
1133
+ modelmerger_merge = gr.Button(elem_id="modelmerger_merge", value="Merge", variant='primary')
1134
+
1135
+ with gr.Column(variant='compact', elem_id="modelmerger_results_container"):
1136
+ with gr.Group(elem_id="modelmerger_results_panel"):
1137
+ modelmerger_result = gr.HTML(elem_id="modelmerger_result", show_label=False)
1138
+
1139
+ with gr.Blocks(analytics_enabled=False) as train_interface:
1140
+ with gr.Row().style(equal_height=False):
1141
+ gr.HTML(value="<p style='margin-bottom: 0.7em'>See <b><a href=\"https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Textual-Inversion\">wiki</a></b> for detailed explanation.</p>")
1142
+
1143
+ with gr.Row(variant="compact").style(equal_height=False):
1144
+ with gr.Tabs(elem_id="train_tabs"):
1145
+
1146
+ with gr.Tab(label="Create embedding", id="create_embedding"):
1147
+ new_embedding_name = gr.Textbox(label="Name", elem_id="train_new_embedding_name")
1148
+ initialization_text = gr.Textbox(label="Initialization text", value="*", elem_id="train_initialization_text")
1149
+ nvpt = gr.Slider(label="Number of vectors per token", minimum=1, maximum=75, step=1, value=1, elem_id="train_nvpt")
1150
+ overwrite_old_embedding = gr.Checkbox(value=False, label="Overwrite Old Embedding", elem_id="train_overwrite_old_embedding")
1151
+
1152
+ with gr.Row():
1153
+ with gr.Column(scale=3):
1154
+ gr.HTML(value="")
1155
+
1156
+ with gr.Column():
1157
+ create_embedding = gr.Button(value="Create embedding", variant='primary', elem_id="train_create_embedding")
1158
+
1159
+ with gr.Tab(label="Create hypernetwork", id="create_hypernetwork"):
1160
+ new_hypernetwork_name = gr.Textbox(label="Name", elem_id="train_new_hypernetwork_name")
1161
+ new_hypernetwork_sizes = gr.CheckboxGroup(label="Modules", value=["768", "320", "640", "1280"], choices=["768", "1024", "320", "640", "1280"], elem_id="train_new_hypernetwork_sizes")
1162
+ new_hypernetwork_layer_structure = gr.Textbox("1, 2, 1", label="Enter hypernetwork layer structure", placeholder="1st and last digit must be 1. ex:'1, 2, 1'", elem_id="train_new_hypernetwork_layer_structure")
1163
+ new_hypernetwork_activation_func = gr.Dropdown(value="linear", label="Select activation function of hypernetwork. Recommended : Swish / Linear(none)", choices=modules.hypernetworks.ui.keys, elem_id="train_new_hypernetwork_activation_func")
1164
+ new_hypernetwork_initialization_option = gr.Dropdown(value = "Normal", label="Select Layer weights initialization. Recommended: Kaiming for relu-like, Xavier for sigmoid-like, Normal otherwise", choices=["Normal", "KaimingUniform", "KaimingNormal", "XavierUniform", "XavierNormal"], elem_id="train_new_hypernetwork_initialization_option")
1165
+ new_hypernetwork_add_layer_norm = gr.Checkbox(label="Add layer normalization", elem_id="train_new_hypernetwork_add_layer_norm")
1166
+ new_hypernetwork_use_dropout = gr.Checkbox(label="Use dropout", elem_id="train_new_hypernetwork_use_dropout")
1167
+ new_hypernetwork_dropout_structure = gr.Textbox("0, 0, 0", label="Enter hypernetwork Dropout structure (or empty). Recommended : 0~0.35 incrementing sequence: 0, 0.05, 0.15", placeholder="1st and last digit must be 0 and values should be between 0 and 1. ex:'0, 0.01, 0'")
1168
+ overwrite_old_hypernetwork = gr.Checkbox(value=False, label="Overwrite Old Hypernetwork", elem_id="train_overwrite_old_hypernetwork")
1169
+
1170
+ with gr.Row():
1171
+ with gr.Column(scale=3):
1172
+ gr.HTML(value="")
1173
+
1174
+ with gr.Column():
1175
+ create_hypernetwork = gr.Button(value="Create hypernetwork", variant='primary', elem_id="train_create_hypernetwork")
1176
+
1177
+ with gr.Tab(label="Preprocess images", id="preprocess_images"):
1178
+ process_src = gr.Textbox(label='Source directory', elem_id="train_process_src")
1179
+ process_dst = gr.Textbox(label='Destination directory', elem_id="train_process_dst")
1180
+ process_width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="train_process_width")
1181
+ process_height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="train_process_height")
1182
+ preprocess_txt_action = gr.Dropdown(label='Existing Caption txt Action', value="ignore", choices=["ignore", "copy", "prepend", "append"], elem_id="train_preprocess_txt_action")
1183
+
1184
+ with gr.Row():
1185
+ process_keep_original_size = gr.Checkbox(label='Keep original size', elem_id="train_process_keep_original_size")
1186
+ process_flip = gr.Checkbox(label='Create flipped copies', elem_id="train_process_flip")
1187
+ process_split = gr.Checkbox(label='Split oversized images', elem_id="train_process_split")
1188
+ process_focal_crop = gr.Checkbox(label='Auto focal point crop', elem_id="train_process_focal_crop")
1189
+ process_multicrop = gr.Checkbox(label='Auto-sized crop', elem_id="train_process_multicrop")
1190
+ process_caption = gr.Checkbox(label='Use BLIP for caption', elem_id="train_process_caption")
1191
+ process_caption_deepbooru = gr.Checkbox(label='Use deepbooru for caption', visible=True, elem_id="train_process_caption_deepbooru")
1192
+
1193
+ with gr.Row(visible=False) as process_split_extra_row:
1194
+ process_split_threshold = gr.Slider(label='Split image threshold', value=0.5, minimum=0.0, maximum=1.0, step=0.05, elem_id="train_process_split_threshold")
1195
+ process_overlap_ratio = gr.Slider(label='Split image overlap ratio', value=0.2, minimum=0.0, maximum=0.9, step=0.05, elem_id="train_process_overlap_ratio")
1196
+
1197
+ with gr.Row(visible=False) as process_focal_crop_row:
1198
+ process_focal_crop_face_weight = gr.Slider(label='Focal point face weight', value=0.9, minimum=0.0, maximum=1.0, step=0.05, elem_id="train_process_focal_crop_face_weight")
1199
+ process_focal_crop_entropy_weight = gr.Slider(label='Focal point entropy weight', value=0.15, minimum=0.0, maximum=1.0, step=0.05, elem_id="train_process_focal_crop_entropy_weight")
1200
+ process_focal_crop_edges_weight = gr.Slider(label='Focal point edges weight', value=0.5, minimum=0.0, maximum=1.0, step=0.05, elem_id="train_process_focal_crop_edges_weight")
1201
+ process_focal_crop_debug = gr.Checkbox(label='Create debug image', elem_id="train_process_focal_crop_debug")
1202
+
1203
+ with gr.Column(visible=False) as process_multicrop_col:
1204
+ gr.Markdown('Each image is center-cropped with an automatically chosen width and height.')
1205
+ with gr.Row():
1206
+ process_multicrop_mindim = gr.Slider(minimum=64, maximum=2048, step=8, label="Dimension lower bound", value=384, elem_id="train_process_multicrop_mindim")
1207
+ process_multicrop_maxdim = gr.Slider(minimum=64, maximum=2048, step=8, label="Dimension upper bound", value=768, elem_id="train_process_multicrop_maxdim")
1208
+ with gr.Row():
1209
+ process_multicrop_minarea = gr.Slider(minimum=64*64, maximum=2048*2048, step=1, label="Area lower bound", value=64*64, elem_id="train_process_multicrop_minarea")
1210
+ process_multicrop_maxarea = gr.Slider(minimum=64*64, maximum=2048*2048, step=1, label="Area upper bound", value=640*640, elem_id="train_process_multicrop_maxarea")
1211
+ with gr.Row():
1212
+ process_multicrop_objective = gr.Radio(["Maximize area", "Minimize error"], value="Maximize area", label="Resizing objective", elem_id="train_process_multicrop_objective")
1213
+ process_multicrop_threshold = gr.Slider(minimum=0, maximum=1, step=0.01, label="Error threshold", value=0.1, elem_id="train_process_multicrop_threshold")
1214
+
1215
+ with gr.Row():
1216
+ with gr.Column(scale=3):
1217
+ gr.HTML(value="")
1218
+
1219
+ with gr.Column():
1220
+ with gr.Row():
1221
+ interrupt_preprocessing = gr.Button("Interrupt", elem_id="train_interrupt_preprocessing")
1222
+ run_preprocess = gr.Button(value="Preprocess", variant='primary', elem_id="train_run_preprocess")
1223
+
1224
+ process_split.change(
1225
+ fn=lambda show: gr_show(show),
1226
+ inputs=[process_split],
1227
+ outputs=[process_split_extra_row],
1228
+ )
1229
+
1230
+ process_focal_crop.change(
1231
+ fn=lambda show: gr_show(show),
1232
+ inputs=[process_focal_crop],
1233
+ outputs=[process_focal_crop_row],
1234
+ )
1235
+
1236
+ process_multicrop.change(
1237
+ fn=lambda show: gr_show(show),
1238
+ inputs=[process_multicrop],
1239
+ outputs=[process_multicrop_col],
1240
+ )
1241
+
1242
+ def get_textual_inversion_template_names():
1243
+ return sorted(textual_inversion.textual_inversion_templates)
1244
+
1245
+ with gr.Tab(label="Train", id="train"):
1246
+ gr.HTML(value="<p style='margin-bottom: 0.7em'>Train an embedding or Hypernetwork; you must specify a directory with a set of 1:1 ratio images <a href=\"https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Textual-Inversion\" style=\"font-weight:bold;\">[wiki]</a></p>")
1247
+ with FormRow():
1248
+ train_embedding_name = gr.Dropdown(label='Embedding', elem_id="train_embedding", choices=sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys()))
1249
+ create_refresh_button(train_embedding_name, sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings, lambda: {"choices": sorted(sd_hijack.model_hijack.embedding_db.word_embeddings.keys())}, "refresh_train_embedding_name")
1250
+
1251
+ train_hypernetwork_name = gr.Dropdown(label='Hypernetwork', elem_id="train_hypernetwork", choices=sorted(shared.hypernetworks))
1252
+ create_refresh_button(train_hypernetwork_name, shared.reload_hypernetworks, lambda: {"choices": sorted(shared.hypernetworks)}, "refresh_train_hypernetwork_name")
1253
+
1254
+ with FormRow():
1255
+ embedding_learn_rate = gr.Textbox(label='Embedding Learning rate', placeholder="Embedding Learning rate", value="0.005", elem_id="train_embedding_learn_rate")
1256
+ hypernetwork_learn_rate = gr.Textbox(label='Hypernetwork Learning rate', placeholder="Hypernetwork Learning rate", value="0.00001", elem_id="train_hypernetwork_learn_rate")
1257
+
1258
+ with FormRow():
1259
+ clip_grad_mode = gr.Dropdown(value="disabled", label="Gradient Clipping", choices=["disabled", "value", "norm"])
1260
+ clip_grad_value = gr.Textbox(placeholder="Gradient clip value", value="0.1", show_label=False)
1261
+
1262
+ with FormRow():
1263
+ batch_size = gr.Number(label='Batch size', value=1, precision=0, elem_id="train_batch_size")
1264
+ gradient_step = gr.Number(label='Gradient accumulation steps', value=1, precision=0, elem_id="train_gradient_step")
1265
+
1266
+ dataset_directory = gr.Textbox(label='Dataset directory', placeholder="Path to directory with input images", elem_id="train_dataset_directory")
1267
+ log_directory = gr.Textbox(label='Log directory', placeholder="Path to directory where to write outputs", value="textual_inversion", elem_id="train_log_directory")
1268
+
1269
+ with FormRow():
1270
+ template_file = gr.Dropdown(label='Prompt template', value="style_filewords.txt", elem_id="train_template_file", choices=get_textual_inversion_template_names())
1271
+ create_refresh_button(template_file, textual_inversion.list_textual_inversion_templates, lambda: {"choices": get_textual_inversion_template_names()}, "refrsh_train_template_file")
1272
+
1273
+ training_width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="train_training_width")
1274
+ training_height = gr.Slider(minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id="train_training_height")
1275
+ varsize = gr.Checkbox(label="Do not resize images", value=False, elem_id="train_varsize")
1276
+ steps = gr.Number(label='Max steps', value=100000, precision=0, elem_id="train_steps")
1277
+
1278
+ with FormRow():
1279
+ create_image_every = gr.Number(label='Save an image to log directory every N steps, 0 to disable', value=500, precision=0, elem_id="train_create_image_every")
1280
+ save_embedding_every = gr.Number(label='Save a copy of embedding to log directory every N steps, 0 to disable', value=500, precision=0, elem_id="train_save_embedding_every")
1281
+
1282
+ use_weight = gr.Checkbox(label="Use PNG alpha channel as loss weight", value=False, elem_id="use_weight")
1283
+
1284
+ save_image_with_stored_embedding = gr.Checkbox(label='Save images with embedding in PNG chunks', value=True, elem_id="train_save_image_with_stored_embedding")
1285
+ preview_from_txt2img = gr.Checkbox(label='Read parameters (prompt, etc...) from txt2img tab when making previews', value=False, elem_id="train_preview_from_txt2img")
1286
+
1287
+ shuffle_tags = gr.Checkbox(label="Shuffle tags by ',' when creating prompts.", value=False, elem_id="train_shuffle_tags")
1288
+ tag_drop_out = gr.Slider(minimum=0, maximum=1, step=0.1, label="Drop out tags when creating prompts.", value=0, elem_id="train_tag_drop_out")
1289
+
1290
+ latent_sampling_method = gr.Radio(label='Choose latent sampling method', value="once", choices=['once', 'deterministic', 'random'], elem_id="train_latent_sampling_method")
1291
+
1292
+ with gr.Row():
1293
+ train_embedding = gr.Button(value="Train Embedding", variant='primary', elem_id="train_train_embedding")
1294
+ interrupt_training = gr.Button(value="Interrupt", elem_id="train_interrupt_training")
1295
+ train_hypernetwork = gr.Button(value="Train Hypernetwork", variant='primary', elem_id="train_train_hypernetwork")
1296
+
1297
+ params = script_callbacks.UiTrainTabParams(txt2img_preview_params)
1298
+
1299
+ script_callbacks.ui_train_tabs_callback(params)
1300
+
1301
+ with gr.Column(elem_id='ti_gallery_container'):
1302
+ ti_output = gr.Text(elem_id="ti_output", value="", show_label=False)
1303
+ gr.Gallery(label='Output', show_label=False, elem_id='ti_gallery').style(columns=4)
1304
+ gr.HTML(elem_id="ti_progress", value="")
1305
+ ti_outcome = gr.HTML(elem_id="ti_error", value="")
1306
+
1307
+ create_embedding.click(
1308
+ fn=modules.textual_inversion.ui.create_embedding,
1309
+ inputs=[
1310
+ new_embedding_name,
1311
+ initialization_text,
1312
+ nvpt,
1313
+ overwrite_old_embedding,
1314
+ ],
1315
+ outputs=[
1316
+ train_embedding_name,
1317
+ ti_output,
1318
+ ti_outcome,
1319
+ ]
1320
+ )
1321
+
1322
+ create_hypernetwork.click(
1323
+ fn=modules.hypernetworks.ui.create_hypernetwork,
1324
+ inputs=[
1325
+ new_hypernetwork_name,
1326
+ new_hypernetwork_sizes,
1327
+ overwrite_old_hypernetwork,
1328
+ new_hypernetwork_layer_structure,
1329
+ new_hypernetwork_activation_func,
1330
+ new_hypernetwork_initialization_option,
1331
+ new_hypernetwork_add_layer_norm,
1332
+ new_hypernetwork_use_dropout,
1333
+ new_hypernetwork_dropout_structure
1334
+ ],
1335
+ outputs=[
1336
+ train_hypernetwork_name,
1337
+ ti_output,
1338
+ ti_outcome,
1339
+ ]
1340
+ )
1341
+
1342
+ run_preprocess.click(
1343
+ fn=wrap_gradio_gpu_call(modules.textual_inversion.ui.preprocess, extra_outputs=[gr.update()]),
1344
+ _js="start_training_textual_inversion",
1345
+ inputs=[
1346
+ dummy_component,
1347
+ process_src,
1348
+ process_dst,
1349
+ process_width,
1350
+ process_height,
1351
+ preprocess_txt_action,
1352
+ process_keep_original_size,
1353
+ process_flip,
1354
+ process_split,
1355
+ process_caption,
1356
+ process_caption_deepbooru,
1357
+ process_split_threshold,
1358
+ process_overlap_ratio,
1359
+ process_focal_crop,
1360
+ process_focal_crop_face_weight,
1361
+ process_focal_crop_entropy_weight,
1362
+ process_focal_crop_edges_weight,
1363
+ process_focal_crop_debug,
1364
+ process_multicrop,
1365
+ process_multicrop_mindim,
1366
+ process_multicrop_maxdim,
1367
+ process_multicrop_minarea,
1368
+ process_multicrop_maxarea,
1369
+ process_multicrop_objective,
1370
+ process_multicrop_threshold,
1371
+ ],
1372
+ outputs=[
1373
+ ti_output,
1374
+ ti_outcome,
1375
+ ],
1376
+ )
1377
+
1378
+ train_embedding.click(
1379
+ fn=wrap_gradio_gpu_call(modules.textual_inversion.ui.train_embedding, extra_outputs=[gr.update()]),
1380
+ _js="start_training_textual_inversion",
1381
+ inputs=[
1382
+ dummy_component,
1383
+ train_embedding_name,
1384
+ embedding_learn_rate,
1385
+ batch_size,
1386
+ gradient_step,
1387
+ dataset_directory,
1388
+ log_directory,
1389
+ training_width,
1390
+ training_height,
1391
+ varsize,
1392
+ steps,
1393
+ clip_grad_mode,
1394
+ clip_grad_value,
1395
+ shuffle_tags,
1396
+ tag_drop_out,
1397
+ latent_sampling_method,
1398
+ use_weight,
1399
+ create_image_every,
1400
+ save_embedding_every,
1401
+ template_file,
1402
+ save_image_with_stored_embedding,
1403
+ preview_from_txt2img,
1404
+ *txt2img_preview_params,
1405
+ ],
1406
+ outputs=[
1407
+ ti_output,
1408
+ ti_outcome,
1409
+ ]
1410
+ )
1411
+
1412
+ train_hypernetwork.click(
1413
+ fn=wrap_gradio_gpu_call(modules.hypernetworks.ui.train_hypernetwork, extra_outputs=[gr.update()]),
1414
+ _js="start_training_textual_inversion",
1415
+ inputs=[
1416
+ dummy_component,
1417
+ train_hypernetwork_name,
1418
+ hypernetwork_learn_rate,
1419
+ batch_size,
1420
+ gradient_step,
1421
+ dataset_directory,
1422
+ log_directory,
1423
+ training_width,
1424
+ training_height,
1425
+ varsize,
1426
+ steps,
1427
+ clip_grad_mode,
1428
+ clip_grad_value,
1429
+ shuffle_tags,
1430
+ tag_drop_out,
1431
+ latent_sampling_method,
1432
+ use_weight,
1433
+ create_image_every,
1434
+ save_embedding_every,
1435
+ template_file,
1436
+ preview_from_txt2img,
1437
+ *txt2img_preview_params,
1438
+ ],
1439
+ outputs=[
1440
+ ti_output,
1441
+ ti_outcome,
1442
+ ]
1443
+ )
1444
+
1445
+ interrupt_training.click(
1446
+ fn=lambda: shared.state.interrupt(),
1447
+ inputs=[],
1448
+ outputs=[],
1449
+ )
1450
+
1451
+ interrupt_preprocessing.click(
1452
+ fn=lambda: shared.state.interrupt(),
1453
+ inputs=[],
1454
+ outputs=[],
1455
+ )
1456
+
1457
+ loadsave = ui_loadsave.UiLoadsave(cmd_opts.ui_config_file)
1458
+
1459
+ settings = ui_settings.UiSettings()
1460
+ settings.create_ui(loadsave, dummy_component)
1461
+
1462
+ interfaces = [
1463
+ (txt2img_interface, "txt2img", "txt2img"),
1464
+ (img2img_interface, "img2img", "img2img"),
1465
+ (extras_interface, "Extras", "extras"),
1466
+ (pnginfo_interface, "PNG Info", "pnginfo"),
1467
+ (modelmerger_interface, "Checkpoint Merger", "modelmerger"),
1468
+ (train_interface, "Train", "train"),
1469
+ ]
1470
+
1471
+ interfaces += script_callbacks.ui_tabs_callback()
1472
+ interfaces += [(settings.interface, "Settings", "settings")]
1473
+
1474
+ extensions_interface = ui_extensions.create_ui()
1475
+ interfaces += [(extensions_interface, "Extensions", "extensions")]
1476
+
1477
+ shared.tab_names = []
1478
+ for _interface, label, _ifid in interfaces:
1479
+ shared.tab_names.append(label)
1480
+
1481
+ with gr.Blocks(theme=shared.gradio_theme, analytics_enabled=False, title="Stable Diffusion") as demo:
1482
+ settings.add_quicksettings()
1483
+
1484
+ parameters_copypaste.connect_paste_params_buttons()
1485
+
1486
+ with gr.Tabs(elem_id="tabs") as tabs:
1487
+ tab_order = {k: i for i, k in enumerate(opts.ui_tab_order)}
1488
+ sorted_interfaces = sorted(interfaces, key=lambda x: tab_order.get(x[1], 9999))
1489
+
1490
+ for interface, label, ifid in sorted_interfaces:
1491
+ if label in shared.opts.hidden_tabs:
1492
+ continue
1493
+ with gr.TabItem(label, id=ifid, elem_id=f"tab_{ifid}"):
1494
+ interface.render()
1495
+
1496
+ for interface, _label, ifid in interfaces:
1497
+ if ifid in ["extensions", "settings"]:
1498
+ continue
1499
+
1500
+ loadsave.add_block(interface, ifid)
1501
+
1502
+ loadsave.add_component(f"webui/Tabs@{tabs.elem_id}", tabs)
1503
+
1504
+ loadsave.setup_ui()
1505
+
1506
+ if os.path.exists(os.path.join(script_path, "notification.mp3")):
1507
+ gr.Audio(interactive=False, value=os.path.join(script_path, "notification.mp3"), elem_id="audio_notification", visible=False)
1508
+
1509
+ footer = shared.html("footer.html")
1510
+ footer = footer.format(versions=versions_html(), api_docs="/docs" if shared.cmd_opts.api else "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/API")
1511
+ gr.HTML(footer, elem_id="footer")
1512
+
1513
+ settings.add_functionality(demo)
1514
+
1515
+ update_image_cfg_scale_visibility = lambda: gr.update(visible=shared.sd_model and shared.sd_model.cond_stage_key == "edit")
1516
+ settings.text_settings.change(fn=update_image_cfg_scale_visibility, inputs=[], outputs=[image_cfg_scale])
1517
+ demo.load(fn=update_image_cfg_scale_visibility, inputs=[], outputs=[image_cfg_scale])
1518
+
1519
+ def modelmerger(*args):
1520
+ try:
1521
+ results = modules.extras.run_modelmerger(*args)
1522
+ except Exception as e:
1523
+ errors.report("Error loading/saving model file", exc_info=True)
1524
+ modules.sd_models.list_models() # to remove the potentially missing models from the list
1525
+ return [*[gr.Dropdown.update(choices=modules.sd_models.checkpoint_tiles()) for _ in range(4)], f"Error merging checkpoints: {e}"]
1526
+ return results
1527
+
1528
+ modelmerger_merge.click(fn=lambda: '', inputs=[], outputs=[modelmerger_result])
1529
+ modelmerger_merge.click(
1530
+ fn=wrap_gradio_gpu_call(modelmerger, extra_outputs=lambda: [gr.update() for _ in range(4)]),
1531
+ _js='modelmerger',
1532
+ inputs=[
1533
+ dummy_component,
1534
+ primary_model_name,
1535
+ secondary_model_name,
1536
+ tertiary_model_name,
1537
+ interp_method,
1538
+ interp_amount,
1539
+ save_as_half,
1540
+ custom_name,
1541
+ checkpoint_format,
1542
+ config_source,
1543
+ bake_in_vae,
1544
+ discard_weights,
1545
+ save_metadata,
1546
+ ],
1547
+ outputs=[
1548
+ primary_model_name,
1549
+ secondary_model_name,
1550
+ tertiary_model_name,
1551
+ settings.component_dict['sd_model_checkpoint'],
1552
+ modelmerger_result,
1553
+ ]
1554
+ )
1555
+
1556
+ loadsave.dump_defaults()
1557
+ demo.ui_loadsave = loadsave
1558
+
1559
+ # Required as a workaround for change() event not triggering when loading values from ui-config.json
1560
+ interp_description.value = update_interp_description(interp_method.value)
1561
+
1562
+ return demo
1563
+
1564
+
1565
+ def versions_html():
1566
+ import torch
1567
+ import launch
1568
+
1569
+ python_version = ".".join([str(x) for x in sys.version_info[0:3]])
1570
+ commit = launch.commit_hash()
1571
+ tag = launch.git_tag()
1572
+
1573
+ if shared.xformers_available:
1574
+ import xformers
1575
+ xformers_version = xformers.__version__
1576
+ else:
1577
+ xformers_version = "N/A"
1578
+
1579
+ return f"""
1580
+ version: <a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/commit/{commit}">{tag}</a>
1581
+ &#x2000;•&#x2000;
1582
+ python: <span title="{sys.version}">{python_version}</span>
1583
+ &#x2000;•&#x2000;
1584
+ torch: {getattr(torch, '__long_version__',torch.__version__)}
1585
+ &#x2000;•&#x2000;
1586
+ xformers: {xformers_version}
1587
+ &#x2000;•&#x2000;
1588
+ gradio: {gr.__version__}
1589
+ &#x2000;•&#x2000;
1590
+ checkpoint: <a id="sd_checkpoint_hash">N/A</a>
1591
+ """
1592
+
1593
+
1594
+ def setup_ui_api(app):
1595
+ from pydantic import BaseModel, Field
1596
+ from typing import List
1597
+
1598
+ class QuicksettingsHint(BaseModel):
1599
+ name: str = Field(title="Name of the quicksettings field")
1600
+ label: str = Field(title="Label of the quicksettings field")
1601
+
1602
+ def quicksettings_hint():
1603
+ return [QuicksettingsHint(name=k, label=v.label) for k, v in opts.data_labels.items()]
1604
+
1605
+ app.add_api_route("/internal/quicksettings-hint", quicksettings_hint, methods=["GET"], response_model=List[QuicksettingsHint])
1606
+
1607
+ app.add_api_route("/internal/ping", lambda: {}, methods=["GET"])
1608
+
1609
+ app.add_api_route("/internal/profile-startup", lambda: timer.startup_record, methods=["GET"])
1610
+
1611
+ def download_sysinfo(attachment=False):
1612
+ from fastapi.responses import PlainTextResponse
1613
+
1614
+ text = sysinfo.get()
1615
+ filename = f"sysinfo-{datetime.datetime.utcnow().strftime('%Y-%m-%d-%H-%M')}.txt"
1616
+
1617
+ return PlainTextResponse(text, headers={'Content-Disposition': f'{"attachment" if attachment else "inline"}; filename="{filename}"'})
1618
+
1619
+ app.add_api_route("/internal/sysinfo", download_sysinfo, methods=["GET"])
1620
+ app.add_api_route("/internal/sysinfo-download", lambda: download_sysinfo(attachment=True), methods=["GET"])
1621
+