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)