From b330c34b29c26d0e554436eff4eb59ef034df838 Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Fri, 9 Dec 2022 19:34:41 -0800 Subject: [PATCH 1/7] Fix auto-scroll setting management After thinking about it, the auto-save toggle is meant for the *Editor* fields listed behind the Configure button. The auto-scroll toggle is not part of the Editor, and is more akin to a system setting, although it's placed in the main UI for convenience reasons related to its nature. As such, and especially considering it's a plugin, I lean towards decoupling auto-scroll from the auto-save settings, and just storing it independently. --- ui/plugins/ui/Autoscroll.plugin.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/ui/plugins/ui/Autoscroll.plugin.js b/ui/plugins/ui/Autoscroll.plugin.js index 2b220f67..72905c6d 100644 --- a/ui/plugins/ui/Autoscroll.plugin.js +++ b/ui/plugins/ui/Autoscroll.plugin.js @@ -16,18 +16,12 @@ clearAllPreviewsBtn.parentNode.insertBefore(autoScrollControl, clearAllPreviewsBtn.nextSibling) prettifyInputs(document); let autoScroll = document.querySelector("#auto_scroll") - - /** - * the use of initSettings() in the autoscroll plugin seems to be breaking the models dropdown and the save-to-disk folder field - * in the settings tab. They're both blank, because they're being re-initialized. Their earlier values came from the API call, - * but those values aren't stored in localStorage, since they aren't user-specified. - * So when initSettings() is called a second time, it overwrites the values with an empty string. - * - * We could either rework how new components can register themselves to be auto-saved, without having to call initSettings() again. - * Or we could move the autoscroll code into the main code, and include it in the list of fields in auto-save.js - */ - // SETTINGS_IDS_LIST.push("auto_scroll") - // initSettings() + + // save/restore the toggle state + autoScroll.addEventListener('click', (e) => { + localStorage.setItem('auto_scroll', autoScroll.checked) + }) + autoScroll.checked = localStorage.getItem('auto_scroll') == "true" // observe for changes in the preview pane var observer = new MutationObserver(function (mutations) { From 83e5410945ca2786145edde978ffe83459cc49c6 Mon Sep 17 00:00:00 2001 From: Marc-Andre Ferland Date: Sat, 10 Dec 2022 00:50:37 -0500 Subject: [PATCH 2/7] Fix (typeof stepUpdate !== 'object') not completing the task on stop. --- ui/media/js/main.js | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index d16b8b88..4e407511 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -605,29 +605,29 @@ function onTaskErrorHandler(task, reqBody, instance, reason) { } function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { - if (typeof stepUpdate !== 'object') { - return - } - if (stepUpdate.status !== 'succeeded') { - const outputMsg = task['outputMsg'] - let msg = '' - if ('detail' in stepUpdate && typeof stepUpdate.detail === 'string' && stepUpdate.detail.length > 0) { - msg = stepUpdate.detail - if (msg.toLowerCase().includes('out of memory')) { - msg += `

- Suggestions: -
- 1. If you have set an initial image, please try reducing its dimension to ${MAX_INIT_IMAGE_DIMENSION}x${MAX_INIT_IMAGE_DIMENSION} or smaller.
- 2. Try disabling the 'Turbo mode' under 'Advanced Settings'.
- 3. Try generating a smaller image.
` - } + if (typeof stepUpdate === 'object') { + if (stepUpdate.status === 'succeeded') { + showImages(reqBody, stepUpdate, outputContainer, false) } else { - msg = `Unexpected Read Error:
StepUpdate: ${JSON.stringify(stepUpdate, undefined, 4)}
` + task.isProcessing = false + const outputMsg = task['outputMsg'] + let msg = '' + if ('detail' in stepUpdate && typeof stepUpdate.detail === 'string' && stepUpdate.detail.length > 0) { + msg = stepUpdate.detail + if (msg.toLowerCase().includes('out of memory')) { + msg += `

