diff --git a/.github/workflows/on_pull_request.yaml b/.github/workflows/on_pull_request.yaml
index 9e44c806a..c595b80aa 100644
--- a/.github/workflows/on_pull_request.yaml
+++ b/.github/workflows/on_pull_request.yaml
@@ -11,8 +11,8 @@ jobs:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Checkout Code
- uses: actions/checkout@v3
- - uses: actions/setup-python@v4
+ uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
with:
python-version: 3.11
# NB: there's no cache: pip here since we're not installing anything
@@ -29,9 +29,9 @@ jobs:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Checkout Code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Install Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: 18
- run: npm i --ci
diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml
index f42e4758e..0610f4f54 100644
--- a/.github/workflows/run_tests.yaml
+++ b/.github/workflows/run_tests.yaml
@@ -11,9 +11,9 @@ jobs:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Checkout Code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Set up Python 3.10
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: 3.10.6
cache: pip
@@ -22,7 +22,7 @@ jobs:
launch.py
- name: Cache models
id: cache-models
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: models
key: "2023-12-30"
@@ -68,13 +68,13 @@ jobs:
python -m coverage report -i
python -m coverage html -i
- name: Upload main app output
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
if: always()
with:
name: output
path: output.txt
- name: Upload coverage HTML
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
if: always()
with:
name: htmlcov
diff --git a/README.md b/README.md
index f4cfcf290..bc08e7ad1 100644
--- a/README.md
+++ b/README.md
@@ -98,6 +98,7 @@ Make sure the required [dependencies](https://github.com/AUTOMATIC1111/stable-di
- [NVidia](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) (recommended)
- [AMD](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs) GPUs.
- [Intel CPUs, Intel GPUs (both integrated and discrete)](https://github.com/openvinotoolkit/stable-diffusion-webui/wiki/Installation-on-Intel-Silicon) (external wiki page)
+- [Ascend NPUs](https://github.com/wangshuai09/stable-diffusion-webui/wiki/Install-and-run-on-Ascend-NPUs) (external wiki page)
Alternatively, use online services (like Google Colab):
diff --git a/extensions-builtin/Lora/network_oft.py b/extensions-builtin/Lora/network_oft.py
index 7821a8a7d..1c515ebb7 100644
--- a/extensions-builtin/Lora/network_oft.py
+++ b/extensions-builtin/Lora/network_oft.py
@@ -36,13 +36,6 @@ class NetworkModuleOFT(network.NetworkModule):
# self.alpha is unused
self.dim = self.oft_blocks.shape[1] # (num_blocks, block_size, block_size)
- # LyCORIS BOFT
- if self.oft_blocks.dim() == 4:
- self.is_boft = True
- self.rescale = weights.w.get('rescale', None)
- if self.rescale is not None:
- self.rescale = self.rescale.reshape(-1, *[1]*(self.org_module[0].weight.dim() - 1))
-
is_linear = type(self.sd_module) in [torch.nn.Linear, torch.nn.modules.linear.NonDynamicallyQuantizableLinear]
is_conv = type(self.sd_module) in [torch.nn.Conv2d]
is_other_linear = type(self.sd_module) in [torch.nn.MultiheadAttention] # unsupported
@@ -54,6 +47,13 @@ class NetworkModuleOFT(network.NetworkModule):
elif is_other_linear:
self.out_dim = self.sd_module.embed_dim
+ # LyCORIS BOFT
+ if self.oft_blocks.dim() == 4:
+ self.is_boft = True
+ self.rescale = weights.w.get('rescale', None)
+ if self.rescale is not None and not is_other_linear:
+ self.rescale = self.rescale.reshape(-1, *[1]*(self.org_module[0].weight.dim() - 1))
+
self.num_blocks = self.dim
self.block_size = self.out_dim // self.dim
self.constraint = (0 if self.alpha is None else self.alpha) * self.out_dim
diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js
index 64e7a638a..ed2ef99b0 100644
--- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js
+++ b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js
@@ -252,6 +252,7 @@ onUiLoaded(async() => {
let isMoving = false;
let mouseX, mouseY;
let activeElement;
+ let interactedWithAltKey = false;
const elements = Object.fromEntries(
Object.keys(elementIDs).map(id => [
@@ -508,6 +509,10 @@ onUiLoaded(async() => {
if (isModifierKey(e, hotkeysConfig.canvas_hotkey_zoom)) {
e.preventDefault();
+ if(hotkeysConfig.canvas_hotkey_zoom === "Alt"){
+ interactedWithAltKey = true;
+ }
+
let zoomPosX, zoomPosY;
let delta = 0.2;
if (elemData[elemId].zoomLevel > 7) {
@@ -793,13 +798,17 @@ onUiLoaded(async() => {
targetElement.addEventListener("wheel", e => {
// change zoom level
- const operation = e.deltaY > 0 ? "-" : "+";
+ const operation = (e.deltaY || -e.wheelDelta) > 0 ? "-" : "+";
changeZoomLevel(operation, e);
// Handle brush size adjustment with ctrl key pressed
if (isModifierKey(e, hotkeysConfig.canvas_hotkey_adjust)) {
e.preventDefault();
+ if(hotkeysConfig.canvas_hotkey_adjust === "Alt"){
+ interactedWithAltKey = true;
+ }
+
// Increase or decrease brush size based on scroll direction
adjustBrushSize(elemId, e.deltaY);
}
@@ -839,6 +848,20 @@ onUiLoaded(async() => {
document.addEventListener("keydown", handleMoveKeyDown);
document.addEventListener("keyup", handleMoveKeyUp);
+
+ // Prevent firefox from opening main menu when alt is used as a hotkey for zoom or brush size
+ function handleAltKeyUp(e) {
+ if (e.key !== "Alt" || !interactedWithAltKey) {
+ return;
+ }
+
+ e.preventDefault();
+ interactedWithAltKey = false;
+ }
+
+ document.addEventListener("keyup", handleAltKeyUp);
+
+
// Detect zoom level and update the pan speed.
function updatePanPosition(movementX, movementY) {
let panSpeed = 2;
diff --git a/scripts/processing_autosized_crop.py b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_autosized_crop.py
similarity index 100%
rename from scripts/processing_autosized_crop.py
rename to extensions-builtin/postprocessing-for-training/scripts/postprocessing_autosized_crop.py
diff --git a/scripts/postprocessing_caption.py b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_caption.py
similarity index 100%
rename from scripts/postprocessing_caption.py
rename to extensions-builtin/postprocessing-for-training/scripts/postprocessing_caption.py
diff --git a/scripts/postprocessing_create_flipped_copies.py b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_create_flipped_copies.py
similarity index 100%
rename from scripts/postprocessing_create_flipped_copies.py
rename to extensions-builtin/postprocessing-for-training/scripts/postprocessing_create_flipped_copies.py
diff --git a/scripts/postprocessing_focal_crop.py b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_focal_crop.py
similarity index 100%
rename from scripts/postprocessing_focal_crop.py
rename to extensions-builtin/postprocessing-for-training/scripts/postprocessing_focal_crop.py
diff --git a/scripts/postprocessing_split_oversized.py b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_split_oversized.py
similarity index 100%
rename from scripts/postprocessing_split_oversized.py
rename to extensions-builtin/postprocessing-for-training/scripts/postprocessing_split_oversized.py
diff --git a/html/extra-networks-pane-dirs.html b/html/extra-networks-pane-dirs.html
new file mode 100644
index 000000000..5ce04289a
--- /dev/null
+++ b/html/extra-networks-pane-dirs.html
@@ -0,0 +1,8 @@
+
+
+ {dirs_html}
+
+
+ {items_html}
+
+
diff --git a/html/extra-networks-pane-tree.html b/html/extra-networks-pane-tree.html
new file mode 100644
index 000000000..88561fcdc
--- /dev/null
+++ b/html/extra-networks-pane-tree.html
@@ -0,0 +1,8 @@
+
+
+ {tree_html}
+
+
+ {items_html}
+
+
\ No newline at end of file
diff --git a/html/extra-networks-pane.html b/html/extra-networks-pane.html
index 02a871086..9a67baea9 100644
--- a/html/extra-networks-pane.html
+++ b/html/extra-networks-pane.html
@@ -1,23 +1,53 @@
-
+
+
+ Sort:
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
-
+
-
+
-
-
- {tree_html}
-
-
- {items_html}
-
-
-
\ No newline at end of file
+ {pane_content}
+
diff --git a/javascript/edit-attention.js b/javascript/edit-attention.js
index 688c2f112..b07ba97cb 100644
--- a/javascript/edit-attention.js
+++ b/javascript/edit-attention.js
@@ -64,6 +64,14 @@ function keyupEditAttention(event) {
selectionEnd++;
}
+ // deselect surrounding whitespace
+ while (text[selectionStart] == " " && selectionStart < selectionEnd) {
+ selectionStart++;
+ }
+ while (text[selectionEnd - 1] == " " && selectionEnd > selectionStart) {
+ selectionEnd--;
+ }
+
target.setSelectionRange(selectionStart, selectionEnd);
return true;
}
diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js
index 584fd6c75..be5f0f304 100644
--- a/javascript/extraNetworks.js
+++ b/javascript/extraNetworks.js
@@ -39,12 +39,12 @@ function setupExtraNetworksForTab(tabname) {
// tabname_full = {tabname}_{extra_networks_tabname}
var tabname_full = elem.id;
var search = gradioApp().querySelector("#" + tabname_full + "_extra_search");
- var sort_mode = gradioApp().querySelector("#" + tabname_full + "_extra_sort");
var sort_dir = gradioApp().querySelector("#" + tabname_full + "_extra_sort_dir");
var refresh = gradioApp().querySelector("#" + tabname_full + "_extra_refresh");
+ var currentSort = '';
// If any of the buttons above don't exist, we want to skip this iteration of the loop.
- if (!search || !sort_mode || !sort_dir || !refresh) {
+ if (!search || !sort_dir || !refresh) {
return; // `return` is equivalent of `continue` but for forEach loops.
}
@@ -52,7 +52,7 @@ function setupExtraNetworksForTab(tabname) {
var searchTerm = search.value.toLowerCase();
gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card').forEach(function(elem) {
var searchOnly = elem.querySelector('.search_only');
- var text = Array.prototype.map.call(elem.querySelectorAll('.search_terms'), function(t) {
+ var text = Array.prototype.map.call(elem.querySelectorAll('.search_terms, .description'), function(t) {
return t.textContent.toLowerCase();
}).join(" ");
@@ -71,42 +71,46 @@ function setupExtraNetworksForTab(tabname) {
};
var applySort = function(force) {
- var cards = gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card');
+ var cards = gradioApp().querySelectorAll('#' + tabname_full + ' div.card');
+ var parent = gradioApp().querySelector('#' + tabname_full + "_cards");
var reverse = sort_dir.dataset.sortdir == "Descending";
- var sortKey = sort_mode.dataset.sortmode.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name";
- sortKey = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1);
- var sortKeyStore = sortKey + "-" + (reverse ? "Descending" : "Ascending") + "-" + cards.length;
+ var activeSearchElem = gradioApp().querySelector('#' + tabname_full + "_controls .extra-network-control--sort.extra-network-control--enabled");
+ var sortKey = activeSearchElem ? activeSearchElem.dataset.sortkey : "default";
+ var sortKeyDataField = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1);
+ var sortKeyStore = sortKey + "-" + sort_dir.dataset.sortdir + "-" + cards.length;
- if (sortKeyStore == sort_mode.dataset.sortkey && !force) {
+ if (sortKeyStore == currentSort && !force) {
return;
}
- sort_mode.dataset.sortkey = sortKeyStore;
+ currentSort = sortKeyStore;
- cards.forEach(function(card) {
- card.originalParentElement = card.parentElement;
- });
var sortedCards = Array.from(cards);
sortedCards.sort(function(cardA, cardB) {
- var a = cardA.dataset[sortKey];
- var b = cardB.dataset[sortKey];
+ var a = cardA.dataset[sortKeyDataField];
+ var b = cardB.dataset[sortKeyDataField];
if (!isNaN(a) && !isNaN(b)) {
return parseInt(a) - parseInt(b);
}
return (a < b ? -1 : (a > b ? 1 : 0));
});
+
if (reverse) {
sortedCards.reverse();
}
- cards.forEach(function(card) {
- card.remove();
- });
+
+ parent.innerHTML = '';
+
+ var frag = document.createDocumentFragment();
sortedCards.forEach(function(card) {
- card.originalParentElement.appendChild(card);
+ frag.appendChild(card);
});
+ parent.appendChild(frag);
};
- search.addEventListener("input", applyFilter);
+ search.addEventListener("input", function() {
+ applyFilter();
+ });
applySort();
applyFilter();
extraNetworksApplySort[tabname_full] = applySort;
@@ -272,6 +276,15 @@ function saveCardPreview(event, tabname, filename) {
event.preventDefault();
}
+function extraNetworksSearchButton(tabname, extra_networks_tabname, event) {
+ var searchTextarea = gradioApp().querySelector("#" + tabname + "_" + extra_networks_tabname + "_extra_search");
+ var button = event.target;
+ var text = button.classList.contains("search-all") ? "" : button.textContent.trim();
+
+ searchTextarea.value = text;
+ updateInput(searchTextarea);
+}
+
function extraNetworksTreeProcessFileClick(event, btn, tabname, extra_networks_tabname) {
/**
* Processes `onclick` events when user clicks on files in tree.
@@ -383,36 +396,17 @@ function extraNetworksTreeOnClick(event, tabname, extra_networks_tabname) {
}
function extraNetworksControlSortOnClick(event, tabname, extra_networks_tabname) {
- /**
- * Handles `onclick` events for the Sort Mode button.
- *
- * Modifies the data attributes of the Sort Mode button to cycle between
- * various sorting modes.
- *
- * @param event The generated event.
- * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
- * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
- */
- var curr_mode = event.currentTarget.dataset.sortmode;
- var el_sort_dir = gradioApp().querySelector("#" + tabname + "_" + extra_networks_tabname + "_extra_sort_dir");
- var sort_dir = el_sort_dir.dataset.sortdir;
- if (curr_mode == "path") {
- event.currentTarget.dataset.sortmode = "name";
- event.currentTarget.dataset.sortkey = "sortName-" + sort_dir + "-640";
- event.currentTarget.setAttribute("title", "Sort by filename");
- } else if (curr_mode == "name") {
- event.currentTarget.dataset.sortmode = "date_created";
- event.currentTarget.dataset.sortkey = "sortDate_created-" + sort_dir + "-640";
- event.currentTarget.setAttribute("title", "Sort by date created");
- } else if (curr_mode == "date_created") {
- event.currentTarget.dataset.sortmode = "date_modified";
- event.currentTarget.dataset.sortkey = "sortDate_modified-" + sort_dir + "-640";
- event.currentTarget.setAttribute("title", "Sort by date modified");
- } else {
- event.currentTarget.dataset.sortmode = "path";
- event.currentTarget.dataset.sortkey = "sortPath-" + sort_dir + "-640";
- event.currentTarget.setAttribute("title", "Sort by path");
- }
+ /** Handles `onclick` events for Sort Mode buttons. */
+
+ var self = event.currentTarget;
+ var parent = event.currentTarget.parentElement;
+
+ parent.querySelectorAll('.extra-network-control--sort').forEach(function(x) {
+ x.classList.remove('extra-network-control--enabled');
+ });
+
+ self.classList.add('extra-network-control--enabled');
+
applyExtraNetworkSort(tabname + "_" + extra_networks_tabname);
}
@@ -447,27 +441,12 @@ function extraNetworksControlTreeViewOnClick(event, tabname, extra_networks_tabn
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
*/
- const tree = gradioApp().getElementById(tabname + "_" + extra_networks_tabname + "_tree");
- const parent = tree.parentElement;
- let resizeHandle = parent.querySelector('.resize-handle');
- tree.classList.toggle("hidden");
+ var button = event.currentTarget;
+ button.classList.toggle("extra-network-control--enabled");
+ var show = !button.classList.contains("extra-network-control--enabled");
- if (tree.classList.contains("hidden")) {
- tree.style.display = 'none';
- parent.style.display = 'flex';
- if (resizeHandle) {
- resizeHandle.style.display = 'none';
- }
- } else {
- tree.style.display = 'block';
- parent.style.display = 'grid';
- if (!resizeHandle) {
- setupResizeHandle(parent);
- resizeHandle = parent.querySelector('.resize-handle');
- }
- resizeHandle.style.display = 'block';
- }
- event.currentTarget.classList.toggle("extra-network-control--enabled");
+ var pane = gradioApp().getElementById(tabname + "_" + extra_networks_tabname + "_pane");
+ pane.classList.toggle("extra-network-dirs-hidden", show);
}
function extraNetworksControlRefreshOnClick(event, tabname, extra_networks_tabname) {
diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js
index 625c5d148..d4d4f016d 100644
--- a/javascript/imageviewer.js
+++ b/javascript/imageviewer.js
@@ -131,19 +131,15 @@ function setupImageForLightbox(e) {
e.style.cursor = 'pointer';
e.style.userSelect = 'none';
- var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
-
- // For Firefox, listening on click first switched to next image then shows the lightbox.
- // If you know how to fix this without switching to mousedown event, please.
- // For other browsers the event is click to make it possiblr to drag picture.
- var event = isFirefox ? 'mousedown' : 'click';
-
- e.addEventListener(event, function(evt) {
+ e.addEventListener('mousedown', function(evt) {
if (evt.button == 1) {
open(evt.target.src);
evt.preventDefault();
return;
}
+ }, true);
+
+ e.addEventListener('click', function(evt) {
if (!opts.js_modal_lightbox || evt.button != 0) return;
modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed);
diff --git a/javascript/resizeHandle.js b/javascript/resizeHandle.js
index 50251ffc1..4aeb14b41 100644
--- a/javascript/resizeHandle.js
+++ b/javascript/resizeHandle.js
@@ -79,6 +79,11 @@
parent.minRightColWidth = 0;
parent.needHideOnMoblie = false;
}
+
+ if (!leftColTemplate) {
+ leftColTemplate = '1fr';
+ }
+
const gridTemplateColumns = `${leftColTemplate} ${PAD}px ${parent.children[1].style.flexGrow}fr`;
parent.style.gridTemplateColumns = gridTemplateColumns;
parent.style.originalGridTemplateColumns = gridTemplateColumns;
diff --git a/javascript/ui.js b/javascript/ui.js
index 1eef6d337..e0f5feebd 100644
--- a/javascript/ui.js
+++ b/javascript/ui.js
@@ -136,8 +136,7 @@ function showSubmitInterruptingPlaceholder(tabname) {
function showRestoreProgressButton(tabname, show) {
var button = gradioApp().getElementById(tabname + "_restore_progress");
if (!button) return;
-
- button.style.display = show ? "flex" : "none";
+ button.style.setProperty('display', show ? 'flex' : 'none', 'important');
}
function submit() {
@@ -209,6 +208,7 @@ function restoreProgressTxt2img() {
var id = localGet("txt2img_task_id");
if (id) {
+ showSubmitInterruptingPlaceholder('txt2img');
requestProgress(id, gradioApp().getElementById('txt2img_gallery_container'), gradioApp().getElementById('txt2img_gallery'), function() {
showSubmitButtons('txt2img', true);
}, null, 0);
@@ -223,6 +223,7 @@ function restoreProgressImg2img() {
var id = localGet("img2img_task_id");
if (id) {
+ showSubmitInterruptingPlaceholder('img2img');
requestProgress(id, gradioApp().getElementById('img2img_gallery_container'), gradioApp().getElementById('img2img_gallery'), function() {
showSubmitButtons('img2img', true);
}, null, 0);
diff --git a/modules/cmd_args.py b/modules/cmd_args.py
index bf3553031..016a33d10 100644
--- a/modules/cmd_args.py
+++ b/modules/cmd_args.py
@@ -124,3 +124,4 @@ parser.add_argument("--disable-extra-extensions", action='store_true', help="pre
parser.add_argument("--skip-load-model-at-start", action='store_true', help="if load a model at web start, only take effect when --nowebui")
parser.add_argument("--unix-filenames-sanitization", action='store_true', help="allow any symbols except '/' in filenames. May conflict with your browser and file system")
parser.add_argument("--filenames-max-length", type=int, default=128, help='maximal length of filenames of saved images. If you override it, it can conflict with your file system')
+parser.add_argument("--no-prompt-history", action='store_true', help="disable read prompt from last generation feature; settings this argument will not create '--data_path/params.txt' file")
diff --git a/modules/extensions.py b/modules/extensions.py
index 04bda297e..88a389388 100644
--- a/modules/extensions.py
+++ b/modules/extensions.py
@@ -1,6 +1,7 @@
from __future__ import annotations
import configparser
+import dataclasses
import os
import threading
import re
@@ -22,6 +23,13 @@ def active():
return [x for x in extensions if x.enabled]
+@dataclasses.dataclass
+class CallbackOrderInfo:
+ name: str
+ before: list
+ after: list
+
+
class ExtensionMetadata:
filename = "metadata.ini"
config: configparser.ConfigParser
@@ -65,6 +73,22 @@ class ExtensionMetadata:
# both "," and " " are accepted as separator
return [x for x in re.split(r"[,\s]+", text.strip()) if x]
+ def list_callback_order_instructions(self):
+ for section in self.config.sections():
+ if not section.startswith("callbacks/"):
+ continue
+
+ callback_name = section[10:]
+
+ if not callback_name.startswith(self.canonical_name):
+ errors.report(f"Callback order section for extension {self.canonical_name} is referencing the wrong extension: {section}")
+ continue
+
+ before = self.parse_list(self.config.get(section, 'Before', fallback=''))
+ after = self.parse_list(self.config.get(section, 'After', fallback=''))
+
+ yield CallbackOrderInfo(callback_name, before, after)
+
class Extension:
lock = threading.Lock()
@@ -156,6 +180,8 @@ class Extension:
def check_updates(self):
repo = Repo(self.path)
for fetch in repo.remote().fetch(dry_run=True):
+ if self.branch and fetch.name != f'{repo.remote().name}/{self.branch}':
+ continue
if fetch.flags != fetch.HEAD_UPTODATE:
self.can_update = True
self.status = "new commits"
@@ -186,6 +212,7 @@ class Extension:
def list_extensions():
extensions.clear()
+ extension_paths.clear()
if shared.cmd_opts.disable_all_extensions:
print("*** \"--disable-all-extensions\" arg was used, will not load any extensions ***")
@@ -220,6 +247,7 @@ def list_extensions():
is_builtin = dirname == extensions_builtin_dir
extension = Extension(name=extension_dirname, path=path, enabled=extension_dirname not in shared.opts.disabled_extensions, is_builtin=is_builtin, metadata=metadata)
extensions.append(extension)
+ extension_paths[extension.path] = extension
loaded_extensions[canonical_name] = extension
# check for requirements
@@ -238,4 +266,19 @@ def list_extensions():
continue
+def find_extension(filename):
+ parentdir = os.path.dirname(os.path.realpath(filename))
+
+ while parentdir != filename:
+ extension = extension_paths.get(parentdir)
+ if extension is not None:
+ return extension
+
+ filename = parentdir
+ parentdir = os.path.dirname(filename)
+
+ return None
+
+
extensions: list[Extension] = []
+extension_paths: dict[str, Extension] = {}
diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py
index db1866449..723cb1f82 100644
--- a/modules/infotext_utils.py
+++ b/modules/infotext_utils.py
@@ -265,17 +265,6 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model
else:
prompt += ("" if prompt == "" else "\n") + line
- if shared.opts.infotext_styles != "Ignore":
- found_styles, prompt, negative_prompt = shared.prompt_styles.extract_styles_from_prompt(prompt, negative_prompt)
-
- if shared.opts.infotext_styles == "Apply":
- res["Styles array"] = found_styles
- elif shared.opts.infotext_styles == "Apply if any" and found_styles:
- res["Styles array"] = found_styles
-
- res["Prompt"] = prompt
- res["Negative prompt"] = negative_prompt
-
for k, v in re_param.findall(lastline):
try:
if v[0] == '"' and v[-1] == '"':
@@ -290,6 +279,26 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model
except Exception:
print(f"Error parsing \"{k}: {v}\"")
+ # Extract styles from prompt
+ if shared.opts.infotext_styles != "Ignore":
+ found_styles, prompt_no_styles, negative_prompt_no_styles = shared.prompt_styles.extract_styles_from_prompt(prompt, negative_prompt)
+
+ same_hr_styles = True
+ if ("Hires prompt" in res or "Hires negative prompt" in res) and (infotext_ver > infotext_versions.v180_hr_styles if (infotext_ver := infotext_versions.parse_version(res.get("Version"))) else True):
+ hr_prompt, hr_negative_prompt = res.get("Hires prompt", prompt), res.get("Hires negative prompt", negative_prompt)
+ hr_found_styles, hr_prompt_no_styles, hr_negative_prompt_no_styles = shared.prompt_styles.extract_styles_from_prompt(hr_prompt, hr_negative_prompt)
+ if same_hr_styles := found_styles == hr_found_styles:
+ res["Hires prompt"] = '' if hr_prompt_no_styles == prompt_no_styles else hr_prompt_no_styles
+ res['Hires negative prompt'] = '' if hr_negative_prompt_no_styles == negative_prompt_no_styles else hr_negative_prompt_no_styles
+
+ if same_hr_styles:
+ prompt, negative_prompt = prompt_no_styles, negative_prompt_no_styles
+ if (shared.opts.infotext_styles == "Apply if any" and found_styles) or shared.opts.infotext_styles == "Apply":
+ res['Styles array'] = found_styles
+
+ res["Prompt"] = prompt
+ res["Negative prompt"] = negative_prompt
+
# Missing CLIP skip means it was set to 1 (the default)
if "Clip skip" not in res:
res["Clip skip"] = "1"
@@ -462,7 +471,7 @@ def get_override_settings(params, *, skip_fields=None):
def connect_paste(button, paste_fields, input_comp, override_settings_component, tabname):
def paste_func(prompt):
- if not prompt and not shared.cmd_opts.hide_ui_dir_config:
+ if not prompt and not shared.cmd_opts.hide_ui_dir_config and not shared.cmd_opts.no_prompt_history:
filename = os.path.join(data_path, "params.txt")
try:
with open(filename, "r", encoding="utf8") as file:
diff --git a/modules/infotext_versions.py b/modules/infotext_versions.py
index b5552a312..cea676cda 100644
--- a/modules/infotext_versions.py
+++ b/modules/infotext_versions.py
@@ -6,6 +6,7 @@ import re
v160 = version.parse("1.6.0")
v170_tsnr = version.parse("v1.7.0-225")
v180 = version.parse("1.8.0")
+v180_hr_styles = version.parse("1.8.0-139")
def parse_version(text):
diff --git a/modules/options.py b/modules/options.py
index 35ccade25..2a78a825e 100644
--- a/modules/options.py
+++ b/modules/options.py
@@ -240,6 +240,9 @@ class Options:
item_categories = {}
for item in self.data_labels.values():
+ if item.section[0] is None:
+ continue
+
category = categories.mapping.get(item.category_id)
category = "Uncategorized" if category is None else category.label
if category not in item_categories:
diff --git a/modules/processing.py b/modules/processing.py
index 93493f80e..d6873a510 100644
--- a/modules/processing.py
+++ b/modules/processing.py
@@ -702,7 +702,7 @@ def program_version():
return res
-def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0, use_main_prompt=False, index=None, all_negative_prompts=None):
+def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0, use_main_prompt=False, index=None, all_negative_prompts=None, all_hr_prompts=None, all_hr_negative_prompts=None):
if index is None:
index = position_in_batch + iteration * p.batch_size
@@ -745,11 +745,18 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter
"RNG": opts.randn_source if opts.randn_source != "GPU" else None,
"NGMS": None if p.s_min_uncond == 0 else p.s_min_uncond,
"Tiling": "True" if p.tiling else None,
+ "Hires prompt": None, # This is set later, insert here to keep order
+ "Hires negative prompt": None, # This is set later, insert here to keep order
**p.extra_generation_params,
"Version": program_version() if opts.add_version_to_infotext else None,
"User": p.user if opts.add_user_name_to_info else None,
}
+ if all_hr_prompts := all_hr_prompts or getattr(p, 'all_hr_prompts', None):
+ generation_params['Hires prompt'] = all_hr_prompts[index] if all_hr_prompts[index] != all_prompts[index] else None
+ if all_hr_negative_prompts := all_hr_negative_prompts or getattr(p, 'all_hr_negative_prompts', None):
+ generation_params['Hires negative prompt'] = all_hr_negative_prompts[index] if all_hr_negative_prompts[index] != all_negative_prompts[index] else None
+
generation_params_text = ", ".join([k if k == v else f'{k}: {infotext_utils.quote(v)}' for k, v in generation_params.items() if v is not None])
prompt_text = p.main_prompt if use_main_prompt else all_prompts[index]
@@ -904,7 +911,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
# infotext could be modified by that callback
# Example: a wildcard processed by process_batch sets an extra model
# strength, which is saved as "Model Strength: 1.0" in the infotext
- if n == 0:
+ if n == 0 and not cmd_opts.no_prompt_history:
with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file:
processed = Processed(p, [])
file.write(processed.infotext(p, 0))
@@ -1194,12 +1201,6 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
if self.hr_sampler_name is not None and self.hr_sampler_name != self.sampler_name:
self.extra_generation_params["Hires sampler"] = self.hr_sampler_name
- if tuple(self.hr_prompt) != tuple(self.prompt):
- self.extra_generation_params["Hires prompt"] = self.hr_prompt
-
- if tuple(self.hr_negative_prompt) != tuple(self.negative_prompt):
- self.extra_generation_params["Hires negative prompt"] = self.hr_negative_prompt
-
self.latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "nearest")
if self.enable_hr and self.latent_scale_mode is None:
if not any(x.name == self.hr_upscaler for x in shared.sd_upscalers):
diff --git a/modules/processing_scripts/comments.py b/modules/processing_scripts/comments.py
index 638e39f29..cf81dfd8b 100644
--- a/modules/processing_scripts/comments.py
+++ b/modules/processing_scripts/comments.py
@@ -26,6 +26,13 @@ class ScriptStripComments(scripts.Script):
p.main_prompt = strip_comments(p.main_prompt)
p.main_negative_prompt = strip_comments(p.main_negative_prompt)
+ if getattr(p, 'enable_hr', False):
+ p.all_hr_prompts = [strip_comments(x) for x in p.all_hr_prompts]
+ p.all_hr_negative_prompts = [strip_comments(x) for x in p.all_hr_negative_prompts]
+
+ p.hr_prompt = strip_comments(p.hr_prompt)
+ p.hr_negative_prompt = strip_comments(p.hr_negative_prompt)
+
def before_token_counter(params: script_callbacks.BeforeTokenCounterParams):
if not shared.opts.enable_prompt_comments:
diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py
index 08bc52564..2cd65e832 100644
--- a/modules/script_callbacks.py
+++ b/modules/script_callbacks.py
@@ -1,13 +1,14 @@
+from __future__ import annotations
+
import dataclasses
import inspect
import os
-from collections import namedtuple
from typing import Optional, Any
from fastapi import FastAPI
from gradio import Blocks
-from modules import errors, timer
+from modules import errors, timer, extensions, shared, util
def report_exception(c, job):
@@ -116,7 +117,105 @@ class BeforeTokenCounterParams:
is_positive: bool = True
-ScriptCallback = namedtuple("ScriptCallback", ["script", "callback"])
+@dataclasses.dataclass
+class ScriptCallback:
+ script: str
+ callback: any
+ name: str = None
+
+
+def add_callback(callbacks, fun, *, name=None, category='unknown', filename=None):
+ if filename is None:
+ stack = [x for x in inspect.stack() if x.filename != __file__]
+ filename = stack[0].filename if stack else 'unknown file'
+
+ extension = extensions.find_extension(filename)
+ extension_name = extension.canonical_name if extension else 'base'
+
+ callback_name = f"{extension_name}/{os.path.basename(filename)}/{category}"
+ if name is not None:
+ callback_name += f'/{name}'
+
+ unique_callback_name = callback_name
+ for index in range(1000):
+ existing = any(x.name == unique_callback_name for x in callbacks)
+ if not existing:
+ break
+
+ unique_callback_name = f'{callback_name}-{index+1}'
+
+ callbacks.append(ScriptCallback(filename, fun, unique_callback_name))
+
+
+def sort_callbacks(category, unordered_callbacks, *, enable_user_sort=True):
+ callbacks = unordered_callbacks.copy()
+ callback_lookup = {x.name: x for x in callbacks}
+ dependencies = {}
+
+ order_instructions = {}
+ for extension in extensions.extensions:
+ for order_instruction in extension.metadata.list_callback_order_instructions():
+ if order_instruction.name in callback_lookup:
+ if order_instruction.name not in order_instructions:
+ order_instructions[order_instruction.name] = []
+
+ order_instructions[order_instruction.name].append(order_instruction)
+
+ if order_instructions:
+ for callback in callbacks:
+ dependencies[callback.name] = []
+
+ for callback in callbacks:
+ for order_instruction in order_instructions.get(callback.name, []):
+ for after in order_instruction.after:
+ if after not in callback_lookup:
+ continue
+
+ dependencies[callback.name].append(after)
+
+ for before in order_instruction.before:
+ if before not in callback_lookup:
+ continue
+
+ dependencies[before].append(callback.name)
+
+ sorted_names = util.topological_sort(dependencies)
+ callbacks = [callback_lookup[x] for x in sorted_names]
+
+ if enable_user_sort:
+ for name in reversed(getattr(shared.opts, 'prioritized_callbacks_' + category, [])):
+ index = next((i for i, callback in enumerate(callbacks) if callback.name == name), None)
+ if index is not None:
+ callbacks.insert(0, callbacks.pop(index))
+
+ return callbacks
+
+
+def ordered_callbacks(category, unordered_callbacks=None, *, enable_user_sort=True):
+ if unordered_callbacks is None:
+ unordered_callbacks = callback_map.get('callbacks_' + category, [])
+
+ if not enable_user_sort:
+ return sort_callbacks(category, unordered_callbacks, enable_user_sort=False)
+
+ callbacks = ordered_callbacks_map.get(category)
+ if callbacks is not None and len(callbacks) == len(unordered_callbacks):
+ return callbacks
+
+ callbacks = sort_callbacks(category, unordered_callbacks)
+
+ ordered_callbacks_map[category] = callbacks
+ return callbacks
+
+
+def enumerate_callbacks():
+ for category, callbacks in callback_map.items():
+ if category.startswith('callbacks_'):
+ category = category[10:]
+
+ yield category, callbacks
+
+
callback_map = dict(
callbacks_app_started=[],
callbacks_model_loaded=[],
@@ -141,14 +240,18 @@ callback_map = dict(
callbacks_before_token_counter=[],
)
+ordered_callbacks_map = {}
+
def clear_callbacks():
for callback_list in callback_map.values():
callback_list.clear()
+ ordered_callbacks_map.clear()
+
def app_started_callback(demo: Optional[Blocks], app: FastAPI):
- for c in callback_map['callbacks_app_started']:
+ for c in ordered_callbacks('app_started'):
try:
c.callback(demo, app)
timer.startup_timer.record(os.path.basename(c.script))
@@ -157,7 +260,7 @@ def app_started_callback(demo: Optional[Blocks], app: FastAPI):
def app_reload_callback():
- for c in callback_map['callbacks_on_reload']:
+ for c in ordered_callbacks('on_reload'):
try:
c.callback()
except Exception:
@@ -165,7 +268,7 @@ def app_reload_callback():
def model_loaded_callback(sd_model):
- for c in callback_map['callbacks_model_loaded']:
+ for c in ordered_callbacks('model_loaded'):
try:
c.callback(sd_model)
except Exception:
@@ -175,7 +278,7 @@ def model_loaded_callback(sd_model):
def ui_tabs_callback():
res = []
- for c in callback_map['callbacks_ui_tabs']:
+ for c in ordered_callbacks('ui_tabs'):
try:
res += c.callback() or []
except Exception:
@@ -185,7 +288,7 @@ def ui_tabs_callback():
def ui_train_tabs_callback(params: UiTrainTabParams):
- for c in callback_map['callbacks_ui_train_tabs']:
+ for c in ordered_callbacks('ui_train_tabs'):
try:
c.callback(params)
except Exception:
@@ -193,7 +296,7 @@ def ui_train_tabs_callback(params: UiTrainTabParams):
def ui_settings_callback():
- for c in callback_map['callbacks_ui_settings']:
+ for c in ordered_callbacks('ui_settings'):
try:
c.callback()
except Exception:
@@ -201,7 +304,7 @@ def ui_settings_callback():
def before_image_saved_callback(params: ImageSaveParams):
- for c in callback_map['callbacks_before_image_saved']:
+ for c in ordered_callbacks('before_image_saved'):
try:
c.callback(params)
except Exception:
@@ -209,7 +312,7 @@ def before_image_saved_callback(params: ImageSaveParams):
def image_saved_callback(params: ImageSaveParams):
- for c in callback_map['callbacks_image_saved']:
+ for c in ordered_callbacks('image_saved'):
try:
c.callback(params)
except Exception:
@@ -217,7 +320,7 @@ def image_saved_callback(params: ImageSaveParams):
def extra_noise_callback(params: ExtraNoiseParams):
- for c in callback_map['callbacks_extra_noise']:
+ for c in ordered_callbacks('extra_noise'):
try:
c.callback(params)
except Exception:
@@ -225,7 +328,7 @@ def extra_noise_callback(params: ExtraNoiseParams):
def cfg_denoiser_callback(params: CFGDenoiserParams):
- for c in callback_map['callbacks_cfg_denoiser']:
+ for c in ordered_callbacks('cfg_denoiser'):
try:
c.callback(params)
except Exception:
@@ -233,7 +336,7 @@ def cfg_denoiser_callback(params: CFGDenoiserParams):
def cfg_denoised_callback(params: CFGDenoisedParams):
- for c in callback_map['callbacks_cfg_denoised']:
+ for c in ordered_callbacks('cfg_denoised'):
try:
c.callback(params)
except Exception:
@@ -241,7 +344,7 @@ def cfg_denoised_callback(params: CFGDenoisedParams):
def cfg_after_cfg_callback(params: AfterCFGCallbackParams):
- for c in callback_map['callbacks_cfg_after_cfg']:
+ for c in ordered_callbacks('cfg_after_cfg'):
try:
c.callback(params)
except Exception:
@@ -249,7 +352,7 @@ def cfg_after_cfg_callback(params: AfterCFGCallbackParams):
def before_component_callback(component, **kwargs):
- for c in callback_map['callbacks_before_component']:
+ for c in ordered_callbacks('before_component'):
try:
c.callback(component, **kwargs)
except Exception:
@@ -257,7 +360,7 @@ def before_component_callback(component, **kwargs):
def after_component_callback(component, **kwargs):
- for c in callback_map['callbacks_after_component']:
+ for c in ordered_callbacks('after_component'):
try:
c.callback(component, **kwargs)
except Exception:
@@ -265,7 +368,7 @@ def after_component_callback(component, **kwargs):
def image_grid_callback(params: ImageGridLoopParams):
- for c in callback_map['callbacks_image_grid']:
+ for c in ordered_callbacks('image_grid'):
try:
c.callback(params)
except Exception:
@@ -273,7 +376,7 @@ def image_grid_callback(params: ImageGridLoopParams):
def infotext_pasted_callback(infotext: str, params: dict[str, Any]):
- for c in callback_map['callbacks_infotext_pasted']:
+ for c in ordered_callbacks('infotext_pasted'):
try:
c.callback(infotext, params)
except Exception:
@@ -281,7 +384,7 @@ def infotext_pasted_callback(infotext: str, params: dict[str, Any]):
def script_unloaded_callback():
- for c in reversed(callback_map['callbacks_script_unloaded']):
+ for c in reversed(ordered_callbacks('script_unloaded')):
try:
c.callback()
except Exception:
@@ -289,7 +392,7 @@ def script_unloaded_callback():
def before_ui_callback():
- for c in reversed(callback_map['callbacks_before_ui']):
+ for c in reversed(ordered_callbacks('before_ui')):
try:
c.callback()
except Exception:
@@ -299,7 +402,7 @@ def before_ui_callback():
def list_optimizers_callback():
res = []
- for c in callback_map['callbacks_list_optimizers']:
+ for c in ordered_callbacks('list_optimizers'):
try:
c.callback(res)
except Exception:
@@ -311,7 +414,7 @@ def list_optimizers_callback():
def list_unets_callback():
res = []
- for c in callback_map['callbacks_list_unets']:
+ for c in ordered_callbacks('list_unets'):
try:
c.callback(res)
except Exception:
@@ -321,20 +424,13 @@ def list_unets_callback():
def before_token_counter_callback(params: BeforeTokenCounterParams):
- for c in callback_map['callbacks_before_token_counter']:
+ for c in ordered_callbacks('before_token_counter'):
try:
c.callback(params)
except Exception:
report_exception(c, 'before_token_counter')
-def add_callback(callbacks, fun):
- stack = [x for x in inspect.stack() if x.filename != __file__]
- filename = stack[0].filename if stack else 'unknown file'
-
- callbacks.append(ScriptCallback(filename, fun))
-
-
def remove_current_script_callbacks():
stack = [x for x in inspect.stack() if x.filename != __file__]
filename = stack[0].filename if stack else 'unknown file'
@@ -351,24 +447,24 @@ def remove_callbacks_for_function(callback_func):
callback_list.remove(callback_to_remove)
-def on_app_started(callback):
+def on_app_started(callback, *, name=None):
"""register a function to be called when the webui started, the gradio `Block` component and
fastapi `FastAPI` object are passed as the arguments"""
- add_callback(callback_map['callbacks_app_started'], callback)
+ add_callback(callback_map['callbacks_app_started'], callback, name=name, category='app_started')
-def on_before_reload(callback):
+def on_before_reload(callback, *, name=None):
"""register a function to be called just before the server reloads."""
- add_callback(callback_map['callbacks_on_reload'], callback)
+ add_callback(callback_map['callbacks_on_reload'], callback, name=name, category='on_reload')
-def on_model_loaded(callback):
+def on_model_loaded(callback, *, name=None):
"""register a function to be called when the stable diffusion model is created; the model is
passed as an argument; this function is also called when the script is reloaded. """
- add_callback(callback_map['callbacks_model_loaded'], callback)
+ add_callback(callback_map['callbacks_model_loaded'], callback, name=name, category='model_loaded')
-def on_ui_tabs(callback):
+def on_ui_tabs(callback, *, name=None):
"""register a function to be called when the UI is creating new tabs.
The function must either return a None, which means no new tabs to be added, or a list, where
each element is a tuple:
@@ -378,71 +474,71 @@ def on_ui_tabs(callback):
title is tab text displayed to user in the UI
elem_id is HTML id for the tab
"""
- add_callback(callback_map['callbacks_ui_tabs'], callback)
+ add_callback(callback_map['callbacks_ui_tabs'], callback, name=name, category='ui_tabs')
-def on_ui_train_tabs(callback):
+def on_ui_train_tabs(callback, *, name=None):
"""register a function to be called when the UI is creating new tabs for the train tab.
Create your new tabs with gr.Tab.
"""
- add_callback(callback_map['callbacks_ui_train_tabs'], callback)
+ add_callback(callback_map['callbacks_ui_train_tabs'], callback, name=name, category='ui_train_tabs')
-def on_ui_settings(callback):
+def on_ui_settings(callback, *, name=None):
"""register a function to be called before UI settings are populated; add your settings
by using shared.opts.add_option(shared.OptionInfo(...)) """
- add_callback(callback_map['callbacks_ui_settings'], callback)
+ add_callback(callback_map['callbacks_ui_settings'], callback, name=name, category='ui_settings')
-def on_before_image_saved(callback):
+def on_before_image_saved(callback, *, name=None):
"""register a function to be called before an image is saved to a file.
The callback is called with one argument:
- params: ImageSaveParams - parameters the image is to be saved with. You can change fields in this object.
"""
- add_callback(callback_map['callbacks_before_image_saved'], callback)
+ add_callback(callback_map['callbacks_before_image_saved'], callback, name=name, category='before_image_saved')
-def on_image_saved(callback):
+def on_image_saved(callback, *, name=None):
"""register a function to be called after an image is saved to a file.
The callback is called with one argument:
- params: ImageSaveParams - parameters the image was saved with. Changing fields in this object does nothing.
"""
- add_callback(callback_map['callbacks_image_saved'], callback)
+ add_callback(callback_map['callbacks_image_saved'], callback, name=name, category='image_saved')
-def on_extra_noise(callback):
+def on_extra_noise(callback, *, name=None):
"""register a function to be called before adding extra noise in img2img or hires fix;
The callback is called with one argument:
- params: ExtraNoiseParams - contains noise determined by seed and latent representation of image
"""
- add_callback(callback_map['callbacks_extra_noise'], callback)
+ add_callback(callback_map['callbacks_extra_noise'], callback, name=name, category='extra_noise')
-def on_cfg_denoiser(callback):
+def on_cfg_denoiser(callback, *, name=None):
"""register a function to be called in the kdiffussion cfg_denoiser method after building the inner model inputs.
The callback is called with one argument:
- params: CFGDenoiserParams - parameters to be passed to the inner model and sampling state details.
"""
- add_callback(callback_map['callbacks_cfg_denoiser'], callback)
+ add_callback(callback_map['callbacks_cfg_denoiser'], callback, name=name, category='cfg_denoiser')
-def on_cfg_denoised(callback):
+def on_cfg_denoised(callback, *, name=None):
"""register a function to be called in the kdiffussion cfg_denoiser method after building the inner model inputs.
The callback is called with one argument:
- params: CFGDenoisedParams - parameters to be passed to the inner model and sampling state details.
"""
- add_callback(callback_map['callbacks_cfg_denoised'], callback)
+ add_callback(callback_map['callbacks_cfg_denoised'], callback, name=name, category='cfg_denoised')
-def on_cfg_after_cfg(callback):
+def on_cfg_after_cfg(callback, *, name=None):
"""register a function to be called in the kdiffussion cfg_denoiser method after cfg calculations are completed.
The callback is called with one argument:
- params: AfterCFGCallbackParams - parameters to be passed to the script for post-processing after cfg calculation.
"""
- add_callback(callback_map['callbacks_cfg_after_cfg'], callback)
+ add_callback(callback_map['callbacks_cfg_after_cfg'], callback, name=name, category='cfg_after_cfg')
-def on_before_component(callback):
+def on_before_component(callback, *, name=None):
"""register a function to be called before a component is created.
The callback is called with arguments:
- component - gradio component that is about to be created.
@@ -451,61 +547,61 @@ def on_before_component(callback):
Use elem_id/label fields of kwargs to figure out which component it is.
This can be useful to inject your own components somewhere in the middle of vanilla UI.
"""
- add_callback(callback_map['callbacks_before_component'], callback)
+ add_callback(callback_map['callbacks_before_component'], callback, name=name, category='before_component')
-def on_after_component(callback):
+def on_after_component(callback, *, name=None):
"""register a function to be called after a component is created. See on_before_component for more."""
- add_callback(callback_map['callbacks_after_component'], callback)
+ add_callback(callback_map['callbacks_after_component'], callback, name=name, category='after_component')
-def on_image_grid(callback):
+def on_image_grid(callback, *, name=None):
"""register a function to be called before making an image grid.
The callback is called with one argument:
- params: ImageGridLoopParams - parameters to be used for grid creation. Can be modified.
"""
- add_callback(callback_map['callbacks_image_grid'], callback)
+ add_callback(callback_map['callbacks_image_grid'], callback, name=name, category='image_grid')
-def on_infotext_pasted(callback):
+def on_infotext_pasted(callback, *, name=None):
"""register a function to be called before applying an infotext.
The callback is called with two arguments:
- infotext: str - raw infotext.
- result: dict[str, any] - parsed infotext parameters.
"""
- add_callback(callback_map['callbacks_infotext_pasted'], callback)
+ add_callback(callback_map['callbacks_infotext_pasted'], callback, name=name, category='infotext_pasted')
-def on_script_unloaded(callback):
+def on_script_unloaded(callback, *, name=None):
"""register a function to be called before the script is unloaded. Any hooks/hijacks/monkeying about that
the script did should be reverted here"""
- add_callback(callback_map['callbacks_script_unloaded'], callback)
+ add_callback(callback_map['callbacks_script_unloaded'], callback, name=name, category='script_unloaded')
-def on_before_ui(callback):
+def on_before_ui(callback, *, name=None):
"""register a function to be called before the UI is created."""
- add_callback(callback_map['callbacks_before_ui'], callback)
+ add_callback(callback_map['callbacks_before_ui'], callback, name=name, category='before_ui')
-def on_list_optimizers(callback):
+def on_list_optimizers(callback, *, name=None):
"""register a function to be called when UI is making a list of cross attention optimization options.
The function will be called with one argument, a list, and shall add objects of type modules.sd_hijack_optimizations.SdOptimization
to it."""
- add_callback(callback_map['callbacks_list_optimizers'], callback)
+ add_callback(callback_map['callbacks_list_optimizers'], callback, name=name, category='list_optimizers')
-def on_list_unets(callback):
+def on_list_unets(callback, *, name=None):
"""register a function to be called when UI is making a list of alternative options for unet.
The function will be called with one argument, a list, and shall add objects of type modules.sd_unet.SdUnetOption to it."""
- add_callback(callback_map['callbacks_list_unets'], callback)
+ add_callback(callback_map['callbacks_list_unets'], callback, name=name, category='list_unets')
-def on_before_token_counter(callback):
+def on_before_token_counter(callback, *, name=None):
"""register a function to be called when UI is counting tokens for a prompt.
The function will be called with one argument of type BeforeTokenCounterParams, and should modify its fields if necessary."""
- add_callback(callback_map['callbacks_before_token_counter'], callback)
+ add_callback(callback_map['callbacks_before_token_counter'], callback, name=name, category='before_token_counter')
diff --git a/modules/scripts.py b/modules/scripts.py
index 77f5e4f3e..20710b37d 100644
--- a/modules/scripts.py
+++ b/modules/scripts.py
@@ -7,7 +7,9 @@ from dataclasses import dataclass
import gradio as gr
-from modules import shared, paths, script_callbacks, extensions, script_loading, scripts_postprocessing, errors, timer
+from modules import shared, paths, script_callbacks, extensions, script_loading, scripts_postprocessing, errors, timer, util
+
+topological_sort = util.topological_sort
AlwaysVisible = object()
@@ -138,7 +140,6 @@ class Script:
"""
pass
-
def before_process(self, p, *args):
"""
This function is called very early during processing begins for AlwaysVisible scripts.
@@ -369,29 +370,6 @@ scripts_data = []
postprocessing_scripts_data = []
ScriptClassData = namedtuple("ScriptClassData", ["script_class", "path", "basedir", "module"])
-def topological_sort(dependencies):
- """Accepts a dictionary mapping name to its dependencies, returns a list of names ordered according to dependencies.
- Ignores errors relating to missing dependeencies or circular dependencies
- """
-
- visited = {}
- result = []
-
- def inner(name):
- visited[name] = True
-
- for dep in dependencies.get(name, []):
- if dep in dependencies and dep not in visited:
- inner(dep)
-
- result.append(name)
-
- for depname in dependencies:
- if depname not in visited:
- inner(depname)
-
- return result
-
@dataclass
class ScriptWithDependencies:
@@ -562,6 +540,25 @@ class ScriptRunner:
self.paste_field_names = []
self.inputs = [None]
+ self.callback_map = {}
+ self.callback_names = [
+ 'before_process',
+ 'process',
+ 'before_process_batch',
+ 'after_extra_networks_activate',
+ 'process_batch',
+ 'postprocess',
+ 'postprocess_batch',
+ 'postprocess_batch_list',
+ 'post_sample',
+ 'on_mask_blend',
+ 'postprocess_image',
+ 'postprocess_maskoverlay',
+ 'postprocess_image_after_composite',
+ 'before_component',
+ 'after_component',
+ ]
+
self.on_before_component_elem_id = {}
"""dict of callbacks to be called before an element is created; key=elem_id, value=list of callbacks"""
@@ -600,6 +597,8 @@ class ScriptRunner:
self.scripts.append(script)
self.selectable_scripts.append(script)
+ self.callback_map.clear()
+
self.apply_on_before_component_callbacks()
def apply_on_before_component_callbacks(self):
@@ -769,8 +768,42 @@ class ScriptRunner:
return processed
+ def list_scripts_for_method(self, method_name):
+ if method_name in ('before_component', 'after_component'):
+ return self.scripts
+ else:
+ return self.alwayson_scripts
+
+ def create_ordered_callbacks_list(self, method_name, *, enable_user_sort=True):
+ script_list = self.list_scripts_for_method(method_name)
+ category = f'script_{method_name}'
+ callbacks = []
+
+ for script in script_list:
+ if getattr(script.__class__, method_name, None) == getattr(Script, method_name, None):
+ continue
+
+ script_callbacks.add_callback(callbacks, script, category=category, name=script.__class__.__name__, filename=script.filename)
+
+ return script_callbacks.sort_callbacks(category, callbacks, enable_user_sort=enable_user_sort)
+
+ def ordered_callbacks(self, method_name, *, enable_user_sort=True):
+ script_list = self.list_scripts_for_method(method_name)
+ category = f'script_{method_name}'
+
+ scrpts_len, callbacks = self.callback_map.get(category, (-1, None))
+
+ if callbacks is None or scrpts_len != len(script_list):
+ callbacks = self.create_ordered_callbacks_list(method_name, enable_user_sort=enable_user_sort)
+ self.callback_map[category] = len(script_list), callbacks
+
+ return callbacks
+
+ def ordered_scripts(self, method_name):
+ return [x.callback for x in self.ordered_callbacks(method_name)]
+
def before_process(self, p):
- for script in self.alwayson_scripts:
+ for script in self.ordered_scripts('before_process'):
try:
script_args = p.script_args[script.args_from:script.args_to]
script.before_process(p, *script_args)
@@ -778,7 +811,7 @@ class ScriptRunner:
errors.report(f"Error running before_process: {script.filename}", exc_info=True)
def process(self, p):
- for script in self.alwayson_scripts:
+ for script in self.ordered_scripts('process'):
try:
script_args = p.script_args[script.args_from:script.args_to]
script.process(p, *script_args)
@@ -786,7 +819,7 @@ class ScriptRunner:
errors.report(f"Error running process: {script.filename}", exc_info=True)
def before_process_batch(self, p, **kwargs):
- for script in self.alwayson_scripts:
+ for script in self.ordered_scripts('before_process_batch'):
try:
script_args = p.script_args[script.args_from:script.args_to]
script.before_process_batch(p, *script_args, **kwargs)
@@ -794,7 +827,7 @@ class ScriptRunner:
errors.report(f"Error running before_process_batch: {script.filename}", exc_info=True)
def after_extra_networks_activate(self, p, **kwargs):
- for script in self.alwayson_scripts:
+ for script in self.ordered_scripts('after_extra_networks_activate'):
try:
script_args = p.script_args[script.args_from:script.args_to]
script.after_extra_networks_activate(p, *script_args, **kwargs)
@@ -802,7 +835,7 @@ class ScriptRunner:
errors.report(f"Error running after_extra_networks_activate: {script.filename}", exc_info=True)
def process_batch(self, p, **kwargs):
- for script in self.alwayson_scripts:
+ for script in self.ordered_scripts('process_batch'):
try:
script_args = p.script_args[script.args_from:script.args_to]
script.process_batch(p, *script_args, **kwargs)
@@ -810,7 +843,7 @@ class ScriptRunner:
errors.report(f"Error running process_batch: {script.filename}", exc_info=True)
def postprocess(self, p, processed):
- for script in self.alwayson_scripts:
+ for script in self.ordered_scripts('postprocess'):
try:
script_args = p.script_args[script.args_from:script.args_to]
script.postprocess(p, processed, *script_args)
@@ -818,7 +851,7 @@ class ScriptRunner:
errors.report(f"Error running postprocess: {script.filename}", exc_info=True)
def postprocess_batch(self, p, images, **kwargs):
- for script in self.alwayson_scripts:
+ for script in self.ordered_scripts('postprocess_batch'):
try:
script_args = p.script_args[script.args_from:script.args_to]
script.postprocess_batch(p, *script_args, images=images, **kwargs)
@@ -826,7 +859,7 @@ class ScriptRunner:
errors.report(f"Error running postprocess_batch: {script.filename}", exc_info=True)
def postprocess_batch_list(self, p, pp: PostprocessBatchListArgs, **kwargs):
- for script in self.alwayson_scripts:
+ for script in self.ordered_scripts('postprocess_batch_list'):
try:
script_args = p.script_args[script.args_from:script.args_to]
script.postprocess_batch_list(p, pp, *script_args, **kwargs)
@@ -834,7 +867,7 @@ class ScriptRunner:
errors.report(f"Error running postprocess_batch_list: {script.filename}", exc_info=True)
def post_sample(self, p, ps: PostSampleArgs):
- for script in self.alwayson_scripts:
+ for script in self.ordered_scripts('post_sample'):
try:
script_args = p.script_args[script.args_from:script.args_to]
script.post_sample(p, ps, *script_args)
@@ -842,7 +875,7 @@ class ScriptRunner:
errors.report(f"Error running post_sample: {script.filename}", exc_info=True)
def on_mask_blend(self, p, mba: MaskBlendArgs):
- for script in self.alwayson_scripts:
+ for script in self.ordered_scripts('on_mask_blend'):
try:
script_args = p.script_args[script.args_from:script.args_to]
script.on_mask_blend(p, mba, *script_args)
@@ -850,7 +883,7 @@ class ScriptRunner:
errors.report(f"Error running post_sample: {script.filename}", exc_info=True)
def postprocess_image(self, p, pp: PostprocessImageArgs):
- for script in self.alwayson_scripts:
+ for script in self.ordered_scripts('postprocess_image'):
try:
script_args = p.script_args[script.args_from:script.args_to]
script.postprocess_image(p, pp, *script_args)
@@ -858,7 +891,7 @@ class ScriptRunner:
errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True)
def postprocess_maskoverlay(self, p, ppmo: PostProcessMaskOverlayArgs):
- for script in self.alwayson_scripts:
+ for script in self.ordered_scripts('postprocess_maskoverlay'):
try:
script_args = p.script_args[script.args_from:script.args_to]
script.postprocess_maskoverlay(p, ppmo, *script_args)
@@ -866,7 +899,7 @@ class ScriptRunner:
errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True)
def postprocess_image_after_composite(self, p, pp: PostprocessImageArgs):
- for script in self.alwayson_scripts:
+ for script in self.ordered_scripts('postprocess_image_after_composite'):
try:
script_args = p.script_args[script.args_from:script.args_to]
script.postprocess_image_after_composite(p, pp, *script_args)
@@ -880,7 +913,7 @@ class ScriptRunner:
except Exception:
errors.report(f"Error running on_before_component: {script.filename}", exc_info=True)
- for script in self.scripts:
+ for script in self.ordered_scripts('before_component'):
try:
script.before_component(component, **kwargs)
except Exception:
@@ -893,7 +926,7 @@ class ScriptRunner:
except Exception:
errors.report(f"Error running on_after_component: {script.filename}", exc_info=True)
- for script in self.scripts:
+ for script in self.ordered_scripts('after_component'):
try:
script.after_component(component, **kwargs)
except Exception:
@@ -921,7 +954,7 @@ class ScriptRunner:
self.scripts[si].args_to = args_to
def before_hr(self, p):
- for script in self.alwayson_scripts:
+ for script in self.ordered_scripts('before_hr'):
try:
script_args = p.script_args[script.args_from:script.args_to]
script.before_hr(p, *script_args)
@@ -929,7 +962,7 @@ class ScriptRunner:
errors.report(f"Error running before_hr: {script.filename}", exc_info=True)
def setup_scrips(self, p, *, is_ui=True):
- for script in self.alwayson_scripts:
+ for script in self.ordered_scripts('setup'):
if not is_ui and script.setup_for_ui_only:
continue
diff --git a/modules/sd_models_xl.py b/modules/sd_models_xl.py
index 0de17af3d..94ff973fb 100644
--- a/modules/sd_models_xl.py
+++ b/modules/sd_models_xl.py
@@ -13,8 +13,8 @@ def get_learned_conditioning(self: sgm.models.diffusion.DiffusionEngine, batch:
for embedder in self.conditioner.embedders:
embedder.ucg_rate = 0.0
- width = getattr(batch, 'width', 1024)
- height = getattr(batch, 'height', 1024)
+ width = getattr(batch, 'width', 1024) or 1024
+ height = getattr(batch, 'height', 1024) or 1024
is_negative_prompt = getattr(batch, 'is_negative_prompt', False)
aesthetic_score = shared.opts.sdxl_refiner_low_aesthetic_score if is_negative_prompt else shared.opts.sdxl_refiner_high_aesthetic_score
diff --git a/modules/shared.py b/modules/shared.py
index b4ba14ad7..4cf7f6a81 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -6,6 +6,10 @@ import gradio as gr
from modules import shared_cmd_options, shared_gradio_themes, options, shared_items, sd_models_types
from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # noqa: F401
from modules import util
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from modules import shared_state, styles, interrogate, shared_total_tqdm, memmon
cmd_opts = shared_cmd_options.cmd_opts
parser = shared_cmd_options.parser
@@ -16,11 +20,11 @@ styles_filename = cmd_opts.styles_file = cmd_opts.styles_file if len(cmd_opts.st
config_filename = cmd_opts.ui_settings_file
hide_dirs = {"visible": not cmd_opts.hide_ui_dir_config}
-demo = None
+demo: gr.Blocks = None
-device = None
+device: str = None
-weight_load_location = None
+weight_load_location: str = None
xformers_available = False
@@ -28,21 +32,21 @@ hypernetworks = {}
loaded_hypernetworks = []
-state = None
+state: 'shared_state.State' = None
-prompt_styles = None
+prompt_styles: 'styles.StyleDatabase' = None
-interrogator = None
+interrogator: 'interrogate.InterrogateModels' = None
face_restorers = []
-options_templates = None
-opts = None
-restricted_opts = None
+options_templates: dict = None
+opts: options.Options = None
+restricted_opts: set[str] = None
sd_model: sd_models_types.WebuiSdModel = None
-settings_components = None
+settings_components: dict = None
"""assigned from ui.py, a mapping on setting names to gradio components repsponsible for those settings"""
tab_names = []
@@ -65,9 +69,9 @@ progress_print_out = sys.stdout
gradio_theme = gr.themes.Base()
-total_tqdm = None
+total_tqdm: 'shared_total_tqdm.TotalTQDM' = None
-mem_mon = None
+mem_mon: 'memmon.MemUsageMonitor' = None
options_section = options.options_section
OptionInfo = options.OptionInfo
diff --git a/modules/shared_items.py b/modules/shared_items.py
index 88f636452..11f10b3f7 100644
--- a/modules/shared_items.py
+++ b/modules/shared_items.py
@@ -1,5 +1,8 @@
+import html
import sys
+from modules import script_callbacks, scripts, ui_components
+from modules.options import OptionHTML, OptionInfo
from modules.shared_cmd_options import cmd_opts
@@ -118,6 +121,45 @@ def ui_reorder_categories():
yield "scripts"
+def callbacks_order_settings():
+ options = {
+ "sd_vae_explanation": OptionHTML("""
+ For categories below, callbacks added to dropdowns happen before others, in order listed.
+ """),
+
+ }
+
+ callback_options = {}
+
+ for category, _ in script_callbacks.enumerate_callbacks():
+ callback_options[category] = script_callbacks.ordered_callbacks(category, enable_user_sort=False)
+
+ for method_name in scripts.scripts_txt2img.callback_names:
+ callback_options["script_" + method_name] = scripts.scripts_txt2img.create_ordered_callbacks_list(method_name, enable_user_sort=False)
+
+ for method_name in scripts.scripts_img2img.callback_names:
+ callbacks = callback_options.get("script_" + method_name, [])
+
+ for addition in scripts.scripts_img2img.create_ordered_callbacks_list(method_name, enable_user_sort=False):
+ if any(x.name == addition.name for x in callbacks):
+ continue
+
+ callbacks.append(addition)
+
+ callback_options["script_" + method_name] = callbacks
+
+ for category, callbacks in callback_options.items():
+ if not callbacks:
+ continue
+
+ option_info = OptionInfo([], f"{category} callback priority", ui_components.DropdownMulti, {"choices": [x.name for x in callbacks]})
+ option_info.needs_restart()
+ option_info.html("
Default order: " + "".join(f"
{html.escape(x.name)}
\n" for x in callbacks) + "
")
+ options['prioritized_callbacks_' + category] = option_info
+
+ return options
+
+
class Shared(sys.modules[__name__].__class__):
"""
this class is here to provide sd_model field as a property, so that it can be created and loaded on demand rather than
diff --git a/modules/shared_options.py b/modules/shared_options.py
index 536766dbe..fc9f13d6f 100644
--- a/modules/shared_options.py
+++ b/modules/shared_options.py
@@ -101,6 +101,7 @@ options_templates.update(options_section(('upscaling', "Upscaling", "postprocess
"DAT_tile": OptionInfo(192, "Tile size for DAT upscalers.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}).info("0 = no tiling"),
"DAT_tile_overlap": OptionInfo(8, "Tile overlap for DAT upscalers.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}).info("Low values = visible seam"),
"upscaler_for_img2img": OptionInfo(None, "Upscaler for img2img", gr.Dropdown, lambda: {"choices": [x.name for x in shared.sd_upscalers]}),
+ "set_scale_by_when_changing_upscaler": OptionInfo(False, "Automatically set the Scale by factor based on the name of the selected Upscaler."),
}))
options_templates.update(options_section(('face-restoration', "Face restoration", "postprocessing"), {
@@ -258,7 +259,8 @@ options_templates.update(options_section(('extra_networks', "Extra Networks", "s
"extra_networks_card_description_is_html": OptionInfo(False, "Treat card description as HTML"),
"extra_networks_card_order_field": OptionInfo("Path", "Default order field for Extra Networks cards", gr.Dropdown, {"choices": ['Path', 'Name', 'Date Created', 'Date Modified']}).needs_reload_ui(),
"extra_networks_card_order": OptionInfo("Ascending", "Default order for Extra Networks cards", gr.Dropdown, {"choices": ['Ascending', 'Descending']}).needs_reload_ui(),
- "extra_networks_tree_view_default_enabled": OptionInfo(False, "Enables the Extra Networks directory tree view by default").needs_reload_ui(),
+ "extra_networks_tree_view_style": OptionInfo("Dirs", "Extra Networks directory view style", gr.Radio, {"choices": ["Tree", "Dirs"]}).needs_reload_ui(),
+ "extra_networks_tree_view_default_enabled": OptionInfo(True, "Show the Extra Networks directory view by default").needs_reload_ui(),
"extra_networks_tree_view_default_width": OptionInfo(180, "Default width for the Extra Networks directory tree view", gr.Number).needs_reload_ui(),
"extra_networks_add_text_separator": OptionInfo(" ", "Extra networks separator").info("extra text to add before <...> when adding extra network to prompt"),
"ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order").needs_reload_ui(),
diff --git a/modules/styles.py b/modules/styles.py
index a9d8636a9..25f22d3dd 100644
--- a/modules/styles.py
+++ b/modules/styles.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
from pathlib import Path
from modules import errors
import csv
diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py
index ad2c23054..f4627ce8d 100644
--- a/modules/ui_extra_networks.py
+++ b/modules/ui_extra_networks.py
@@ -164,6 +164,8 @@ class ExtraNetworksPage:
self.lister = util.MassFileLister()
# HTML Templates
self.pane_tpl = shared.html("extra-networks-pane.html")
+ self.pane_content_tree_tpl = shared.html("extra-networks-pane-tree.html")
+ self.pane_content_dirs_tpl = shared.html("extra-networks-pane-dirs.html")
self.card_tpl = shared.html("extra-networks-card.html")
self.btn_tree_tpl = shared.html("extra-networks-tree-button.html")
self.btn_copy_path_tpl = shared.html("extra-networks-copy-path-button.html")
@@ -476,6 +478,47 @@ class ExtraNetworksPage:
return f"
{res}
"
+ def create_dirs_view_html(self, tabname: str) -> str:
+ """Generates HTML for displaying folders."""
+
+ subdirs = {}
+ for parentdir in [os.path.abspath(x) for x in self.allowed_directories_for_previews()]:
+ for root, dirs, _ in sorted(os.walk(parentdir, followlinks=True), key=lambda x: shared.natural_sort_key(x[0])):
+ for dirname in sorted(dirs, key=shared.natural_sort_key):
+ x = os.path.join(root, dirname)
+
+ if not os.path.isdir(x):
+ continue
+
+ subdir = os.path.abspath(x)[len(parentdir):]
+
+ if shared.opts.extra_networks_dir_button_function:
+ if not subdir.startswith(os.path.sep):
+ subdir = os.path.sep + subdir
+ else:
+ while subdir.startswith(os.path.sep):
+ subdir = subdir[1:]
+
+ is_empty = len(os.listdir(x)) == 0
+ if not is_empty and not subdir.endswith(os.path.sep):
+ subdir = subdir + os.path.sep
+
+ if (os.path.sep + "." in subdir or subdir.startswith(".")) and not shared.opts.extra_networks_show_hidden_directories:
+ continue
+
+ subdirs[subdir] = 1
+
+ if subdirs:
+ subdirs = {"": 1, **subdirs}
+
+ subdirs_html = "".join([f"""
+
+ """ for subdir in subdirs])
+
+ return subdirs_html
+
def create_card_view_html(self, tabname: str, *, none_message) -> str:
"""Generates HTML for the network Card View section for a tab.
@@ -489,15 +532,15 @@ class ExtraNetworksPage:
Returns:
HTML formatted string.
"""
- res = ""
+ res = []
for item in self.items.values():
- res += self.create_item_html(tabname, item, self.card_tpl)
+ res.append(self.create_item_html(tabname, item, self.card_tpl))
- if res == "":
+ if not res:
dirs = "".join([f"
{x}
" for x in self.allowed_directories_for_previews()])
- res = none_message or shared.html("extra-networks-no-cards.html").format(dirs=dirs)
+ res = [none_message or shared.html("extra-networks-no-cards.html").format(dirs=dirs)]
- return res
+ return "".join(res)
def create_html(self, tabname, *, empty=False):
"""Generates an HTML string for the current pane.
@@ -526,35 +569,28 @@ class ExtraNetworksPage:
if "user_metadata" not in item:
self.read_user_metadata(item)
- data_sortdir = shared.opts.extra_networks_card_order
- data_sortmode = shared.opts.extra_networks_card_order_field.lower().replace("sort", "").replace(" ", "_").rstrip("_").strip()
- data_sortkey = f"{data_sortmode}-{data_sortdir}-{len(self.items)}"
- tree_view_btn_extra_class = ""
- tree_view_div_extra_class = "hidden"
- tree_view_div_default_display = "none"
- extra_network_pane_content_default_display = "flex"
- if shared.opts.extra_networks_tree_view_default_enabled:
- tree_view_btn_extra_class = "extra-network-control--enabled"
- tree_view_div_extra_class = ""
- tree_view_div_default_display = "block"
- extra_network_pane_content_default_display = "grid"
+ show_tree = shared.opts.extra_networks_tree_view_default_enabled
- return self.pane_tpl.format(
- **{
- "tabname": tabname,
- "extra_networks_tabname": self.extra_networks_tabname,
- "data_sortmode": data_sortmode,
- "data_sortkey": data_sortkey,
- "data_sortdir": data_sortdir,
- "tree_view_btn_extra_class": tree_view_btn_extra_class,
- "tree_view_div_extra_class": tree_view_div_extra_class,
- "tree_html": self.create_tree_view_html(tabname),
- "items_html": self.create_card_view_html(tabname, none_message="Loading..." if empty else None),
- "extra_networks_tree_view_default_width": shared.opts.extra_networks_tree_view_default_width,
- "tree_view_div_default_display": tree_view_div_default_display,
- "extra_network_pane_content_default_display": extra_network_pane_content_default_display,
- }
- )
+ page_params = {
+ "tabname": tabname,
+ "extra_networks_tabname": self.extra_networks_tabname,
+ "data_sortdir": shared.opts.extra_networks_card_order,
+ "sort_path_active": ' extra-network-control--enabled' if shared.opts.extra_networks_card_order_field == 'Path' else '',
+ "sort_name_active": ' extra-network-control--enabled' if shared.opts.extra_networks_card_order_field == 'Name' else '',
+ "sort_date_created_active": ' extra-network-control--enabled' if shared.opts.extra_networks_card_order_field == 'Date Created' else '',
+ "sort_date_modified_active": ' extra-network-control--enabled' if shared.opts.extra_networks_card_order_field == 'Date Modified' else '',
+ "tree_view_btn_extra_class": "extra-network-control--enabled" if show_tree else "",
+ "items_html": self.create_card_view_html(tabname, none_message="Loading..." if empty else None),
+ "extra_networks_tree_view_default_width": shared.opts.extra_networks_tree_view_default_width,
+ "tree_view_div_default_display_class": "" if show_tree else "extra-network-dirs-hidden",
+ }
+
+ if shared.opts.extra_networks_tree_view_style == "Tree":
+ pane_content = self.pane_content_tree_tpl.format(**page_params, tree_html=self.create_tree_view_html(tabname))
+ else:
+ pane_content = self.pane_content_dirs_tpl.format(**page_params, dirs_html=self.create_dirs_view_html(tabname))
+
+ return self.pane_tpl.format(**page_params, pane_content=pane_content)
def create_item(self, name, index=None):
raise NotImplementedError()
diff --git a/modules/ui_extra_networks_user_metadata.py b/modules/ui_extra_networks_user_metadata.py
index 2ca937fd1..fde093700 100644
--- a/modules/ui_extra_networks_user_metadata.py
+++ b/modules/ui_extra_networks_user_metadata.py
@@ -133,8 +133,10 @@ class UserMetadataEditor:
filename = item.get("filename", None)
basename, ext = os.path.splitext(filename)
- with open(basename + '.json', "w", encoding="utf8") as file:
+ metadata_path = basename + '.json'
+ with open(metadata_path, "w", encoding="utf8") as file:
json.dump(metadata, file, indent=4, ensure_ascii=False)
+ self.page.lister.update_file_entry(metadata_path)
def save_user_metadata(self, name, desc, notes):
user_metadata = self.get_user_metadata(name)
@@ -185,7 +187,8 @@ class UserMetadataEditor:
geninfo, items = images.read_info_from_image(image)
images.save_image_with_geninfo(image, geninfo, item["local_preview"])
-
+ self.page.lister.update_file_entry(item["local_preview"])
+ item['preview'] = self.page.find_preview(item["local_preview"])
return self.get_card_html(name), ''
def setup_ui(self, gallery):
@@ -200,6 +203,3 @@ class UserMetadataEditor:
inputs=[self.edit_name_input],
outputs=[]
)
-
-
-
diff --git a/modules/ui_loadsave.py b/modules/ui_loadsave.py
index 2555cdb6c..0cc1ab82a 100644
--- a/modules/ui_loadsave.py
+++ b/modules/ui_loadsave.py
@@ -104,6 +104,8 @@ class UiLoadsave:
apply_field(x, 'value', check_dropdown, getattr(x, 'init_field', None))
if type(x) == InputAccordion:
+ if hasattr(x, 'custom_script_source'):
+ x.accordion.custom_script_source = x.custom_script_source
if x.accordion.visible:
apply_field(x.accordion, 'visible')
apply_field(x, 'value')
diff --git a/modules/ui_settings.py b/modules/ui_settings.py
index d17ef1d95..087b91f3b 100644
--- a/modules/ui_settings.py
+++ b/modules/ui_settings.py
@@ -1,7 +1,8 @@
import gradio as gr
-from modules import ui_common, shared, script_callbacks, scripts, sd_models, sysinfo, timer
+from modules import ui_common, shared, script_callbacks, scripts, sd_models, sysinfo, timer, shared_items
from modules.call_queue import wrap_gradio_call
+from modules.options import options_section
from modules.shared import opts
from modules.ui_components import FormRow
from modules.ui_gradio_extensions import reload_javascript
@@ -108,6 +109,11 @@ class UiSettings:
shared.settings_components = self.component_dict
+ # we add this as late as possible so that scripts have already registered their callbacks
+ opts.data_labels.update(options_section(('callbacks', "Callbacks", "system"), {
+ **shared_items.callbacks_order_settings(),
+ }))
+
opts.reorder()
with gr.Blocks(analytics_enabled=False) as settings_interface:
diff --git a/modules/upscaler.py b/modules/upscaler.py
index 3aee69db8..4ffd428c6 100644
--- a/modules/upscaler.py
+++ b/modules/upscaler.py
@@ -20,7 +20,7 @@ class Upscaler:
filter = None
model = None
user_path = None
- scalers: []
+ scalers: list
tile = True
def __init__(self, create_dirs=False):
diff --git a/modules/util.py b/modules/util.py
index 8d1aea44f..777160f16 100644
--- a/modules/util.py
+++ b/modules/util.py
@@ -81,6 +81,17 @@ class MassFileListerCachedDir:
self.files = {x[0].lower(): x for x in files}
self.files_cased = {x[0]: x for x in files}
+ def update_entry(self, filename):
+ """Add a file to the cache"""
+ file_path = os.path.join(self.dirname, filename)
+ try:
+ stat = os.stat(file_path)
+ entry = (filename, stat.st_mtime, stat.st_ctime)
+ self.files[filename.lower()] = entry
+ self.files_cased[filename] = entry
+ except FileNotFoundError as e:
+ print(f'MassFileListerCachedDir.add_entry: "{file_path}" {e}')
+
class MassFileLister:
"""A class that provides a way to check for the existence and mtime/ctile of files without doing more than one stat call per file."""
@@ -136,3 +147,27 @@ class MassFileLister:
def reset(self):
"""Clear the cache of all directories."""
self.cached_dirs.clear()
+
+
+def topological_sort(dependencies):
+ """Accepts a dictionary mapping name to its dependencies, returns a list of names ordered according to dependencies.
+ Ignores errors relating to missing dependeencies or circular dependencies
+ """
+
+ visited = {}
+ result = []
+
+ def inner(name):
+ visited[name] = True
+
+ for dep in dependencies.get(name, []):
+ if dep in dependencies and dep not in visited:
+ inner(dep)
+
+ result.append(name)
+
+ for depname in dependencies:
+ if depname not in visited:
+ inner(depname)
+
+ return result
diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py
index e269682d0..c2574346d 100644
--- a/scripts/postprocessing_upscale.py
+++ b/scripts/postprocessing_upscale.py
@@ -1,10 +1,12 @@
+import re
+
from PIL import Image
import numpy as np
from modules import scripts_postprocessing, shared
import gradio as gr
-from modules.ui_components import FormRow, ToolButton
+from modules.ui_components import FormRow, ToolButton, InputAccordion
from modules.ui import switch_values_symbol
upscale_cache = {}
@@ -17,7 +19,14 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing):
def ui(self):
selected_tab = gr.Number(value=0, visible=False)
- with gr.Column():
+ with InputAccordion(True, label="Upscale", elem_id="extras_upscale") as upscale_enabled:
+ with FormRow():
+ extras_upscaler_1 = gr.Dropdown(label='Upscaler 1', elem_id="extras_upscaler_1", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name)
+
+ with FormRow():
+ extras_upscaler_2 = gr.Dropdown(label='Upscaler 2', elem_id="extras_upscaler_2", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name)
+ extras_upscaler_2_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Upscaler 2 visibility", value=0.0, elem_id="extras_upscaler_2_visibility")
+
with FormRow():
with gr.Tabs(elem_id="extras_resize_mode"):
with gr.TabItem('Scale by', elem_id="extras_scale_by_tab") as tab_scale_by:
@@ -32,18 +41,24 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing):
upscaling_res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="upscaling_res_switch_btn", tooltip="Switch width/height")
upscaling_crop = gr.Checkbox(label='Crop to fit', value=True, elem_id="extras_upscaling_crop")
- with FormRow():
- extras_upscaler_1 = gr.Dropdown(label='Upscaler 1', elem_id="extras_upscaler_1", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name)
+ def on_selected_upscale_method(upscale_method):
+ if not shared.opts.set_scale_by_when_changing_upscaler:
+ return gr.update()
- with FormRow():
- extras_upscaler_2 = gr.Dropdown(label='Upscaler 2', elem_id="extras_upscaler_2", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name)
- extras_upscaler_2_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Upscaler 2 visibility", value=0.0, elem_id="extras_upscaler_2_visibility")
+ match = re.search(r'(\d)[xX]|[xX](\d)', upscale_method)
+ if not match:
+ return gr.update()
+
+ return gr.update(value=int(match.group(1) or match.group(2)))
upscaling_res_switch_btn.click(lambda w, h: (h, w), inputs=[upscaling_resize_w, upscaling_resize_h], outputs=[upscaling_resize_w, upscaling_resize_h], show_progress=False)
tab_scale_by.select(fn=lambda: 0, inputs=[], outputs=[selected_tab])
tab_scale_to.select(fn=lambda: 1, inputs=[], outputs=[selected_tab])
+ extras_upscaler_1.change(on_selected_upscale_method, inputs=[extras_upscaler_1], outputs=[upscaling_resize], show_progress="hidden")
+
return {
+ "upscale_enabled": upscale_enabled,
"upscale_mode": selected_tab,
"upscale_by": upscaling_resize,
"upscale_to_width": upscaling_resize_w,
@@ -81,7 +96,7 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing):
return image
- def process_firstpass(self, pp: scripts_postprocessing.PostprocessedImage, upscale_mode=1, upscale_by=2.0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0):
+ def process_firstpass(self, pp: scripts_postprocessing.PostprocessedImage, upscale_enabled=True, upscale_mode=1, upscale_by=2.0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0):
if upscale_mode == 1:
pp.shared.target_width = upscale_to_width
pp.shared.target_height = upscale_to_height
@@ -89,7 +104,10 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing):
pp.shared.target_width = int(pp.image.width * upscale_by)
pp.shared.target_height = int(pp.image.height * upscale_by)
- def process(self, pp: scripts_postprocessing.PostprocessedImage, upscale_mode=1, upscale_by=2.0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0):
+ def process(self, pp: scripts_postprocessing.PostprocessedImage, upscale_enabled=True, upscale_mode=1, upscale_by=2.0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0):
+ if not upscale_enabled:
+ return
+
if upscaler_1_name == "None":
upscaler_1_name = None
diff --git a/style.css b/style.css
index 004038f89..f6a89b8f9 100644
--- a/style.css
+++ b/style.css
@@ -1,6 +1,6 @@
/* temporary fix to load default gradio font in frontend instead of backend */
-@import url('webui-assets/css/sourcesanspro.css');
+@import url('/webui-assets/css/sourcesanspro.css');
/* temporary fix to hide gradio crop tool until it's fixed https://github.com/gradio-app/gradio/issues/3810 */
@@ -528,6 +528,10 @@ table.popup-table .link{
opacity: 0.75;
}
+.settings-comment .info ol{
+ margin: 0.4em 0 0.8em 1em;
+}
+
#sysinfo_download a.sysinfo_big_link{
font-size: 24pt;
}
@@ -1205,12 +1209,24 @@ body.resizing .resize-handle {
overflow: hidden;
}
-.extra-network-pane .extra-network-pane-content {
+.extra-network-pane .extra-network-pane-content-dirs {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.extra-network-pane .extra-network-pane-content-tree {
display: flex;
flex: 1;
overflow: hidden;
}
+.extra-network-dirs-hidden .extra-network-dirs{ display: none; }
+.extra-network-dirs-hidden .extra-network-tree{ display: none; }
+.extra-network-dirs-hidden .resize-handle { display: none; }
+.extra-network-dirs-hidden .resize-handle-row { display: flex !important; }
+
.extra-network-pane .extra-network-tree {
flex: 1;
font-size: 1rem;
@@ -1260,7 +1276,7 @@ body.resizing .resize-handle {
.extra-network-control {
position: relative;
- display: grid;
+ display: flex;
width: 100%;
padding: 0 !important;
margin-top: 0 !important;
@@ -1277,6 +1293,12 @@ body.resizing .resize-handle {
align-items: start;
}
+.extra-network-control small{
+ color: var(--input-placeholder-color);
+ line-height: 2.2rem;
+ margin: 0 0.5rem 0 0.75rem;
+}
+
.extra-network-tree .tree-list--tree {}
/* Remove auto indentation from tree. Will be overridden later. */
@@ -1424,6 +1446,12 @@ body.resizing .resize-handle {
line-height: 1rem;
}
+
+.extra-network-control .extra-network-control--search .extra-network-control--search-text::placeholder {
+ color: var(--input-placeholder-color);
+}
+
+
/* clear button (x on right side) styling */
.extra-network-control .extra-network-control--search .extra-network-control--search-text::-webkit-search-cancel-button {
-webkit-appearance: none;
@@ -1456,19 +1484,19 @@ body.resizing .resize-handle {
background-color: var(--input-placeholder-color);
}
-.extra-network-control .extra-network-control--sort[data-sortmode="path"] .extra-network-control--sort-icon {
+.extra-network-control .extra-network-control--sort[data-sortkey="default"] .extra-network-control--sort-icon {
mask-image: url('data:image/svg+xml,');
}
-.extra-network-control .extra-network-control--sort[data-sortmode="name"] .extra-network-control--sort-icon {
+.extra-network-control .extra-network-control--sort[data-sortkey="name"] .extra-network-control--sort-icon {
mask-image: url('data:image/svg+xml,');
}
-.extra-network-control .extra-network-control--sort[data-sortmode="date_created"] .extra-network-control--sort-icon {
+.extra-network-control .extra-network-control--sort[data-sortkey="date_created"] .extra-network-control--sort-icon {
mask-image: url('data:image/svg+xml,');
}
-.extra-network-control .extra-network-control--sort[data-sortmode="date_modified"] .extra-network-control--sort-icon {
+.extra-network-control .extra-network-control--sort[data-sortkey="date_modified"] .extra-network-control--sort-icon {
mask-image: url('data:image/svg+xml,');
}
@@ -1518,13 +1546,18 @@ body.resizing .resize-handle {
}
.extra-network-control .extra-network-control--enabled {
- background-color: rgba(0, 0, 0, 0.15);
+ background-color: rgba(0, 0, 0, 0.1);
+ border-radius: 0.25rem;
}
.dark .extra-network-control .extra-network-control--enabled {
background-color: rgba(255, 255, 255, 0.15);
}
+.extra-network-control .extra-network-control--enabled .extra-network-control--icon{
+ background-color: var(--button-secondary-text-color);
+}
+
/* ==== REFRESH ICON ACTIONS ==== */
.extra-network-control .extra-network-control--refresh {
padding: 0.25rem;
diff --git a/webui.sh b/webui.sh
index 361255f69..b348c387e 100755
--- a/webui.sh
+++ b/webui.sh
@@ -130,12 +130,18 @@ case "$gpu_info" in
if [[ -z "${TORCH_COMMAND}" ]]
then
pyv="$(${python_cmd} -c 'import sys; print(".".join(map(str, sys.version_info[0:2])))')"
- if [[ $(bc <<< "$pyv <= 3.10") -eq 1 ]]
+ # Using an old nightly compiled against rocm 5.2 for Navi1, see https://github.com/pytorch/pytorch/issues/106728#issuecomment-1749511711
+ if [[ $pyv == "3.8" ]]
then
- # Navi users will still use torch 1.13 because 2.0 does not seem to work.
- export TORCH_COMMAND="pip install --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm5.6"
+ export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp38-cp38-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp38-cp38-linux_x86_64.whl"
+ elif [[ $pyv == "3.9" ]]
+ then
+ export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp39-cp39-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp39-cp39-linux_x86_64.whl"
+ elif [[ $pyv == "3.10" ]]
+ then
+ export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp310-cp310-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp310-cp310-linux_x86_64.whl"
else
- printf "\e[1m\e[31mERROR: RX 5000 series GPUs must be using at max python 3.10, aborting...\e[0m"
+ printf "\e[1m\e[31mERROR: RX 5000 series GPUs python version must be between 3.8 and 3.10, aborting...\e[0m"
exit 1
fi
fi
@@ -143,7 +149,7 @@ case "$gpu_info" in
*"Navi 2"*) export HSA_OVERRIDE_GFX_VERSION=10.3.0
;;
*"Navi 3"*) [[ -z "${TORCH_COMMAND}" ]] && \
- export TORCH_COMMAND="pip install --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm5.7"
+ export TORCH_COMMAND="pip install torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm5.7"
;;
*"Renoir"*) export HSA_OVERRIDE_GFX_VERSION=9.0.0
printf "\n%s\n" "${delimiter}"