diff --git a/.eslintrc.json b/.eslintrc.json
index e0fe61fba..fdda21aa8 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -50,6 +50,7 @@
"optionsChangedCallbacks": "readonly",
"onUiLoaded": "readonly",
"onUiUpdate": "readonly",
+ "onUiTabChange": "readonly",
"uiCurrentTab": "writable",
"uiElementIsVisible": "readonly",
"uiElementInSight": "readonly",
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ba453c0d8..fb025be50 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,7 @@
- Styles apply wildcards to params
- Make metadata in full screen viewer optional
- Add VAE civitai scan metadata/preview
+ - More efficient in-browser callbacks
- **IPEX**
- update to *IPEX 2.1.20* on Linux
requires removing the venv folder to update properly
diff --git a/javascript/contextMenus.js b/javascript/contextMenus.js
index 9ded4a006..80b685106 100644
--- a/javascript/contextMenus.js
+++ b/javascript/contextMenus.js
@@ -113,7 +113,5 @@ async function initContextMenu() {
appendContextMenuOption(id, 'nVidia overlay', initNVML);
}
}
+ addContextMenuEventListener();
}
-
-onUiLoaded(initContextMenu);
-onAfterUiUpdate(() => addContextMenuEventListener());
diff --git a/javascript/control.js b/javascript/control.js
index 36c6affdb..e6e352eb6 100644
--- a/javascript/control.js
+++ b/javascript/control.js
@@ -42,5 +42,3 @@ async function setupControlUI() {
log('initControlUI');
}
-
-onUiLoaded(setupControlUI);
diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js
index 35918b44e..d47489485 100644
--- a/javascript/extraNetworks.js
+++ b/javascript/extraNetworks.js
@@ -481,5 +481,3 @@ async function setupExtraNetworks() {
registerPrompt('control', 'control_neg_prompt');
log('initExtraNetworks');
}
-
-onUiLoaded(setupExtraNetworks);
diff --git a/javascript/gallery.js b/javascript/gallery.js
index 96d0773a8..e8f1b5310 100644
--- a/javascript/gallery.js
+++ b/javascript/gallery.js
@@ -383,8 +383,8 @@ async function galleryHidden() {
if (pruneImagesTimer) clearInterval(pruneImagesTimer);
}
-async function galleryObserve() { // triggered on gradio change to monitor when ui gets sufficiently constructed
- log('initBrowser');
+async function initGallery() { // triggered on gradio change to monitor when ui gets sufficiently constructed
+ log('initGallery');
el.folders = gradioApp().getElementById('tab-gallery-folders');
el.files = gradioApp().getElementById('tab-gallery-files');
el.status = gradioApp().getElementById('tab-gallery-status');
@@ -403,4 +403,3 @@ async function galleryObserve() { // triggered on gradio change to monitor when
customElements.define('gallery-folder', GalleryFolder);
customElements.define('gallery-file', GalleryFile);
-onUiLoaded(galleryObserve);
diff --git a/javascript/generationParams.js b/javascript/generationParams.js
index b9d38a514..c3bdbb6e8 100644
--- a/javascript/generationParams.js
+++ b/javascript/generationParams.js
@@ -16,10 +16,8 @@ function attachGalleryListeners(tab_name) {
let txt2img_gallery;
let img2img_gallery;
let modal;
-let generationParamsInitialized = false;
async function initiGenerationParams() {
- if (generationParamsInitialized) return;
if (!modal) modal = gradioApp().getElementById('lightboxModal');
if (!modal) return;
@@ -33,10 +31,6 @@ async function initiGenerationParams() {
if (!txt2img_gallery) txt2img_gallery = attachGalleryListeners('txt2img');
if (!img2img_gallery) img2img_gallery = attachGalleryListeners('img2img');
- if (txt2img_gallery && img2img_gallery) generationParamsInitialized = true;
- else return;
modalObserver.observe(modal, { attributes: true, attributeFilter: ['style'] });
log('initGenerationParams');
}
-
-onAfterUiUpdate(initiGenerationParams);
diff --git a/javascript/imageParams.js b/javascript/imageParams.js
index e31aaa667..9ac42ace2 100644
--- a/javascript/imageParams.js
+++ b/javascript/imageParams.js
@@ -1,8 +1,4 @@
-let dragDropInitialized = false;
-
async function initDragDrop() {
- if (dragDropInitialized) return;
- dragDropInitialized = true;
log('initDragDrop');
window.addEventListener('drop', (e) => {
const target = e.composedPath()[0];
@@ -26,5 +22,3 @@ async function initDragDrop() {
}
});
}
-
-onAfterUiUpdate(initDragDrop);
diff --git a/javascript/imageViewer.js b/javascript/imageViewer.js
index 1e3536716..8d85bebcd 100644
--- a/javascript/imageViewer.js
+++ b/javascript/imageViewer.js
@@ -159,8 +159,6 @@ function modalResetInstance(event) {
previewInstance = panzoom(modalImage, { zoomSpeed: 0.05, minZoom: 0.1, maxZoom: 5.0, filterKey: (/* e, dx, dy, dz */) => true });
}
-let imageViewerInitialized = false;
-
function galleryClickEventHandler(event) {
if (event.button !== 0) return;
if (event.target.nodeName === 'IMG' && !event.target.parentNode.classList.contains('thumbnail-item')) {
@@ -183,9 +181,6 @@ async function initImageViewer() {
}
}
}
- if (imageViewerInitialized) return;
- imageViewerInitialized = true;
-
// main elements
const modal = document.createElement('div');
modal.id = 'lightboxModal';
@@ -286,5 +281,3 @@ async function initImageViewer() {
gradioApp().appendChild(modal);
log('initImageViewer');
}
-
-onAfterUiUpdate(initImageViewer);
diff --git a/javascript/indexdb.js b/javascript/indexdb.js
index 789a15b46..dbedd86c4 100644
--- a/javascript/indexdb.js
+++ b/javascript/indexdb.js
@@ -74,10 +74,3 @@ async function put(record) {
request.onerror = (evt) => reject(evt);
});
}
-
-window.idbAdd = add;
-window.idbDel = del;
-window.idbGet = get;
-window.idbPut = put;
-
-onUiLoaded(initIndexDB);
diff --git a/javascript/logMonitor.js b/javascript/logMonitor.js
index 0f5123164..ebdeb9137 100644
--- a/javascript/logMonitor.js
+++ b/javascript/logMonitor.js
@@ -59,13 +59,9 @@ async function logMonitor() {
}
}
-let logMonitorInitialized = false;
-
async function initLogMonitor() {
- if (logMonitorInitialized) return;
const el = document.getElementsByTagName('footer')[0];
if (!el) return;
- logMonitorInitialized = true;
el.classList.add('log-monitor');
el.innerHTML = `
@@ -89,5 +85,3 @@ async function initLogMonitor() {
logMonitor();
log('initLogMonitor');
}
-
-onAfterUiUpdate(initLogMonitor);
diff --git a/javascript/notification.js b/javascript/notification.js
index e78452b04..0c8879885 100644
--- a/javascript/notification.js
+++ b/javascript/notification.js
@@ -3,7 +3,7 @@
let lastHeadImg = null;
let notificationButton = null;
-function initNotifications() {
+async function initNotifications() {
if (!notificationButton) {
notificationButton = gradioApp().getElementById('request_notifications');
if (notificationButton) notificationButton.addEventListener('click', (evt) => Notification.requestPermission(), true);
@@ -28,5 +28,3 @@ function initNotifications() {
};
log('sendNotification');
}
-
-onAfterUiUpdate(initNotifications);
diff --git a/javascript/promptChecker.js b/javascript/promptChecker.js
index 284482205..a02119e97 100644
--- a/javascript/promptChecker.js
+++ b/javascript/promptChecker.js
@@ -3,8 +3,6 @@
// Counts open and closed brackets (round, square, curly) in the prompt and negative prompt text boxes in the txt2img and img2img tabs.
// If there's a mismatch, the keyword counter turns red and if you hover on it, a tooltip tells you what's wrong.
-let promptCheckerInitialized = false;
-
function checkBrackets(textArea, counterElt) {
const counts = {};
const errors = [];
@@ -25,17 +23,15 @@ function setupBracketChecking(idPrompt, idCounter) {
const textarea = gradioApp().querySelector(`#${idPrompt} > label > textarea`);
const counter = gradioApp().getElementById(idCounter);
if (!textarea || !counter) return;
- if (!promptCheckerInitialized) log('initPromptChecker');
- promptCheckerInitialized = true;
textarea.addEventListener('input', () => checkBrackets(textarea, counter));
}
-onAfterUiUpdate(() => {
- if (promptCheckerInitialized) return;
+async function initPromptChecker() {
+ log('initPromptChecker');
setupBracketChecking('txt2img_prompt', 'txt2img_token_counter');
setupBracketChecking('txt2img_neg_prompt', 'txt2img_negative_token_counter');
setupBracketChecking('img2img_prompt', 'img2img_token_counter');
setupBracketChecking('img2img_neg_prompt', 'img2img_negative_token_counter');
setupBracketChecking('control_prompt', 'control_token_counter');
setupBracketChecking('control_neg_prompt', 'control_negative_token_counter');
-});
+}
diff --git a/javascript/script.js b/javascript/script.js
index 3711e5abb..267c586ac 100644
--- a/javascript/script.js
+++ b/javascript/script.js
@@ -10,6 +10,10 @@ const debug = (...msg) => {
console.debug(ts, ...msg); // eslint-disable-line no-console
};
+async function sleep(ms) {
+ return new Promise((resolve) => setTimeout(resolve, ms)); // eslint-disable-line no-promise-executor-return
+}
+
function gradioApp() {
const elems = document.getElementsByTagName('gradio-app');
const elem = elems.length === 0 ? document : elems[0];
@@ -30,6 +34,7 @@ const get_uiCurrentTab = getUICurrentTab;
const uiAfterUpdateCallbacks = [];
const uiUpdateCallbacks = [];
const uiLoadedCallbacks = [];
+const uiReadyCallbacks = [];
const uiTabChangeCallbacks = [];
const optionsChangedCallbacks = [];
let uiCurrentTab = null;
@@ -47,6 +52,10 @@ function onUiLoaded(callback) {
uiLoadedCallbacks.push(callback);
}
+function onUiReady(callback) {
+ uiReadyCallbacks.push(callback);
+}
+
function onUiTabChange(callback) {
uiTabChangeCallbacks.push(callback);
}
@@ -72,23 +81,34 @@ function scheduleAfterUiUpdateCallbacks() {
}
let executedOnLoaded = false;
+const ignoreElements = ['logMonitorData', 'logWarnings', 'logErrors', 'tooltip-container'];
+const ignoreClasses = ['wrap'];
+
+async function mutationCallback(mutations) {
+ let validMutations = mutations;
+ validMutations = validMutations.filter((m) => m.target.nodeName !== 'LABEL');
+ validMutations = validMutations.filter((m) => ignoreElements.indexOf(m.target.id) === -1);
+ validMutations = validMutations.filter((m) => m.target.id !== 'logWarnings' && m.target.id !== 'logErrors');
+ validMutations = validMutations.filter((m) => !m.target.classList?.contains('wrap'));
+ if (validMutations.length < 1) return;
+
+ if (!executedOnLoaded && gradioApp().getElementById('txt2img_prompt')) { // execute once
+ executedOnLoaded = true;
+ executeCallbacks(uiLoadedCallbacks);
+ }
+ if (executedOnLoaded) { // execute on each mutation
+ executeCallbacks(uiUpdateCallbacks, mutations);
+ scheduleAfterUiUpdateCallbacks();
+ }
+ const newTab = getUICurrentTab();
+ if (newTab && (newTab !== uiCurrentTab)) {
+ uiCurrentTab = newTab;
+ executeCallbacks(uiTabChangeCallbacks);
+ }
+}
document.addEventListener('DOMContentLoaded', () => {
- const mutationObserver = new MutationObserver((m) => {
- if (!executedOnLoaded && gradioApp().getElementById('txt2img_prompt')) {
- executedOnLoaded = true;
- executeCallbacks(uiLoadedCallbacks);
- }
- if (executedOnLoaded) {
- executeCallbacks(uiUpdateCallbacks, m);
- scheduleAfterUiUpdateCallbacks();
- }
- const newTab = getUICurrentTab();
- if (newTab && (newTab !== uiCurrentTab)) {
- uiCurrentTab = newTab;
- executeCallbacks(uiTabChangeCallbacks);
- }
- });
+ const mutationObserver = new MutationObserver(mutationCallback);
mutationObserver.observe(gradioApp(), { childList: true, subtree: true });
});
diff --git a/javascript/setHints.js b/javascript/setHints.js
index cbf7fa8c8..21c2f6407 100644
--- a/javascript/setHints.js
+++ b/javascript/setHints.js
@@ -107,11 +107,12 @@ async function setHints() {
const t1 = performance.now();
log('setHints', { type: localeData.type, elements: elements.length, localized, hints, data: localeData.data.length, time: t1 - t0 });
// sortUIElements();
- removeSplash();
// validateHints(elements, localeData.data);
}
+/*
onAfterUiUpdate(async () => {
if (localeData.timeout) clearTimeout(localeData.timeout);
localeData.timeout = setTimeout(setHints, 250);
});
+*/
diff --git a/javascript/settings.js b/javascript/settings.js
index b20083da9..c12ab2085 100644
--- a/javascript/settings.js
+++ b/javascript/settings.js
@@ -195,6 +195,3 @@ async function initSettings() {
});
log('initSettings');
}
-
-onUiLoaded(initSettings);
-onUiLoaded(initModels);
diff --git a/javascript/startup.js b/javascript/startup.js
new file mode 100644
index 000000000..76c573eab
--- /dev/null
+++ b/javascript/startup.js
@@ -0,0 +1,44 @@
+/* eslint-disable no-undef */
+
+async function initStartup() {
+ log('initStartup');
+
+ // all items here are non-blocking async calls
+ initModels();
+ getUIDefaults();
+ initiGenerationParams();
+ initNotifications();
+ initPromptChecker();
+ initLogMonitor();
+ initContextMenu();
+ initDragDrop();
+ initSettings();
+ initImageViewer();
+ initGallery();
+ setupControlUI();
+ setupExtraNetworks();
+
+ // reconnect server session
+ await reconnectUI();
+
+ // make sure all of the ui is ready and options are loaded
+ while (Object.keys(window.opts).length === 0) await sleep(50);
+ executeCallbacks(uiReadyCallbacks);
+
+ // optinally wait for modern ui
+ if (window.waitForUiUxReady) await window.waitForUiUxReady();
+ removeSplash();
+
+ // post startup tasks that may take longer but are not critical
+ setHints();
+ initIndexDB();
+}
+
+onUiLoaded(initStartup);
+onUiReady(() => log('uiReady'));
+
+// onAfterUiUpdate(() => log('evt onAfterUiUpdate'));
+// onUiLoaded(() => log('evt onUiLoaded'));
+// onOptionsChanged(() => log('evt onOptionsChanged'));
+// onUiTabChange(() => log('evt onUiTabChange'));
+// onUiUpdate(() => log('evt onUiUpdate'));
diff --git a/javascript/ui.js b/javascript/ui.js
index 008b06e12..89e4ce51c 100644
--- a/javascript/ui.js
+++ b/javascript/ui.js
@@ -492,11 +492,9 @@ async function browseFolder() {
async function reconnectUI() {
const gallery = gradioApp().getElementById('txt2img_gallery');
- if (!gallery) return;
const task_id = localStorage.getItem('task');
const api_logo = Array.from(gradioApp().querySelectorAll('img')).filter((el) => el?.src?.endsWith('api-logo.svg'));
if (api_logo.length > 0) api_logo[0].remove();
- clearInterval(start_check); // eslint-disable-line no-use-before-define
if (task_id) {
debug('task check:', task_id);
requestProgress(task_id, null, gallery, null, null, true);
@@ -524,5 +522,3 @@ async function reconnectUI() {
sd_model_observer.observe(sd_model, { attributes: true, childList: true, subtree: true });
log('reconnectUI');
}
-
-const start_check = setInterval(reconnectUI, 100);
diff --git a/javascript/uiConfig.js b/javascript/uiConfig.js
index 9a0d8ea69..79356d4ac 100644
--- a/javascript/uiConfig.js
+++ b/javascript/uiConfig.js
@@ -10,7 +10,7 @@ function uiOpenSubmenus() {
return states;
}
-function getUIDefaults() {
+async function getUIDefaults() {
const btn = gradioApp().getElementById('ui_defaults_view');
if (!btn) return;
const intersectionObserver = new IntersectionObserver((entries) => {
@@ -19,5 +19,3 @@ function getUIDefaults() {
});
intersectionObserver.observe(btn); // monitor visibility of tab
}
-
-onUiLoaded(getUIDefaults);
diff --git a/modules/control/run.py b/modules/control/run.py
index db8e6d062..a5279d789 100644
--- a/modules/control/run.py
+++ b/modules/control/run.py
@@ -530,6 +530,7 @@ def control_run(units: List[unit.Unit] = [], inputs: List[Image.Image] = [], ini
# pipeline
output = None
+ script_run = False
if pipe is not None: # run new pipeline
pipe.restore_pipeline = restore_pipeline
debug(f'Control exec pipeline: task={sd_models.get_diffusers_task(pipe)} class={pipe.__class__}')
@@ -549,6 +550,8 @@ def control_run(units: List[unit.Unit] = [], inputs: List[Image.Image] = [], ini
processed = p.scripts.run(p, *p.script_args)
if processed is None:
processed: processing.Processed = processing.process_images(p) # run actual pipeline
+ else:
+ script_run = True
output = processed.images if processed is not None else None
# output = pipe(**vars(p)).images # alternative direct pipe exec call
else: # blend all processed images and return
@@ -570,7 +573,7 @@ def control_run(units: List[unit.Unit] = [], inputs: List[Image.Image] = [], ini
output_image = images.resize_image(resize_mode_after, output_image, width_after, height_after, resize_name_after)
output_images.append(output_image)
- if shared.opts.include_mask:
+ if shared.opts.include_mask and not script_run:
if processed_image is not None and isinstance(processed_image, Image.Image):
output_images.append(processed_image)
diff --git a/modules/ui_sections.py b/modules/ui_sections.py
index 2f18c545e..578e6ffdd 100644
--- a/modules/ui_sections.py
+++ b/modules/ui_sections.py
@@ -280,7 +280,8 @@ def create_resize_inputs(tab, images, accordion=True, latent=False):
res_switch_btn = ToolButton(value=ui_symbols.switch, elem_id=f"{tab}_res_switch_btn")
res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False)
detect_image_size_btn = ToolButton(value=ui_symbols.detect, elem_id=f"{tab}_detect_image_size_btn")
- detect_image_size_btn.click(fn=lambda w, h, _: (w or gr.update(), h or gr.update()), _js=f'currentImageResolution{tab}', inputs=[dummy_component, dummy_component, dummy_component], outputs=[width, height], show_progress=False)
+ el = tab.split('_')[0]
+ detect_image_size_btn.click(fn=lambda w, h, _: (w or gr.update(), h or gr.update()), _js=f'currentImageResolution{el}', inputs=[dummy_component, dummy_component, dummy_component], outputs=[width, height], show_progress=False)
with gr.Tab(label="Scale") as tab_scale_by:
scale_by = gr.Slider(minimum=0.05, maximum=8.0, step=0.05, label="Scale", value=1.0, elem_id=f"{tab}_scale")
for component in images:
diff --git a/scripts/differential_diffusion.py b/scripts/differential_diffusion.py
index 2d49f3ddb..618d6c4c1 100644
--- a/scripts/differential_diffusion.py
+++ b/scripts/differential_diffusion.py
@@ -1981,6 +1981,7 @@ class Script(scripts.Script):
# run pipeline
processed: processing.Processed = processing.process_images(p) # runs processing using main loop
if shared.opts.include_mask:
+ p.image_mask = image_mask
if image_mask is not None and isinstance(image_mask, Image.Image):
processed.images.append(image_mask)