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}
` + } + 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 += "
" + 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.

" + + reason + + "
" +
+            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 += `

+ 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 picking a lower level in the 'GPU Memory Usage' setting (in the 'Settings' tab).
+ 3. Try generating a smaller image.
` + } else if (msg.includes("DefaultCPUAllocator: not enough memory")) { + msg += `

+ Reason: Your computer is running out of system RAM! +

+ Suggestions: +
+ 1. Try closing unnecessary programs and browser tabs.
+ 2. If that doesn't help, please increase your computer's virtual memory by following these steps for + Windows or + Linux.
+ 3. Try restarting your computer.
` + } + } else { + msg = `Unexpected Read Error:
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 */