+ Suggestions: +
+ 1. If you have set an initial image, please try reducing its dimension to ${MAX_INIT_IMAGE_DIMENSION}x${MAX_INIT_IMAGE_DIMENSION} or smaller.
+ 2. Try disabling the 'Turbo mode' under 'Advanced Settings'.
+ 3. Try generating a smaller image.
` + } + } else { + msg = `Unexpected Read Error:
StepUpdate: ${JSON.stringify(stepUpdate, undefined, 4)}
` + } + logError(msg, stepUpdate, outputMsg) } - logError(msg, stepUpdate, outputMsg) - return false } - showImages(reqBody, stepUpdate, outputContainer, false) if (task.isProcessing && task.batchesDone < task.batchCount) { task['taskStatusLabel'].innerText = "Pending" task['taskStatusLabel'].classList.add('waitingTaskLabel') @@ -638,8 +638,6 @@ function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { return } - setStatus('request', 'done', 'success') - task.isProcessing = false task['stopTask'].innerHTML = ' Remove' task['taskStatusLabel'].style.display = 'none' From 099fde26524f153f646099e654d31adc28338017 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Sat, 10 Dec 2022 17:17:37 +0100 Subject: [PATCH 3/7] show initimg in task list --- CHANGES.md | 1 + ui/media/css/main.css | 10 ++++++++++ ui/media/js/main.js | 36 +++++++++++++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 6fb7d025..aa7947a5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -27,6 +27,7 @@ - Support loading models in the safetensor format, for improved safety ### Detailed changelog +* 2.4.19 - 10 Dec 2022 - Show init img in task list * 2.4.19 - 7 Dec 2022 - Use pre-trained hypernetworks while generating images. Thanks @C0bra5 * 2.4.19 - 6 Dec 2022 - Allow processing new tasks first. Thanks @madrang * 2.4.19 - 6 Dec 2022 - Allow reordering the task queue (by dragging tasks). Thanks @madrang diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 6390a229..2c9d0054 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -945,6 +945,7 @@ input::file-selector-button { height: 16px; position: relative; transition: 0.25s 1s border, 0.25s 1s height; + clear: both; } .progress-bar > div { background: var(--accent-color); @@ -1096,6 +1097,15 @@ button:active { left: 1px; } +div.task-initimg > img { + margin-right: 6px; + display: block; +} +div.task-fs-initimage { + display: none; + position: absolute; +} + button#save-system-settings-btn { padding: 4pt 8pt; } diff --git a/ui/media/js/main.js b/ui/media/js/main.js index d16b8b88..325d035a 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -757,8 +757,37 @@ function onTaskStart(task) { previewTools.style.display = 'block' } +/* Hover effect for the init image in the task list */ +function createInitImageHover(taskEntry) { + var $tooltip = $( taskEntry.querySelector('.task-fs-initimage') ) + $( taskEntry.querySelector('div.task-initimg > img') ).on('mouseenter', function() { + var img = this, + $img = $(img), + offset = $img.offset(); + + $tooltip + .css({ + 'top': offset.top, + 'left': offset.left, + 'z-index': 99999, + 'display': 'block' + }) + .append($img.clone().css({width:"", height:""})); + }) + $tooltip.on('mouseleave', function() { + $tooltip.empty().addClass('hidden'); + }); +} + function createTask(task) { - let taskConfig = `Seed: ${task.seed}, Sampler: ${task.reqBody.sampler}, Inference Steps: ${task.reqBody.num_inference_steps}, Guidance Scale: ${task.reqBody.guidance_scale}, Model: ${task.reqBody.use_stable_diffusion_model}` + let taskConfig = '' + + if (task.reqBody.init_image !== undefined) { + let h = 80 + let w = task.reqBody.width * h / task.reqBody.height >>0 + taskConfig += `
` + } + taskConfig += `Seed: ${task.seed}, Sampler: ${task.reqBody.sampler}, Inference Steps: ${task.reqBody.num_inference_steps}, Guidance Scale: ${task.reqBody.guidance_scale}, Model: ${task.reqBody.use_stable_diffusion_model}` if (task.reqBody.use_vae_model.trim() !== '') { taskConfig += `, VAE: ${task.reqBody.use_vae_model}` } @@ -797,6 +826,11 @@ function createTask(task) { createCollapsibles(taskEntry) + + if (task.reqBody.init_image !== undefined) { + createInitImageHover(taskEntry) + } + task['taskStatusLabel'] = taskEntry.querySelector('.taskStatusLabel') task['outputContainer'] = taskEntry.querySelector('.img-preview') task['outputMsg'] = taskEntry.querySelector('.outputMsg') From 7b2be12587ba95d838c006078e5d71cb8dd0f1a4 Mon Sep 17 00:00:00 2001 From: Marc-Andre Ferland Date: Sat, 10 Dec 2022 18:26:48 -0500 Subject: [PATCH 4/7] Check if window is defined. Not all JS execution environments have it. --- ui/media/js/engine.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/media/js/engine.js b/ui/media/js/engine.js index dd34ddb1..32b609d0 100644 --- a/ui/media/js/engine.js +++ b/ui/media/js/engine.js @@ -1081,7 +1081,7 @@ function getServerCapacity() { let activeDevicesCount = Object.keys(serverState?.devices?.active || {}).length - if (window.document.visibilityState === 'hidden') { + if (typeof window === "object" && window.document.visibilityState === 'hidden') { activeDevicesCount = 1 + activeDevicesCount } return activeDevicesCount From b5329ee93d0366623333a8751abeb7e8b4b803fa Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Sat, 10 Dec 2022 17:45:14 -0800 Subject: [PATCH 5/7] Fixing a typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Yeah, I know... What can I say? I have my OCD too. 👀 --- ui/media/js/engine.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/media/js/engine.js b/ui/media/js/engine.js index dd34ddb1..8989d9a1 100644 --- a/ui/media/js/engine.js +++ b/ui/media/js/engine.js @@ -248,7 +248,7 @@ setServerStatus('busy', 'rendering..') break default: // Unavailable - console.error('Ping received an unexpedted server status. Status: %s', serverState.status) + console.error('Ping received an unexpected server status. Status: %s', serverState.status) setServerStatus('error', serverState.status.toLowerCase()) break } @@ -277,7 +277,7 @@ case ServerStates.online: return true default: - console.warn('Unexpedted server status. Server could be unavailable... Status: %s', serverState.status) + console.warn('Unexpected server status. Server could be unavailable... Status: %s', serverState.status) return false } } From 5b7cd11de89ead0bc72d58c8d20ff5af5457e752 Mon Sep 17 00:00:00 2001 From: Marc-Andre Ferland Date: Sun, 11 Dec 2022 00:52:52 -0500 Subject: [PATCH 6/7] Added support for Async events (#643) * Added support for async events callbacks * Don't fire IDLE event if the first callback hasn't completed execution. --- ui/media/js/engine.js | 23 ++++++++++++++--------- ui/media/js/main.js | 16 +++++++++------- ui/media/js/utils.js | 14 ++++++++++---- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/ui/media/js/engine.js b/ui/media/js/engine.js index c6d7b20a..2bad6c41 100644 --- a/ui/media/js/engine.js +++ b/ui/media/js/engine.js @@ -196,7 +196,7 @@ const eventSource = new GenericEventSource(EVENTS_TYPES) function setServerStatus(msgType, msg) { - eventSource.fireEvent(EVENT_STATUS_CHANGED, {type: msgType, message: msg}) + return eventSource.fireEvent(EVENT_STATUS_CHANGED, {type: msgType, message: msg}) } const ServerStates = { @@ -625,7 +625,7 @@ } this._setStatus(TaskStatus.pending) task_queue.set(this, promiseGenerator) - eventSource.fireEvent(EVENT_TASK_QUEUED, {task:this}) + await eventSource.fireEvent(EVENT_TASK_QUEUED, {task:this}) await Task.enqueue(promiseGenerator, ...args) await this.waitUntil({status: TaskStatus.completed}) if (this.exception) { @@ -843,7 +843,7 @@ if (typeof jsonResponse?.task !== 'number') { console.warn('Endpoint error response: ', jsonResponse) const event = Object.assign({task:this}, jsonResponse) - eventSource.fireEvent(EVENT_UNEXPECTED_RESPONSE, event) + await eventSource.fireEvent(EVENT_UNEXPECTED_RESPONSE, event) if ('continueWith' in event) { jsonResponse = await Promise.resolve(event.continueWith) } @@ -1087,6 +1087,7 @@ return activeDevicesCount } + let idleEventPromise = undefined function continueTasks() { if (typeof navigator?.scheduling?.isInputPending === 'function') { const inputPendingOptions = { @@ -1101,14 +1102,18 @@ } const serverCapacity = getServerCapacity() if (task_queue.size <= 0 && concurrent_generators.size <= 0) { - eventSource.fireEvent(EVENT_IDLE, {capacity: serverCapacity, idle: true}) + if (!idleEventPromise?.isPending) { + idleEventPromise = makeQuerablePromise(eventSource.fireEvent(EVENT_IDLE, {capacity: serverCapacity, idle: true})) + } // Calling idle could result in task being added to queue. if (task_queue.size <= 0 && concurrent_generators.size <= 0) { - return asyncDelay(IDLE_COOLDOWN) + return idleEventPromise.then(() => asyncDelay(IDLE_COOLDOWN)) } } if (task_queue.size < serverCapacity) { - eventSource.fireEvent(EVENT_IDLE, {capacity: serverCapacity - task_queue.size}) + if (!idleEventPromise?.isPending) { + idleEventPromise = makeQuerablePromise(eventSource.fireEvent(EVENT_IDLE, {capacity: serverCapacity - task_queue.size})) + } } const completedTasks = [] for (let [generator, promise] of concurrent_generators.entries()) { @@ -1175,8 +1180,8 @@ continue } const event = {task, generator}; - eventSource.fireEvent(EVENT_TASK_START, event) // optional beforeStart promise to wait on before starting task. - const promise = makeQuerablePromise(Promise.resolve(event.beforeStart)) + const beforeStart = eventSource.fireEvent(EVENT_TASK_START, event) // optional beforeStart promise to wait on before starting task. + const promise = makeQuerablePromise(beforeStart.then(() => Promise.resolve(event.beforeStart))) concurrent_generators.set(event.generator, promise) task_queue.set(task, event.generator) } @@ -1201,7 +1206,7 @@ } const continuePromise = continueTasks().catch(async function(err) { console.error(err) - eventSource.fireEvent(EVENT_UNHANDLED_REJECTION, {reason: err}) + await eventSource.fireEvent(EVENT_UNHANDLED_REJECTION, {reason: err}) await asyncDelay(RETRY_DELAY_ON_ERROR) }) taskPromise = makeQuerablePromise(continuePromise) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index e03dba4c..77034fe1 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -473,7 +473,7 @@ function makeImage() { initialText.style.display = 'none' } -function onIdle() { +async function onIdle() { const serverCapacity = SD.serverCapacity for (const taskEntry of getUncompletedTaskEntries()) { if (SD.activeTasks.size >= serverCapacity) { @@ -485,7 +485,7 @@ function onIdle() { taskStatusLabel.style.display = 'none' continue } - onTaskStart(task) + await onTaskStart(task) } } @@ -676,7 +676,7 @@ function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { } -function onTaskStart(task) { +async function onTaskStart(task) { if (!task.isProcessing || task.batchesDone >= task.batchCount) { return } @@ -714,22 +714,24 @@ function onTaskStart(task) { task.outputContainer.insertBefore(outputContainer, task.outputContainer.firstChild) const eventInfo = {reqBody:newTaskReqBody} - PLUGINS['TASK_CREATE'].forEach((hook) => { + const callbacksPromises = PLUGINS['TASK_CREATE'].map((hook) => { if (typeof hook !== 'function') { console.error('The provided TASK_CREATE hook is not a function. Hook: %o', hook) - return + return Promise.reject(new Error('hook is not a function.')) } try { - hook.call(task, eventInfo) + return Promise.resolve(hook.call(task, eventInfo)) } catch (err) { console.error(err) + return Promise.reject(err) } }) + await Promise.allSettled(callbacksPromises) let instance = eventInfo.instance if (!instance) { const factory = PLUGINS.OUTPUTS_FORMATS.get(eventInfo.reqBody?.output_format || newTaskReqBody.output_format) if (factory) { - instance = factory(eventInfo.reqBody || newTaskReqBody) + instance = await Promise.resolve(factory(eventInfo.reqBody || newTaskReqBody)) } if (!instance) { console.error(`${factory ? "Factory " + String(factory) : 'No factory defined'} for output format ${eventInfo.reqBody?.output_format || newTaskReqBody.output_format}. Instance is ${instance || 'undefined'}. Using default renderer.`) diff --git a/ui/media/js/utils.js b/ui/media/js/utils.js index 1408917b..09e1e502 100644 --- a/ui/media/js/utils.js +++ b/ui/media/js/utils.js @@ -561,16 +561,22 @@ class GenericEventSource { throw new Error(`Event ${String(name)} missing from Events.types`) } if (!this.#events.hasOwnProperty(name)) { - return + return Promise.resolve() } if (!args || !args.length) { args = [] } const evs = this.#events[name] - const len = evs.length - for (let i = 0; i < len; ++i) { - evs[i].apply(SD, args) + if (evs.length <= 0) { + return Promise.resolve() } + return Promise.allSettled(evs.map((callback) => { + try { + return Promise.resolve(callback.apply(SD, args)) + } catch (ex) { + return Promise.reject(ex) + } + })) } } From af5c68051a8767ed4a17a9b3dee15265b6451232 Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Sat, 10 Dec 2022 23:29:23 -0800 Subject: [PATCH 7/7] Fix for the tooltips being cutoff (#636) --- ui/index.html | 14 +++++++------- ui/media/css/main.css | 9 +++++++++ ui/media/js/dnd.js | 4 ++-- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/ui/index.html b/ui/index.html index d0aef771..7a085896 100644 --- a/ui/index.html +++ b/ui/index.html @@ -55,7 +55,7 @@
@@ -95,7 +95,7 @@
- +
@@ -109,7 +109,7 @@

Image Settings - + Reset Image Settings @@ -123,13 +123,13 @@ - Click to learn more about custom models + Click to learn more about custom models - Click to learn more about VAEs + Click to learn more about VAEs - Click to learn more about samplers + Click to learn more about samplers