From 49445d6473270624367cfa341c2b99c67d273cd4 Mon Sep 17 00:00:00 2001
From: ManInDark <61268856+ManInDark@users.noreply.github.com>
Date: Mon, 26 Jun 2023 19:36:26 +0200
Subject: [PATCH] Added Progess to Page Title as mentioned in #307
---
ui/media/js/main.js | 339 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 336 insertions(+), 3 deletions(-)
diff --git a/ui/media/js/main.js b/ui/media/js/main.js
index 101a88e7..9bfb8ef3 100644
--- a/ui/media/js/main.js
+++ b/ui/media/js/main.js
@@ -62,6 +62,7 @@ const taskConfigSetup = {
let imageCounter = 0
let imageRequest = []
+let SHOW_PROGRESS = true
let promptField = document.querySelector("#prompt")
let promptsFromFileSelector = document.querySelector("#prompt_from_file")
@@ -1044,13 +1045,345 @@ function makeImage() {
newTaskRequests.forEach(createTask)
updateInitialText()
+}
- const countBeforeBanner = localStorage.getItem("countBeforeBanner") || 1
- if (countBeforeBanner <= 0) {
- // supportBanner.classList.remove("displayNone")
+async function onIdle() {
+ const serverCapacity = SD.serverCapacity
+ if (pauseClient === true) {
+ await resumeClient()
+ }
+
+ for (const taskEntry of getUncompletedTaskEntries()) {
+ if (SD.activeTasks.size >= serverCapacity) {
+ break
+ }
+ const task = htmlTaskMap.get(taskEntry)
+ if (!task) {
+ const taskStatusLabel = taskEntry.querySelector(".taskStatusLabel")
+ taskStatusLabel.style.display = "none"
+ continue
+ }
+ await onTaskStart(task)
+ }
+}
+
+function getTaskUpdater(task, reqBody, outputContainer) {
+ const outputMsg = task["outputMsg"]
+ const progressBar = task["progressBar"]
+ const progressBarInner = progressBar.querySelector("div")
+
+ const batchCount = task.batchCount
+ let lastStatus = undefined
+ return async function(event) {
+ if (this.status !== lastStatus) {
+ lastStatus = this.status
+ switch (this.status) {
+ case SD.TaskStatus.pending:
+ task["taskStatusLabel"].innerText = "Pending"
+ task["taskStatusLabel"].classList.add("waitingTaskLabel")
+ break
+ case SD.TaskStatus.waiting:
+ task["taskStatusLabel"].innerText = "Waiting"
+ task["taskStatusLabel"].classList.add("waitingTaskLabel")
+ task["taskStatusLabel"].classList.remove("activeTaskLabel")
+ break
+ case SD.TaskStatus.processing:
+ case SD.TaskStatus.completed:
+ task["taskStatusLabel"].innerText = "Processing"
+ task["taskStatusLabel"].classList.add("activeTaskLabel")
+ task["taskStatusLabel"].classList.remove("waitingTaskLabel")
+ break
+ case SD.TaskStatus.stopped:
+ break
+ case SD.TaskStatus.failed:
+ if (!SD.isServerAvailable()) {
+ logError(
+ "Stable Diffusion is still starting up, please wait. If this goes on beyond a few minutes, Stable Diffusion has probably crashed. Please check the error message in the command-line window.",
+ event,
+ outputMsg
+ )
+ } else if (typeof event?.response === "object") {
+ let msg = "Stable Diffusion had an error reading the response:
" + if (this.exception) { + msg += `Error: ${this.exception.message}" + logError(msg, event, outputMsg) + } + break + } + } + if ("update" in event) { + const stepUpdate = event.update + if (!("step" in stepUpdate)) { + return + } + // task.instances can be a mix of different tasks with uneven number of steps (Render Vs Filter Tasks) + const overallStepCount = + task.instances.reduce( + (sum, instance) => + sum + + (instance.isPending + ? Math.max(0, instance.step || stepUpdate.step) / + (instance.total_steps || stepUpdate.total_steps) + : 1), + 0 // Initial value + ) * stepUpdate.total_steps // Scale to current number of steps. + const totalSteps = task.instances.reduce( + (sum, instance) => sum + (instance.total_steps || stepUpdate.total_steps), + stepUpdate.total_steps * (batchCount - task.batchesDone) // Initial value at (unstarted task count * Nbr of steps) + ) + const percent = Math.min(100, 100 * (overallStepCount / totalSteps)).toFixed(0) + if (SHOW_PROGRESS) { + document.title = `${percent}% - Easy Diffusion` + } + + const timeTaken = stepUpdate.step_time // sec + const stepsRemaining = Math.max(0, totalSteps - overallStepCount) + const timeRemaining = timeTaken < 0 ? "" : millisecondsToStr(stepsRemaining * timeTaken * 1000) + outputMsg.innerHTML = `Batch ${task.batchesDone} of ${batchCount}. Generating image(s): ${percent}%. Time remaining (approx): ${timeRemaining}` + outputMsg.style.display = "block" + progressBarInner.style.width = `${percent}%` + + if (stepUpdate.output) { + showImages(reqBody, stepUpdate, outputContainer, true) + } + } + } +} + +function abortTask(task) { + if (!task.isProcessing) { + return false + } + task.isProcessing = false + task.progressBar.classList.remove("active") + task["taskStatusLabel"].style.display = "none" + task["stopTask"].innerHTML = ' Remove' + if (!task.instances?.some((r) => r.isPending)) { + return + } + task.instances.forEach((instance) => { + try { + instance.abort() + } catch (e) { + console.error(e) + } + }) + if (SHOW_PROGRESS) { + document.title = "Stopped - Easy Diffusion" + } +} + +function onTaskErrorHandler(task, reqBody, instance, reason) { + if (!task.isProcessing) { + return + } + console.log("Render request %o, Instance: %o, Error: %s", reqBody, instance, reason) + abortTask(task) + const outputMsg = task["outputMsg"] + logError( + "Stable Diffusion had an error. Please check the logs in the command-line window.
` + } + try { + // 'Response': body stream already read + msg += "Read: " + (await event.response.text()) + } catch (e) { + msg += "Unexpected end of stream. " + } + const bufferString = event.reader.bufferedString + if (bufferString) { + msg += "Buffered data: " + bufferString + } + msg += "
" + + reason.stack + + "", + task, + outputMsg + ) + setStatus("request", "error", "error") +} + +function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { + if (typeof stepUpdate === "object") { + if (stepUpdate.status === "succeeded") { + showImages(reqBody, stepUpdate, outputContainer, false) + } else { + 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 += `
StepUpdate: ${JSON.stringify(stepUpdate, undefined, 4)}` + } + logError(msg, stepUpdate, outputMsg) + } + } + if (task.isProcessing && task.batchesDone < task.batchCount) { + task["taskStatusLabel"].innerText = "Pending" + task["taskStatusLabel"].classList.add("waitingTaskLabel") + task["taskStatusLabel"].classList.remove("activeTaskLabel") + return + } + if ("instances" in task && task.instances.some((ins) => ins != instance && ins.isPending)) { + return + } + + task.isProcessing = false + task["stopTask"].innerHTML = ' Remove' + task["taskStatusLabel"].style.display = "none" + + let time = millisecondsToStr(Date.now() - task.startTime) + + if (task.batchesDone == task.batchCount) { + if (!task.outputMsg.innerText.toLowerCase().includes("error")) { + task.outputMsg.innerText = `Processed ${task.numOutputsTotal} images in ${time}` + } + task.progressBar.style.height = "0px" + task.progressBar.style.border = "0px solid var(--background-color3)" + task.progressBar.classList.remove("active") + setStatus("request", "done", "success") } else { localStorage.setItem("countBeforeBanner", countBeforeBanner - 1) } + + if (randomSeedField.checked) { + seedField.value = task.seed + } + + if (SD.activeTasks.size > 0) { + return + } + const uncompletedTasks = getUncompletedTaskEntries() + if (uncompletedTasks && uncompletedTasks.length > 0) { + return + } + + if (pauseClient) { + resumeBtn.click() + } + renderButtons.style.display = "none" + renameMakeImageButton() + + if (isSoundEnabled()) { + playSound() + } + if (SHOW_PROGRESS) { + document.title = "Completed - Easy Diffusion" + } +} + +async function onTaskStart(task) { + if (!task.isProcessing || task.batchesDone >= task.batchCount) { + return + } + + if (typeof task.startTime !== "number") { + task.startTime = Date.now() + } + if (!("instances" in task)) { + task["instances"] = [] + } + + task["stopTask"].innerHTML = ' Stop' + task["taskStatusLabel"].innerText = "Starting" + task["taskStatusLabel"].classList.add("waitingTaskLabel") + + let newTaskReqBody = task.reqBody + if (task.batchCount > 1) { + // Each output render batch needs it's own task reqBody instance to avoid altering the other runs after they are completed. + newTaskReqBody = Object.assign({}, task.reqBody) + if (task.batchesDone == task.batchCount - 1) { + // Last batch of the task + // If the number of parallel jobs is no factor of the total number of images, the last batch must create less than "parallel jobs count" images + // E.g. with numOutputsTotal = 6 and num_outputs = 5, the last batch shall only generate 1 image. + newTaskReqBody.num_outputs = task.numOutputsTotal - task.reqBody.num_outputs * (task.batchCount - 1) + } + } + + const startSeed = task.seed || newTaskReqBody.seed + const genSeeds = Boolean( + typeof newTaskReqBody.seed !== "number" || (newTaskReqBody.seed === task.seed && task.numOutputsTotal > 1) + ) + if (genSeeds) { + newTaskReqBody.seed = parseInt(startSeed) + task.batchesDone * task.reqBody.num_outputs + } + + // Update the seed *before* starting the processing so it's retained if user stops the task + if (randomSeedField.checked) { + seedField.value = task.seed + } + + const outputContainer = document.createElement("div") + outputContainer.className = "img-batch" + task.outputContainer.insertBefore(outputContainer, task.outputContainer.firstChild) + + const eventInfo = { reqBody: newTaskReqBody } + 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 Promise.reject(new Error("hook is not a function.")) + } + try { + 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 = 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.` + ) + instance = new SD.RenderTask(eventInfo.reqBody || newTaskReqBody) + } + } + + task["instances"].push(instance) + task.batchesDone++ + + instance.enqueue(getTaskUpdater(task, newTaskReqBody, outputContainer)).then( + (renderResult) => { + onTaskCompleted(task, newTaskReqBody, instance, outputContainer, renderResult) + }, + (reason) => { + onTaskErrorHandler(task, newTaskReqBody, instance, reason) + } + ) + + setStatus("request", "fetching..") + renderButtons.style.display = "flex" + renameMakeImageButton() + updateInitialText() } /* Hover effect for the init image in the task list */