aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--extensions-builtin/Lora/extra_networks_lora.py1
-rw-r--r--extensions-builtin/Lora/lora.py49
-rw-r--r--extensions-builtin/Lora/scripts/lora_script.py1
-rw-r--r--extensions-builtin/Lora/ui_extra_networks_lora.py2
-rw-r--r--javascript/hints.js4
-rw-r--r--javascript/imageviewerGamepad.js85
-rw-r--r--modules/images.py1
-rw-r--r--modules/shared.py2
-rw-r--r--modules/ui_tempdir.py2
-rw-r--r--style.css4
-rw-r--r--webui.py11
11 files changed, 120 insertions, 42 deletions
diff --git a/extensions-builtin/Lora/extra_networks_lora.py b/extensions-builtin/Lora/extra_networks_lora.py
index 45f899fc..ccb249ac 100644
--- a/extensions-builtin/Lora/extra_networks_lora.py
+++ b/extensions-builtin/Lora/extra_networks_lora.py
@@ -1,6 +1,7 @@
from modules import extra_networks, shared
import lora
+
class ExtraNetworkLora(extra_networks.ExtraNetwork):
def __init__(self):
super().__init__('lora')
diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py
index 6f246921..94ec021b 100644
--- a/extensions-builtin/Lora/lora.py
+++ b/extensions-builtin/Lora/lora.py
@@ -4,7 +4,7 @@ import re
import torch
from typing import Union
-from modules import shared, devices, sd_models, errors
+from modules import shared, devices, sd_models, errors, scripts
metadata_tags_order = {"ss_sd_model_name": 1, "ss_resolution": 2, "ss_clip_skip": 3, "ss_num_train_images": 10, "ss_tag_frequency": 20}
@@ -93,6 +93,7 @@ class LoraOnDisk:
self.metadata = m
self.ssmd_cover_images = self.metadata.pop('ssmd_cover_images', None) # those are cover images and they are too big to display in UI as text
+ self.alias = self.metadata.get('ss_output_name', self.name)
class LoraModule:
@@ -199,11 +200,11 @@ def load_loras(names, multipliers=None):
loaded_loras.clear()
- loras_on_disk = [available_loras.get(name, None) for name in names]
+ loras_on_disk = [available_lora_aliases.get(name, None) for name in names]
if any([x is None for x in loras_on_disk]):
list_available_loras()
- loras_on_disk = [available_loras.get(name, None) for name in names]
+ loras_on_disk = [available_lora_aliases.get(name, None) for name in names]
for i, name in enumerate(names):
lora = already_loaded.get(name, None)
@@ -343,6 +344,7 @@ def lora_MultiheadAttention_load_state_dict(self, *args, **kwargs):
def list_available_loras():
available_loras.clear()
+ available_lora_aliases.clear()
os.makedirs(shared.cmd_opts.lora_dir, exist_ok=True)
@@ -356,11 +358,50 @@ def list_available_loras():
continue
name = os.path.splitext(os.path.basename(filename))[0]
+ entry = LoraOnDisk(name, filename)
- available_loras[name] = LoraOnDisk(name, filename)
+ available_loras[name] = entry
+
+ available_lora_aliases[name] = entry
+ available_lora_aliases[entry.alias] = entry
+
+
+re_lora_name = re.compile(r"(.*)\s*\([0-9a-fA-F]+\)")
+
+
+def infotext_pasted(infotext, params):
+ if "AddNet Module 1" in [x[1] for x in scripts.scripts_txt2img.infotext_fields]:
+ return # if the other extension is active, it will handle those fields, no need to do anything
+
+ added = []
+
+ for k, v in params.items():
+ if not k.startswith("AddNet Model "):
+ continue
+
+ num = k[13:]
+
+ if params.get("AddNet Module " + num) != "LoRA":
+ continue
+
+ name = params.get("AddNet Model " + num)
+ if name is None:
+ continue
+
+ m = re_lora_name.match(name)
+ if m:
+ name = m.group(1)
+
+ multiplier = params.get("AddNet Weight A " + num, "1.0")
+
+ added.append(f"<lora:{name}:{multiplier}>")
+
+ if added:
+ params["Prompt"] += "\n" + "".join(added)
available_loras = {}
+available_lora_aliases = {}
loaded_loras = []
list_available_loras()
diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py
index 3fc38ab9..2f2267a2 100644
--- a/extensions-builtin/Lora/scripts/lora_script.py
+++ b/extensions-builtin/Lora/scripts/lora_script.py
@@ -49,6 +49,7 @@ torch.nn.MultiheadAttention._load_from_state_dict = lora.lora_MultiheadAttention
script_callbacks.on_model_loaded(lora.assign_lora_names_to_compvis_modules)
script_callbacks.on_script_unloaded(unload)
script_callbacks.on_before_ui(before_ui)
+script_callbacks.on_infotext_pasted(lora.infotext_pasted)
shared.options_templates.update(shared.options_section(('extra_networks', "Extra Networks"), {
diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py
index 68b11332..a0edbc1e 100644
--- a/extensions-builtin/Lora/ui_extra_networks_lora.py
+++ b/extensions-builtin/Lora/ui_extra_networks_lora.py
@@ -21,7 +21,7 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage):
"preview": self.find_preview(path),
"description": self.find_description(path),
"search_term": self.search_terms_from_path(lora_on_disk.filename),
- "prompt": json.dumps(f"<lora:{name}:") + " + opts.extra_networks_default_multiplier + " + json.dumps(">"),
+ "prompt": json.dumps(f"<lora:{lora_on_disk.alias}:") + " + opts.extra_networks_default_multiplier + " + json.dumps(">"),
"local_preview": f"{path}.{shared.opts.samples_format}",
"metadata": json.dumps(lora_on_disk.metadata, indent=4) if lora_on_disk.metadata else None,
}
diff --git a/javascript/hints.js b/javascript/hints.js
index 8d1967a7..c55fedfb 100644
--- a/javascript/hints.js
+++ b/javascript/hints.js
@@ -66,8 +66,8 @@ titles = {
"Interrogate": "Reconstruct prompt from existing image and put it into the prompt field.",
- "Images filename pattern": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [clip_skip], [batch_number], [generation_number], [prompt_hash], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [model_name], [prompt_words], [date], [datetime], [datetime<Format>], [datetime<Format><Time Zone>], [job_timestamp], [hasprompt<prompt1|default><prompt2>..]; leave empty for default.",
- "Directory name pattern": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [clip_skip], [batch_number], [generation_number], [prompt_hash], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [model_name], [prompt_words], [date], [datetime], [datetime<Format>], [datetime<Format><Time Zone>], [job_timestamp], [hasprompt<prompt1|default><prompt2>..]; leave empty for default.",
+ "Images filename pattern": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [denoising], [clip_skip], [batch_number], [generation_number], [prompt_hash], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [model_name], [prompt_words], [date], [datetime], [datetime<Format>], [datetime<Format><Time Zone>], [job_timestamp], [hasprompt<prompt1|default><prompt2>..]; leave empty for default.",
+ "Directory name pattern": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [denoising], [clip_skip], [batch_number], [generation_number], [prompt_hash], [prompt], [prompt_no_styles], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [model_name], [prompt_words], [date], [datetime], [datetime<Format>], [datetime<Format><Time Zone>], [job_timestamp], [hasprompt<prompt1|default><prompt2>..]; leave empty for default.",
"Max prompt words": "Set the maximum number of words to be used in the [prompt_words] option; ATTENTION: If the words are too long, they may exceed the maximum length of the file path that the system can handle",
"Loopback": "Performs img2img processing multiple times. Output images are used as input for the next loop.",
diff --git a/javascript/imageviewerGamepad.js b/javascript/imageviewerGamepad.js
index 29bd7140..6297a12b 100644
--- a/javascript/imageviewerGamepad.js
+++ b/javascript/imageviewerGamepad.js
@@ -1,36 +1,57 @@
- let delay = 350//ms
- window.addEventListener('gamepadconnected', (e) => {
- console.log("Gamepad connected!")
- const gamepad = e.gamepad;
- setInterval(() => {
- const xValue = gamepad.axes[0].toFixed(2);
- if (xValue < -0.3) {
- modalPrevImage(e);
- } else if (xValue > 0.3) {
- modalNextImage(e);
- }
-
- }, delay);
- });
-
-
- /*
- Primarily for vr controller type pointer devices.
- I use the wheel event because there's currently no way to do it properly with web xr.
- */
-
- let isScrolling = false;
- window.addEventListener('wheel', (e) => {
- if (isScrolling) return;
- isScrolling = true;
-
- if (e.deltaX <= -0.6) {
+window.addEventListener('gamepadconnected', (e) => {
+ const index = e.gamepad.index;
+ let isWaiting = false;
+ setInterval(async () => {
+ if (!opts.js_modal_lightbox_gamepad || isWaiting) return;
+ const gamepad = navigator.getGamepads()[index];
+ const xValue = gamepad.axes[0];
+ if (xValue <= -0.3) {
modalPrevImage(e);
- } else if (e.deltaX >= 0.6) {
+ isWaiting = true;
+ } else if (xValue >= 0.3) {
modalNextImage(e);
+ isWaiting = true;
}
+ if (isWaiting) {
+ await sleepUntil(() => {
+ const xValue = navigator.getGamepads()[index].axes[0]
+ if (xValue < 0.3 && xValue > -0.3) {
+ return true;
+ }
+ }, opts.js_modal_lightbox_gamepad_repeat);
+ isWaiting = false;
+ }
+ }, 10);
+});
+
+/*
+Primarily for vr controller type pointer devices.
+I use the wheel event because there's currently no way to do it properly with web xr.
+ */
+let isScrolling = false;
+window.addEventListener('wheel', (e) => {
+ if (!opts.js_modal_lightbox_gamepad || isScrolling) return;
+ isScrolling = true;
+
+ if (e.deltaX <= -0.6) {
+ modalPrevImage(e);
+ } else if (e.deltaX >= 0.6) {
+ modalNextImage(e);
+ }
- setTimeout(() => {
- isScrolling = false;
- }, delay);
- }); \ No newline at end of file
+ setTimeout(() => {
+ isScrolling = false;
+ }, opts.js_modal_lightbox_gamepad_repeat);
+});
+
+function sleepUntil(f, timeout) {
+ return new Promise((resolve) => {
+ const timeStart = new Date();
+ const wait = setInterval(function() {
+ if (f() || new Date() - timeStart > timeout) {
+ clearInterval(wait);
+ resolve();
+ }
+ }, 20);
+ });
+}
diff --git a/modules/images.py b/modules/images.py
index fd173829..6ceb7c7c 100644
--- a/modules/images.py
+++ b/modules/images.py
@@ -357,6 +357,7 @@ class FilenameGenerator:
'generation_number': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if self.p.n_iter == 1 and self.p.batch_size == 1 else self.p.iteration * self.p.batch_size + self.p.batch_index + 1,
'hasprompt': lambda self, *args: self.hasprompt(*args), # accepts formats:[hasprompt<prompt1|default><prompt2>..]
'clip_skip': lambda self: opts.data["CLIP_stop_at_last_layers"],
+ 'denoising': lambda self: self.p.denoising_strength if self.p and self.p.denoising_strength else NOTHING_AND_SKIP_PREVIOUS_TEXT,
}
default_time_format = '%Y%m%d%H%M%S'
diff --git a/modules/shared.py b/modules/shared.py
index 151bab9e..91aac1a3 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -400,6 +400,8 @@ options_templates.update(options_section(('ui', "User interface"), {
"font": OptionInfo("", "Font for image grids that have text"),
"js_modal_lightbox": OptionInfo(True, "Enable full page image viewer"),
"js_modal_lightbox_initially_zoomed": OptionInfo(True, "Show images zoomed in by default in full page image viewer"),
+ "js_modal_lightbox_gamepad": OptionInfo(True, "Navigate image viewer with gamepad"),
+ "js_modal_lightbox_gamepad_repeat": OptionInfo(250, "Gamepad repeat period, in milliseconds"),
"show_progress_in_title": OptionInfo(True, "Show generation progress in window title."),
"samplers_in_dropdown": OptionInfo(True, "Use dropdown for sampler selection instead of radio group"),
"dimensions_and_batch_together": OptionInfo(True, "Show Width/Height and Batch sliders in same row"),
diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py
index 21945235..9cb4954a 100644
--- a/modules/ui_tempdir.py
+++ b/modules/ui_tempdir.py
@@ -36,7 +36,7 @@ def save_pil_to_file(pil_image, dir=None):
if already_saved_as and os.path.isfile(already_saved_as):
register_tmp_file(shared.demo, already_saved_as)
- file_obj = Savedfile(already_saved_as)
+ file_obj = Savedfile(f"{already_saved_as}?{os.path.getmtime(already_saved_as)}")
return file_obj
if shared.opts.temp_dir != "":
diff --git a/style.css b/style.css
index 3f56087a..57ddba0e 100644
--- a/style.css
+++ b/style.css
@@ -246,7 +246,7 @@ button.custom-button{
}
}
-#txt2img_gallery img, #img2img_gallery img{
+#txt2img_gallery img, #img2img_gallery img, #extras_gallery img{
object-fit: scale-down;
}
#txt2img_actions_column, #img2img_actions_column {
@@ -534,6 +534,8 @@ div#extras_scale_to_tab div.form{
#lightboxModal > img.modalImageFullscreen{
object-fit: contain;
height: 100%;
+ width: 100%;
+ min-height: 0;
}
.modalPrev,
diff --git a/webui.py b/webui.py
index aec9ede0..3ecc3f07 100644
--- a/webui.py
+++ b/webui.py
@@ -280,7 +280,6 @@ def api_only():
print(f"Startup time: {startup_timer.summary()}.")
api.launch(server_name="0.0.0.0" if cmd_opts.listen else "127.0.0.1", port=cmd_opts.port if cmd_opts.port else 7861)
-
def webui():
launch_api = cmd_opts.api
initialize()
@@ -307,6 +306,16 @@ def webui():
for line in file.readlines():
gradio_auth_creds += [x.strip() for x in line.split(',') if x.strip()]
+ # this restores the missing /docs endpoint
+ if launch_api and not hasattr(FastAPI, 'original_setup'):
+ def fastapi_setup(self):
+ self.docs_url = "/docs"
+ self.redoc_url = "/redoc"
+ self.original_setup()
+
+ FastAPI.original_setup = FastAPI.setup
+ FastAPI.setup = fastapi_setup
+
app, local_url, share_url = shared.demo.launch(
share=cmd_opts.share,
server_name=server_name,