"use strict" // Opt in to a restricted variant of JavaScript const MAX_INIT_IMAGE_DIMENSION = 768 const MIN_GPUS_TO_SHOW_SELECTION = 2 const IMAGE_REGEX = new RegExp("data:image/[A-Za-z]+;base64") const htmlTaskMap = new WeakMap() const taskConfigSetup = { taskConfig: { seed: { value: ({ seed }) => seed, label: "Seed" }, dimensions: { value: ({ reqBody }) => `${reqBody?.width}x${reqBody?.height}`, label: "Dimensions" }, sampler_name: "Sampler", num_inference_steps: "Inference Steps", guidance_scale: "Guidance Scale", use_stable_diffusion_model: "Model", clip_skip: { label: "Clip Skip", visible: ({ reqBody }) => reqBody?.clip_skip, value: ({ reqBody }) => "yes", }, tiling: { label: "Tiling", visible: ({ reqBody }) => reqBody?.tiling != "none", value: ({ reqBody }) => reqBody?.tiling, }, use_vae_model: { label: "VAE", visible: ({ reqBody }) => reqBody?.use_vae_model !== undefined && reqBody?.use_vae_model.trim() !== "", }, negative_prompt: { label: "Negative Prompt", visible: ({ reqBody }) => reqBody?.negative_prompt !== undefined && reqBody?.negative_prompt.trim() !== "", }, prompt_strength: "Prompt Strength", use_face_correction: "Fix Faces", upscale: { value: ({ reqBody }) => `${reqBody?.use_upscale} (${reqBody?.upscale_amount || 4}x)`, label: "Upscale", visible: ({ reqBody }) => !!reqBody?.use_upscale, }, use_hypernetwork_model: "Hypernetwork", hypernetwork_strength: { label: "Hypernetwork Strength", visible: ({ reqBody }) => !!reqBody?.use_hypernetwork_model, }, use_lora_model: { label: "Lora Model", visible: ({ reqBody }) => !!reqBody?.use_lora_model }, lora_alpha: { label: "Lora Strength", visible: ({ reqBody }) => !!reqBody?.use_lora_model }, preserve_init_image_color_profile: "Preserve Color Profile", }, pluginTaskConfig: {}, getCSSKey: (key) => key .split("_") .map((s) => s.charAt(0).toUpperCase() + s.slice(1)) .join(""), } let imageCounter = 0 let imageRequest = [] let promptField = document.querySelector("#prompt") let promptsFromFileSelector = document.querySelector("#prompt_from_file") let promptsFromFileBtn = document.querySelector("#promptsFromFileBtn") let negativePromptField = document.querySelector("#negative_prompt") let numOutputsTotalField = document.querySelector("#num_outputs_total") let numOutputsParallelField = document.querySelector("#num_outputs_parallel") let numInferenceStepsField = document.querySelector("#num_inference_steps") let guidanceScaleSlider = document.querySelector("#guidance_scale_slider") let guidanceScaleField = document.querySelector("#guidance_scale") let outputQualitySlider = document.querySelector("#output_quality_slider") let outputQualityField = document.querySelector("#output_quality") let outputQualityRow = document.querySelector("#output_quality_row") let randomSeedField = document.querySelector("#random_seed") let seedField = document.querySelector("#seed") let widthField = document.querySelector("#width") let heightField = document.querySelector("#height") let smallImageWarning = document.querySelector("#small_image_warning") let initImageSelector = document.querySelector("#init_image") let initImagePreview = document.querySelector("#init_image_preview") let initImageSizeBox = document.querySelector("#init_image_size_box") let maskImageSelector = document.querySelector("#mask") let maskImagePreview = document.querySelector("#mask_preview") let applyColorCorrectionField = document.querySelector("#apply_color_correction") let colorCorrectionSetting = document.querySelector("#apply_color_correction_setting") let promptStrengthSlider = document.querySelector("#prompt_strength_slider") let promptStrengthField = document.querySelector("#prompt_strength") let samplerField = document.querySelector("#sampler_name") let samplerSelectionContainer = document.querySelector("#samplerSelection") let useFaceCorrectionField = document.querySelector("#use_face_correction") let gfpganModelField = new ModelDropdown(document.querySelector("#gfpgan_model"), ["gfpgan", "codeformer"], "", false) let useUpscalingField = document.querySelector("#use_upscale") let upscaleModelField = document.querySelector("#upscale_model") let upscaleAmountField = document.querySelector("#upscale_amount") let latentUpscalerSettings = document.querySelector("#latent_upscaler_settings") let latentUpscalerStepsSlider = document.querySelector("#latent_upscaler_steps_slider") let latentUpscalerStepsField = document.querySelector("#latent_upscaler_steps") let codeformerFidelitySlider = document.querySelector("#codeformer_fidelity_slider") let codeformerFidelityField = document.querySelector("#codeformer_fidelity") let stableDiffusionModelField = new ModelDropdown(document.querySelector("#stable_diffusion_model"), "stable-diffusion") let clipSkipField = document.querySelector("#clip_skip") let tilingField = document.querySelector("#tiling") let vaeModelField = new ModelDropdown(document.querySelector("#vae_model"), "vae", "None") let hypernetworkModelField = new ModelDropdown(document.querySelector("#hypernetwork_model"), "hypernetwork", "None") let hypernetworkStrengthSlider = document.querySelector("#hypernetwork_strength_slider") let hypernetworkStrengthField = document.querySelector("#hypernetwork_strength") let loraModelField = new ModelDropdown(document.querySelector("#lora_model"), "lora", "None") let loraAlphaSlider = document.querySelector("#lora_alpha_slider") let loraAlphaField = document.querySelector("#lora_alpha") let outputFormatField = document.querySelector("#output_format") let outputLosslessField = document.querySelector("#output_lossless") let outputLosslessContainer = document.querySelector("#output_lossless_container") let blockNSFWField = document.querySelector("#block_nsfw") let showOnlyFilteredImageField = document.querySelector("#show_only_filtered_image") let updateBranchLabel = document.querySelector("#updateBranchLabel") let streamImageProgressField = document.querySelector("#stream_image_progress") let thumbnailSizeField = document.querySelector("#thumbnail_size-input") let autoscrollBtn = document.querySelector("#auto_scroll_btn") let autoScroll = document.querySelector("#auto_scroll") let makeImageBtn = document.querySelector("#makeImage") let stopImageBtn = document.querySelector("#stopImage") let pauseBtn = document.querySelector("#pause") let resumeBtn = document.querySelector("#resume") let renderButtons = document.querySelector("#render-buttons") let imagesContainer = document.querySelector("#current-images") let initImagePreviewContainer = document.querySelector("#init_image_preview_container") let initImageClearBtn = document.querySelector(".init_image_clear") let promptStrengthContainer = document.querySelector("#prompt_strength_container") let initialText = document.querySelector("#initial-text") let versionText = document.querySelector("#version") let previewTools = document.querySelector("#preview-tools") let clearAllPreviewsBtn = document.querySelector("#clear-all-previews") let showDownloadDialogBtn = document.querySelector("#show-download-dialog") let saveAllImagesDialog = document.querySelector("#download-images-dialog") let saveAllImagesBtn = document.querySelector("#save-all-images") let saveAllImagesCloseBtn = document.querySelector("#download-images-close-button") let saveAllZipToggle = document.querySelector("#zip_toggle") let saveAllTreeToggle = document.querySelector("#tree_toggle") let saveAllJSONToggle = document.querySelector("#json_toggle") let saveAllFoldersOption = document.querySelector("#download-add-folders") let splashScreenPopup = document.querySelector("#splash-screen") let maskSetting = document.querySelector("#enable_mask") const processOrder = document.querySelector("#process_order_toggle") let imagePreview = document.querySelector("#preview") let imagePreviewContent = document.querySelector("#preview-content") let undoButton = document.querySelector("#undo") let undoBuffer = [] const UNDO_LIMIT = 20 imagePreview.addEventListener("drop", function(ev) { const data = ev.dataTransfer?.getData("text/plain") if (!data) { return } const movedTask = document.getElementById(data) if (!movedTask) { return } ev.preventDefault() let moveTarget = ev.target while (moveTarget && typeof moveTarget === "object" && moveTarget.parentNode !== imagePreviewContent) { moveTarget = moveTarget.parentNode } if (moveTarget === initialText || moveTarget === previewTools) { moveTarget = null } if (moveTarget === movedTask) { return } if (moveTarget) { const childs = Array.from(imagePreviewContent.children) if (moveTarget.nextSibling && childs.indexOf(movedTask) < childs.indexOf(moveTarget)) { // Move after the target if lower than current position. moveTarget = moveTarget.nextSibling } } const newNode = imagePreviewContent.insertBefore(movedTask, moveTarget || previewTools.nextSibling) if (newNode === movedTask) { return } imagePreviewContent.removeChild(movedTask) const task = htmlTaskMap.get(movedTask) if (task) { htmlTaskMap.delete(movedTask) } if (task) { htmlTaskMap.set(newNode, task) } }) let showConfigToggle = document.querySelector("#configToggleBtn") // let configBox = document.querySelector('#config') // let outputMsg = document.querySelector('#outputMsg') let soundToggle = document.querySelector("#sound_toggle") let serverStatusColor = document.querySelector("#server-status-color") let serverStatusMsg = document.querySelector("#server-status-msg") function getLocalStorageBoolItem(key, fallback) { let item = localStorage.getItem(key) if (item === null) { return fallback } return item === "true" ? true : false } function handleBoolSettingChange(key) { return function(e) { localStorage.setItem(key, e.target.checked.toString()) } } function handleStringSettingChange(key) { return function(e) { localStorage.setItem(key, e.target.value.toString()) } } function isSoundEnabled() { return getSetting("sound_toggle") } function getSavedDiskPath() { return getSetting("diskPath") } function setStatus(statusType, msg, msgType) {} function setServerStatus(event) { switch (event.type) { case "online": serverStatusColor.style.color = "var(--status-green)" serverStatusMsg.style.color = "var(--status-green)" serverStatusMsg.innerText = "Stable Diffusion is " + event.message break case "busy": serverStatusColor.style.color = "var(--status-orange)" serverStatusMsg.style.color = "var(--status-orange)" serverStatusMsg.innerText = "Stable Diffusion is " + event.message break case "error": serverStatusColor.style.color = "var(--status-red)" serverStatusMsg.style.color = "var(--status-red)" serverStatusMsg.innerText = "Stable Diffusion has stopped" break } if (SD.serverState.devices) { document.dispatchEvent(new CustomEvent("system_info_update", { detail: SD.serverState.devices })) } } // shiftOrConfirm(e, prompt, fn) // e : MouseEvent // prompt : Text to be shown as prompt. Should be a question to which "yes" is a good answer. // fn : function to be called if the user confirms the dialog or has the shift key pressed // // If the user had the shift key pressed while clicking, the function fn will be executed. // If the setting "confirm_dangerous_actions" in the system settings is disabled, the function // fn will be executed. // Otherwise, a confirmation dialog is shown. If the user confirms, the function fn will also // be executed. function shiftOrConfirm(e, prompt, fn) { e.stopPropagation() if (e.shiftKey || !confirmDangerousActionsField.checked) { fn(e) } else { confirm( 'Tip: To skip this dialog, use shift-click or disable the "Confirm dangerous actions" setting in the Settings tab.', prompt, () => { fn(e) } ) } } function logMsg(msg, level, outputMsg) { if (outputMsg.hasChildNodes()) { outputMsg.appendChild(document.createElement("br")) } if (level === "error") { outputMsg.innerHTML += 'Error: ' + msg + "" } else if (level === "warn") { outputMsg.innerHTML += 'Warning: ' + msg + "" } else { outputMsg.innerText += msg } console.log(level, msg) } function logError(msg, res, outputMsg) { logMsg(msg, "error", outputMsg) console.log("request error", res) console.trace() setStatus("request", "error", "error") } function playSound() { const audio = new Audio("/media/ding.mp3") audio.volume = 0.2 var promise = audio.play() if (promise !== undefined) { promise .then((_) => {}) .catch((error) => { console.warn("browser blocked autoplay") }) } } function undoableRemove(element, doubleUndo = false) { let data = { element: element, parent: element.parentNode, prev: element.previousSibling, next: element.nextSibling, doubleUndo: doubleUndo, } undoBuffer.push(data) if (undoBuffer.length > UNDO_LIMIT) { // Remove item from memory and also remove it from the data structures let item = undoBuffer.shift() htmlTaskMap.delete(item.element) item.element.querySelectorAll("[data-imagecounter]").forEach((img) => { delete imageRequest[img.dataset["imagecounter"]] }) } element.remove() if (undoBuffer.length != 0) { undoButton.classList.remove("displayNone") } } function undoRemove() { let data = undoBuffer.pop() if (!data) { return } if (data.next == null) { data.parent.appendChild(data.element) } else { data.parent.insertBefore(data.element, data.next) } if (data.doubleUndo) { undoRemove() } if (undoBuffer.length == 0) { undoButton.classList.add("displayNone") } updateInitialText() } undoButton.addEventListener("click", () => { undoRemove() }) document.addEventListener("keydown", function(e) { if ((e.ctrlKey || e.metaKey) && e.key === "z" && e.target == document.body) { undoRemove() } }) function showImages(reqBody, res, outputContainer, livePreview) { let imageItemElements = outputContainer.querySelectorAll(".imgItem") if (typeof res != "object") return res.output.reverse() res.output.forEach((result, index) => { const imageData = result?.data || result?.path + "?t=" + Date.now(), imageSeed = result?.seed, imagePrompt = reqBody.prompt, imageInferenceSteps = reqBody.num_inference_steps, imageGuidanceScale = reqBody.guidance_scale, imageWidth = reqBody.width, imageHeight = reqBody.height if (!imageData.includes("/")) { // res contained no data for the image, stop execution setStatus("request", "invalid image", "error") return } let imageItemElem = index < imageItemElements.length ? imageItemElements[index] : null if (!imageItemElem) { imageItemElem = document.createElement("div") imageItemElem.className = "imgItem" imageItemElem.innerHTML = `
` outputContainer.appendChild(imageItemElem) const imageRemoveBtn = imageItemElem.querySelector(".imgPreviewItemClearBtn") let parentTaskContainer = imageRemoveBtn.closest(".imageTaskContainer") imageRemoveBtn.addEventListener("click", (e) => { undoableRemove(imageItemElem) let allHidden = true let children = parentTaskContainer.querySelectorAll(".imgItem") for (let x = 0; x < children.length; x++) { let child = children[x] if (child.style.display != "none") { allHidden = false } } if (allHidden === true) { const req = htmlTaskMap.get(parentTaskContainer) if (!req.isProcessing || req.batchesDone == req.batchCount) { undoableRemove(parentTaskContainer, true) } } }) } const imageElem = imageItemElem.querySelector("img") imageElem.src = imageData imageElem.width = parseInt(imageWidth) imageElem.height = parseInt(imageHeight) imageElem.setAttribute("data-prompt", imagePrompt) imageElem.setAttribute("data-steps", imageInferenceSteps) imageElem.setAttribute("data-guidance", imageGuidanceScale) imageElem.addEventListener("load", function() { imageItemElem.querySelector(".img_bottom_label").innerText = `${this.naturalWidth} x ${this.naturalHeight}` }) const imageInfo = imageItemElem.querySelector(".imgItemInfo") imageInfo.style.visibility = livePreview ? "hidden" : "visible" if ("seed" in result && !imageElem.hasAttribute("data-seed")) { const imageExpandBtn = imageItemElem.querySelector(".imgExpandBtn") imageExpandBtn.addEventListener("click", function() { function previousImage(img) { const allImages = Array.from(outputContainer.parentNode.querySelectorAll(".imgItem img")) const index = allImages.indexOf(img) return allImages.slice(0, index).reverse()[0] } function nextImage(img) { const allImages = Array.from(outputContainer.parentNode.querySelectorAll(".imgItem img")) const index = allImages.indexOf(img) return allImages.slice(index + 1)[0] } function imageModalParameter(img) { const previousImg = previousImage(img) const nextImg = nextImage(img) return { src: img.src, previous: previousImg ? () => imageModalParameter(previousImg) : undefined, next: nextImg ? () => imageModalParameter(nextImg) : undefined, } } imageModal(imageModalParameter(imageElem)) }) const req = Object.assign({}, reqBody, { seed: result?.seed || reqBody.seed, }) imageElem.setAttribute("data-seed", req.seed) imageElem.setAttribute("data-imagecounter", ++imageCounter) imageRequest[imageCounter] = req const imageSeedLabel = imageItemElem.querySelector(".imgSeedLabel") imageSeedLabel.innerText = "Seed: " + req.seed let buttons = [ { text: "Use as Input", on_click: onUseAsInputClick }, [ { html: ' Download Image', on_click: onDownloadImageClick, class: "download-img", }, { html: ' JSON', on_click: onDownloadJSONClick, class: "download-json", }, ], { text: "Make Similar Images", on_click: onMakeSimilarClick }, { text: "Draw another 25 steps", on_click: onContinueDrawingClick }, [ { text: "Upscale", on_click: onUpscaleClick, filter: (req, img) => !req.use_upscale }, { text: "Fix Faces", on_click: onFixFacesClick, filter: (req, img) => !req.use_face_correction }, ], ] // include the plugins buttons = buttons.concat(PLUGINS["IMAGE_INFO_BUTTONS"]) const imgItemInfo = imageItemElem.querySelector(".imgItemInfo") const img = imageItemElem.querySelector("img") const createButton = function(btnInfo) { if (Array.isArray(btnInfo)) { const wrapper = document.createElement("div") btnInfo.map(createButton).forEach((buttonElement) => wrapper.appendChild(buttonElement)) return wrapper } const isLabel = btnInfo.type === "label" const newButton = document.createElement(isLabel ? "span" : "button") newButton.classList.add("tasksBtns") if (btnInfo.html) { const html = typeof btnInfo.html === "function" ? btnInfo.html() : btnInfo.html if (html instanceof HTMLElement) { newButton.appendChild(html) } else { newButton.innerHTML = html } } else { newButton.innerText = typeof btnInfo.text === "function" ? btnInfo.text() : btnInfo.text } if (btnInfo.on_click || !isLabel) { newButton.addEventListener("click", function(event) { btnInfo.on_click(req, img, event) }) } if (btnInfo.class !== undefined) { if (Array.isArray(btnInfo.class)) { newButton.classList.add(...btnInfo.class) } else { newButton.classList.add(btnInfo.class) } } return newButton } buttons.forEach((btn) => { if (Array.isArray(btn)) { btn = btn.filter((btnInfo) => !btnInfo.filter || btnInfo.filter(req, img) === true) if (btn.length === 0) { return } } else if (btn.filter && btn.filter(req, img) === false) { return } try { imgItemInfo.appendChild(createButton(btn)) } catch (err) { console.error("Error creating image info button from plugin: ", btn, err) } }) } }) } function onUseAsInputClick(req, img) { const imgData = img.src initImageSelector.value = null initImagePreview.src = imgData maskSetting.checked = false } function getDownloadFilename(img, suffix) { const imageSeed = img.getAttribute("data-seed") const imagePrompt = img.getAttribute("data-prompt") const imageInferenceSteps = img.getAttribute("data-steps") const imageGuidanceScale = img.getAttribute("data-guidance") return createFileName(imagePrompt, imageSeed, imageInferenceSteps, imageGuidanceScale, suffix) } function onDownloadJSONClick(req, img) { const name = getDownloadFilename(img, "json") const blob = new Blob([JSON.stringify(req, null, 2)], { type: "text/plain" }) saveAs(blob, name) } function onDownloadImageClick(req, img) { const name = getDownloadFilename(img, req["output_format"]) const blob = dataURItoBlob(img.src) saveAs(blob, name) } function modifyCurrentRequest(...reqDiff) { const newTaskRequest = getCurrentUserRequest() newTaskRequest.reqBody = Object.assign(newTaskRequest.reqBody, ...reqDiff, { use_cpu: useCPUField.checked, }) newTaskRequest.seed = newTaskRequest.reqBody.seed return newTaskRequest } function onMakeSimilarClick(req, img) { const newTaskRequest = modifyCurrentRequest(req, { num_outputs: 1, num_inference_steps: 50, guidance_scale: 7.5, prompt_strength: 0.7, init_image: img.src, seed: Math.floor(Math.random() * 10000000), }) newTaskRequest.numOutputsTotal = 5 newTaskRequest.batchCount = 5 delete newTaskRequest.reqBody.mask createTask(newTaskRequest) } function enqueueImageVariationTask(req, img, reqDiff) { const imageSeed = img.getAttribute("data-seed") const newRequestBody = { num_outputs: 1, // this can be user-configurable in the future seed: imageSeed, } // If the user is editing pictures, stop modifyCurrentRequest from importing // new values by setting the missing properties to undefined if (!("init_image" in req) && !("init_image" in reqDiff)) { newRequestBody.init_image = undefined newRequestBody.mask = undefined } else if (!("mask" in req) && !("mask" in reqDiff)) { newRequestBody.mask = undefined } const newTaskRequest = modifyCurrentRequest(req, reqDiff, newRequestBody) newTaskRequest.numOutputsTotal = 1 // this can be user-configurable in the future newTaskRequest.batchCount = 1 createTask(newTaskRequest) } function onUpscaleClick(req, img) { enqueueImageVariationTask(req, img, { use_upscale: upscaleModelField.value, }) } function onFixFacesClick(req, img) { enqueueImageVariationTask(req, img, { use_face_correction: gfpganModelField.value, }) } function onContinueDrawingClick(req, img) { enqueueImageVariationTask(req, img, { num_inference_steps: parseInt(req.num_inference_steps) + 25, }) } function getUncompletedTaskEntries() { const taskEntries = Array.from(document.querySelectorAll("#preview .imageTaskContainer .taskStatusLabel")) .filter((taskLabel) => taskLabel.style.display !== "none") .map(function(taskLabel) { let imageTaskContainer = taskLabel.parentNode while (!imageTaskContainer.classList.contains("imageTaskContainer") && imageTaskContainer.parentNode) { imageTaskContainer = imageTaskContainer.parentNode } return imageTaskContainer }) if (!processOrder.checked) { taskEntries.reverse() } return taskEntries } function makeImage() { if (typeof performance == "object" && performance.mark) { performance.mark("click-makeImage") } if (!SD.isServerAvailable()) { alert("The server is not available.") return } if (!randomSeedField.checked && seedField.value == "") { alert('The "Seed" field must not be empty.') return } if (numInferenceStepsField.value == "") { alert('The "Inference Steps" field must not be empty.') return } if (numOutputsTotalField.value == "" || numOutputsTotalField.value == 0) { numOutputsTotalField.value = 1 } if (numOutputsParallelField.value == "" || numOutputsParallelField.value == 0) { numOutputsParallelField.value = 1 } if (guidanceScaleField.value == "") { guidanceScaleField.value = guidanceScaleSlider.value / 10 } if (hypernetworkStrengthField.value == "") { hypernetworkStrengthField.value = hypernetworkStrengthSlider.value / 100 } const taskTemplate = getCurrentUserRequest() const newTaskRequests = getPrompts().map((prompt) => Object.assign({}, taskTemplate, { reqBody: Object.assign({ prompt: prompt }, taskTemplate.reqBody), }) ) newTaskRequests.forEach(createTask) updateInitialText() } 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) 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) } }) } 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 { task.outputMsg.innerText += `. Task ended after ${time}` } 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() } } 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 */ function createInitImageHover(taskEntry) { var $tooltip = $(taskEntry.querySelector(".task-fs-initimage")) var img = document.createElement("img") img.src = taskEntry.querySelector("div.task-initimg > img").src $tooltip.append(img) $tooltip.append(`
`) $tooltip.find("button").on("click", (e) => { e.stopPropagation() onUseAsInputClick(null, img) }) } let startX, startY function onTaskEntryDragOver(event) { imagePreview.querySelectorAll(".imageTaskContainer").forEach((itc) => { if (itc != event.target.closest(".imageTaskContainer")) { itc.classList.remove("dropTargetBefore", "dropTargetAfter") } }) if (event.target.closest(".imageTaskContainer")) { if (startX && startY) { if (event.target.closest(".imageTaskContainer").offsetTop > startY) { event.target.closest(".imageTaskContainer").classList.add("dropTargetAfter") } else if (event.target.closest(".imageTaskContainer").offsetTop < startY) { event.target.closest(".imageTaskContainer").classList.add("dropTargetBefore") } else if (event.target.closest(".imageTaskContainer").offsetLeft > startX) { event.target.closest(".imageTaskContainer").classList.add("dropTargetAfter") } else if (event.target.closest(".imageTaskContainer").offsetLeft < startX) { event.target.closest(".imageTaskContainer").classList.add("dropTargetBefore") } } } } function generateConfig({ label, value, visible, cssKey }) { if (!visible) return null return `
${label}: ${value}` } function getVisibleConfig(config, task) { const mergedTaskConfig = { ...config.taskConfig, ...config.pluginTaskConfig } return Object.keys(mergedTaskConfig) .map((key) => { const value = mergedTaskConfig?.[key]?.value?.(task) ?? task.reqBody[key] const visible = mergedTaskConfig?.[key]?.visible?.(task) ?? value !== undefined ?? true const label = mergedTaskConfig?.[key]?.label ?? mergedTaskConfig?.[key] const cssKey = config.getCSSKey(key) return { label, visible, value, cssKey } }) .map((obj) => generateConfig(obj)) .filter((obj) => obj) } function createTaskConfig(task) { return getVisibleConfig(taskConfigSetup, task).join("
") } function createTask(task) { let taskConfig = "" if (task.reqBody.init_image !== undefined) { let h = 80 let w = ((task.reqBody.width * h) / task.reqBody.height) >> 0 taskConfig += `
` } taskConfig += `
${createTaskConfig(task)}
` let taskEntry = document.createElement("div") taskEntry.id = `imageTaskContainer-${Date.now()}` taskEntry.className = "imageTaskContainer" taskEntry.innerHTML = `
Enqueued
${taskConfig}
` createCollapsibles(taskEntry) let draghandle = taskEntry.querySelector(".drag-handle") draghandle.addEventListener("mousedown", (e) => { taskEntry.setAttribute("draggable", true) }) // Add a debounce delay to allow mobile to bouble tap. draghandle.addEventListener( "mouseup", debounce((e) => { taskEntry.setAttribute("draggable", false) }, 2000) ) draghandle.addEventListener("click", (e) => { e.preventDefault() // Don't allow the results to be collapsed... }) taskEntry.addEventListener("dragend", (e) => { taskEntry.setAttribute("draggable", false) imagePreview.querySelectorAll(".imageTaskContainer").forEach((itc) => { itc.classList.remove("dropTargetBefore", "dropTargetAfter") }) imagePreview.removeEventListener("dragover", onTaskEntryDragOver) }) taskEntry.addEventListener("dragstart", function(e) { imagePreview.addEventListener("dragover", onTaskEntryDragOver) e.dataTransfer.setData("text/plain", taskEntry.id) startX = e.target.closest(".imageTaskContainer").offsetLeft startY = e.target.closest(".imageTaskContainer").offsetTop }) if (task.reqBody.init_image !== undefined) { createInitImageHover(taskEntry) } task["taskStatusLabel"] = taskEntry.querySelector(".taskStatusLabel") task["outputContainer"] = taskEntry.querySelector(".img-preview") task["outputMsg"] = taskEntry.querySelector(".outputMsg") task["previewPrompt"] = taskEntry.querySelector(".preview-prompt") task["progressBar"] = taskEntry.querySelector(".progress-bar") task["stopTask"] = taskEntry.querySelector(".stopTask") task["stopTask"].addEventListener("click", (e) => { e.stopPropagation() if (task["isProcessing"]) { shiftOrConfirm(e, "Stop this task?", async function(e) { if (task.batchesDone <= 0 || !task.isProcessing) { removeTask(taskEntry) } abortTask(task) }) } else { removeTask(taskEntry) } }) task["useSettings"] = taskEntry.querySelector(".useSettings") task["useSettings"].addEventListener("click", function(e) { e.stopPropagation() restoreTaskToUI(task, TASK_REQ_NO_EXPORT) }) task.isProcessing = true taskEntry = imagePreviewContent.insertBefore(taskEntry, previewTools.nextSibling) htmlTaskMap.set(taskEntry, task) task.previewPrompt.innerText = task.reqBody.prompt if (task.previewPrompt.innerText.trim() === "") { task.previewPrompt.innerHTML = " " // allows the results to be collapsed } return taskEntry.id } function getCurrentUserRequest() { const numOutputsTotal = parseInt(numOutputsTotalField.value) const numOutputsParallel = parseInt(numOutputsParallelField.value) const seed = randomSeedField.checked ? Math.floor(Math.random() * (2 ** 32 - 1)) : parseInt(seedField.value) const newTask = { batchesDone: 0, numOutputsTotal: numOutputsTotal, batchCount: Math.ceil(numOutputsTotal / numOutputsParallel), seed, reqBody: { seed, used_random_seed: randomSeedField.checked, negative_prompt: negativePromptField.value.trim(), num_outputs: numOutputsParallel, num_inference_steps: parseInt(numInferenceStepsField.value), guidance_scale: parseFloat(guidanceScaleField.value), width: parseInt(widthField.value), height: parseInt(heightField.value), // allow_nsfw: allowNSFWField.checked, vram_usage_level: vramUsageLevelField.value, sampler_name: samplerField.value, //render_device: undefined, // Set device affinity. Prefer this device, but wont activate. use_stable_diffusion_model: stableDiffusionModelField.value, clip_skip: clipSkipField.checked, tiling: tilingField.value, use_vae_model: vaeModelField.value, stream_progress_updates: true, stream_image_progress: numOutputsTotal > 50 ? false : streamImageProgressField.checked, show_only_filtered_image: showOnlyFilteredImageField.checked, block_nsfw: blockNSFWField.checked, output_format: outputFormatField.value, output_quality: parseInt(outputQualityField.value), output_lossless: outputLosslessField.checked, metadata_output_format: metadataOutputFormatField.value, original_prompt: promptField.value, active_tags: activeTags.map((x) => x.name), inactive_tags: activeTags.filter((tag) => tag.inactive === true).map((x) => x.name), }, } if (IMAGE_REGEX.test(initImagePreview.src)) { newTask.reqBody.init_image = initImagePreview.src newTask.reqBody.prompt_strength = parseFloat(promptStrengthField.value) // if (IMAGE_REGEX.test(maskImagePreview.src)) { // newTask.reqBody.mask = maskImagePreview.src // } if (maskSetting.checked) { newTask.reqBody.mask = imageInpainter.getImg() } newTask.reqBody.preserve_init_image_color_profile = applyColorCorrectionField.checked if (!testDiffusers.checked) { newTask.reqBody.sampler_name = "ddim" } } if (saveToDiskField.checked && diskPathField.value.trim() !== "") { newTask.reqBody.save_to_disk_path = diskPathField.value.trim() } if (useFaceCorrectionField.checked) { newTask.reqBody.use_face_correction = gfpganModelField.value if (gfpganModelField.value.includes("codeformer")) { newTask.reqBody.codeformer_upscale_faces = document.querySelector("#codeformer_upscale_faces").checked newTask.reqBody.codeformer_fidelity = 1 - parseFloat(codeformerFidelityField.value) } } if (useUpscalingField.checked) { newTask.reqBody.use_upscale = upscaleModelField.value newTask.reqBody.upscale_amount = upscaleAmountField.value if (upscaleModelField.value === "latent_upscaler") { newTask.reqBody.upscale_amount = "2" newTask.reqBody.latent_upscaler_steps = latentUpscalerStepsField.value } } if (hypernetworkModelField.value) { newTask.reqBody.use_hypernetwork_model = hypernetworkModelField.value newTask.reqBody.hypernetwork_strength = parseFloat(hypernetworkStrengthField.value) } if (testDiffusers.checked && loraModelField.value) { newTask.reqBody.use_lora_model = loraModelField.value newTask.reqBody.lora_alpha = parseFloat(loraAlphaField.value) } return newTask } function getPrompts(prompts) { if (typeof prompts === "undefined") { prompts = promptField.value } if (prompts.trim() === "" && activeTags.length === 0) { return [""] } let promptsToMake = [] if (prompts.trim() !== "") { prompts = prompts.split("\n") prompts = prompts.map((prompt) => prompt.trim()) prompts = prompts.filter((prompt) => prompt !== "") promptsToMake = applyPermuteOperator(prompts) promptsToMake = applySetOperator(promptsToMake) } const newTags = activeTags.filter((tag) => tag.inactive === undefined || tag.inactive === false) if (newTags.length > 0) { const promptTags = newTags.map((x) => x.name).join(", ") if (promptsToMake.length > 0) { promptsToMake = promptsToMake.map((prompt) => `${prompt}, ${promptTags}`) } else { promptsToMake.push(promptTags) } } promptsToMake = applyPermuteOperator(promptsToMake) promptsToMake = applySetOperator(promptsToMake) PLUGINS["GET_PROMPTS_HOOK"].forEach((fn) => { promptsToMake = fn(promptsToMake) }) return promptsToMake } function getPromptsNumber(prompts) { if (typeof prompts === "undefined") { prompts = promptField.value } if (prompts.trim() === "" && activeTags.length === 0) { return [""] } let promptsToMake = [] let numberOfPrompts = 0 if (prompts.trim() !== "") { // this needs to stay sort of the same, as the prompts have to be passed through to the other functions prompts = prompts.split("\n") prompts = prompts.map((prompt) => prompt.trim()) prompts = prompts.filter((prompt) => prompt !== "") // estimate number of prompts let estimatedNumberOfPrompts = 0 prompts.forEach((prompt) => { estimatedNumberOfPrompts += (prompt.match(/{[^}]*}/g) || []).map((e) => (e.match(/,/g) || []).length + 1).reduce( (p,a) => p*a, 1) * (2**(prompt.match(/\|/g) || []).length) }) if (estimatedNumberOfPrompts >= 10000) { return 10000 } promptsToMake = applySetOperator(prompts) // switched those around as Set grows in a linear fashion and permute in 2^n, and one has to be computed for the other to be calculated numberOfPrompts = applyPermuteOperatorNumber(promptsToMake) } const newTags = activeTags.filter((tag) => tag.inactive === undefined || tag.inactive === false) if (newTags.length > 0) { const promptTags = newTags.map((x) => x.name).join(", ") if (numberOfPrompts > 0) { // promptsToMake = promptsToMake.map((prompt) => `${prompt}, ${promptTags}`) // nothing changes, as all prompts just get modified } else { // promptsToMake.push(promptTags) numberOfPrompts = 1 } } // Why is this applied twice? It does not do anything here, as everything should have already been done earlier // promptsToMake = applyPermuteOperator(promptsToMake) // promptsToMake = applySetOperator(promptsToMake) return numberOfPrompts } function applySetOperator(prompts) { let promptsToMake = [] let braceExpander = new BraceExpander() prompts.forEach((prompt) => { let expandedPrompts = braceExpander.expand(prompt) promptsToMake = promptsToMake.concat(expandedPrompts) }) return promptsToMake } function applyPermuteOperator(prompts) { // prompts is array of input, trimmed, filtered and split by \n let promptsToMake = [] prompts.forEach((prompt) => { let promptMatrix = prompt.split("|") prompt = promptMatrix.shift().trim() promptsToMake.push(prompt) promptMatrix = promptMatrix.map((p) => p.trim()) promptMatrix = promptMatrix.filter((p) => p !== "") if (promptMatrix.length > 0) { let promptPermutations = permutePrompts(prompt, promptMatrix) promptsToMake = promptsToMake.concat(promptPermutations) } }) return promptsToMake } // returns how many prompts would have to be made with the given prompts function applyPermuteOperatorNumber(prompts) { // prompts is array of input, trimmed, filtered and split by \n let numberOfPrompts = 0 prompts.forEach((prompt) => { let promptCounter = 1 let promptMatrix = prompt.split("|") promptMatrix.shift() promptMatrix = promptMatrix.map((p) => p.trim()) promptMatrix = promptMatrix.filter((p) => p !== "") if (promptMatrix.length > 0) { promptCounter *= permuteNumber(promptMatrix) } numberOfPrompts += promptCounter }) return numberOfPrompts } function permutePrompts(promptBase, promptMatrix) { let prompts = [] let permutations = permute(promptMatrix) permutations.forEach((perm) => { let prompt = promptBase if (perm.length > 0) { let promptAddition = perm.join(", ") if (promptAddition.trim() === "") { return } prompt += ", " + promptAddition } prompts.push(prompt) }) return prompts } // create a file name with embedded prompt and metadata // for easier cateloging and comparison function createFileName(prompt, seed, steps, guidance, outputFormat) { // Most important information is the prompt let underscoreName = prompt.replace(/[^a-zA-Z0-9]/g, "_") underscoreName = underscoreName.substring(0, 70) // name and the top level metadata let fileName = `${underscoreName}_S${seed}_St${steps}_G${guidance}.${outputFormat}` return fileName } async function stopAllTasks() { getUncompletedTaskEntries().forEach((taskEntry) => { const taskStatusLabel = taskEntry.querySelector(".taskStatusLabel") if (taskStatusLabel) { taskStatusLabel.style.display = "none" } const task = htmlTaskMap.get(taskEntry) if (!task) { return } abortTask(task) }) } function updateInitialText() { if (document.querySelector(".imageTaskContainer") === null) { if (undoBuffer.length > 0) { initialText.prepend(undoButton) } previewTools.classList.add("displayNone") initialText.classList.remove("displayNone") } else { initialText.classList.add("displayNone") previewTools.classList.remove("displayNone") document.querySelector("div.display-settings").prepend(undoButton) } } function removeTask(taskToRemove) { undoableRemove(taskToRemove) updateInitialText() } clearAllPreviewsBtn.addEventListener("click", (e) => { shiftOrConfirm(e, "Clear all the results and tasks in this window?", async function() { await stopAllTasks() let taskEntries = document.querySelectorAll(".imageTaskContainer") taskEntries.forEach(removeTask) }) }) /* Download images popup */ showDownloadDialogBtn.addEventListener("click", (e) => { saveAllImagesDialog.showModal() }) saveAllImagesCloseBtn.addEventListener("click", (e) => { saveAllImagesDialog.close() }) modalDialogCloseOnBackdropClick(saveAllImagesDialog) makeDialogDraggable(saveAllImagesDialog) saveAllZipToggle.addEventListener("change", (e) => { if (saveAllZipToggle.checked) { saveAllFoldersOption.classList.remove("displayNone") } else { saveAllFoldersOption.classList.add("displayNone") } }) // convert base64 to raw binary data held in a string function dataURItoBlob(dataURI) { var byteString = atob(dataURI.split(",")[1]) // separate out the mime component var mimeString = dataURI .split(",")[0] .split(":")[1] .split(";")[0] // write the bytes of the string to an ArrayBuffer var ab = new ArrayBuffer(byteString.length) // create a view into the buffer var ia = new Uint8Array(ab) // set the bytes of the buffer to the correct values for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i) } // write the ArrayBuffer to a blob, and you're done return new Blob([ab], { type: mimeString }) } function downloadAllImages() { let i = 0 let optZIP = saveAllZipToggle.checked let optTree = optZIP && saveAllTreeToggle.checked let optJSON = saveAllJSONToggle.checked let zip = new JSZip() let folder = zip document.querySelectorAll(".imageTaskContainer").forEach((container) => { if (optTree) { let name = ++i + "-" + container .querySelector(".preview-prompt") .textContent.replace(/[^a-zA-Z0-9]/g, "_") .substring(0, 25) folder = zip.folder(name) } container.querySelectorAll(".imgContainer img").forEach((img) => { let imgItem = img.closest(".imgItem") if (imgItem.style.display === "none") { return } let req = imageRequest[img.dataset["imagecounter"]] if (optZIP) { let suffix = img.dataset["imagecounter"] + "." + req["output_format"] folder.file(getDownloadFilename(img, suffix), dataURItoBlob(img.src)) if (optJSON) { suffix = img.dataset["imagecounter"] + ".json" folder.file(getDownloadFilename(img, suffix), JSON.stringify(req, null, 2)) } } else { setTimeout(() => { imgItem.querySelector(".download-img").click() }, i * 200) i = i + 1 if (optJSON) { setTimeout(() => { imgItem.querySelector(".download-json").click() }, i * 200) i = i + 1 } } }) }) if (optZIP) { let now = Date.now() .toString(36) .toUpperCase() zip.generateAsync({ type: "blob" }).then(function(blob) { saveAs(blob, `EasyDiffusion-Images-${now}.zip`) }) } } saveAllImagesBtn.addEventListener("click", (e) => { downloadAllImages() }) stopImageBtn.addEventListener("click", (e) => { shiftOrConfirm(e, "Stop all the tasks?", async function(e) { await stopAllTasks() }) }) widthField.addEventListener("change", onDimensionChange) heightField.addEventListener("change", onDimensionChange) function renameMakeImageButton() { let totalImages = Math.max(parseInt(numOutputsTotalField.value), parseInt(numOutputsParallelField.value)) * getPromptsNumber() let imageLabel = "Image" if (totalImages > 1) { imageLabel = totalImages + " Images" } if (SD.activeTasks.size == 0) { if (totalImages >= 10000) makeImageBtn.innerText = "Make 10000+ images" else makeImageBtn.innerText = "Make " + imageLabel } else { if (totalImages >= 10000) makeImageBtn.innerText = "Enqueue 10000+ images" else makeImageBtn.innerText = "Enqueue Next " + imageLabel } } numOutputsTotalField.addEventListener("change", renameMakeImageButton) numOutputsTotalField.addEventListener("keyup", debounce(renameMakeImageButton, 300)) numOutputsParallelField.addEventListener("change", renameMakeImageButton) numOutputsParallelField.addEventListener("keyup", debounce(renameMakeImageButton, 300)) function onDimensionChange() { let widthValue = parseInt(widthField.value) let heightValue = parseInt(heightField.value) if (!initImagePreviewContainer.classList.contains("has-image")) { imageEditor.setImage(null, widthValue, heightValue) } else { imageInpainter.setImage(initImagePreview.src, widthValue, heightValue) } if (widthValue < 512 && heightValue < 512) { smallImageWarning.classList.remove("displayNone") } else { smallImageWarning.classList.add("displayNone") } } diskPathField.disabled = !saveToDiskField.checked metadataOutputFormatField.disabled = !saveToDiskField.checked gfpganModelField.disabled = !useFaceCorrectionField.checked useFaceCorrectionField.addEventListener("change", function(e) { gfpganModelField.disabled = !this.checked onFixFaceModelChange() }) function onFixFaceModelChange() { let codeformerSettings = document.querySelector("#codeformer_settings") if (gfpganModelField.value === "codeformer" && !gfpganModelField.disabled) { codeformerSettings.classList.remove("displayNone") codeformerSettings.classList.add("expandedSettingRow") } else { codeformerSettings.classList.add("displayNone") codeformerSettings.classList.remove("expandedSettingRow") } } gfpganModelField.addEventListener("change", onFixFaceModelChange) onFixFaceModelChange() upscaleModelField.disabled = !useUpscalingField.checked upscaleAmountField.disabled = !useUpscalingField.checked useUpscalingField.addEventListener("change", function(e) { upscaleModelField.disabled = !this.checked upscaleAmountField.disabled = !this.checked onUpscaleModelChange() }) function onUpscaleModelChange() { let upscale4x = document.querySelector("#upscale_amount_4x") if (upscaleModelField.value === "latent_upscaler" && !upscaleModelField.disabled) { upscale4x.disabled = true upscaleAmountField.value = "2" latentUpscalerSettings.classList.remove("displayNone") latentUpscalerSettings.classList.add("expandedSettingRow") } else { upscale4x.disabled = false latentUpscalerSettings.classList.add("displayNone") latentUpscalerSettings.classList.remove("expandedSettingRow") } } upscaleModelField.addEventListener("change", onUpscaleModelChange) onUpscaleModelChange() makeImageBtn.addEventListener("click", makeImage) document.onkeydown = function(e) { if (e.ctrlKey && e.code === "Enter") { makeImage() e.preventDefault() } } /********************* CodeFormer Fidelity **************************/ function updateCodeformerFidelity() { codeformerFidelityField.value = codeformerFidelitySlider.value / 10 codeformerFidelityField.dispatchEvent(new Event("change")) } function updateCodeformerFidelitySlider() { if (codeformerFidelityField.value < 0) { codeformerFidelityField.value = 0 } else if (codeformerFidelityField.value > 1) { codeformerFidelityField.value = 1 } codeformerFidelitySlider.value = codeformerFidelityField.value * 10 codeformerFidelitySlider.dispatchEvent(new Event("change")) } codeformerFidelitySlider.addEventListener("input", updateCodeformerFidelity) codeformerFidelityField.addEventListener("input", updateCodeformerFidelitySlider) updateCodeformerFidelity() /********************* Latent Upscaler Steps **************************/ function updateLatentUpscalerSteps() { latentUpscalerStepsField.value = latentUpscalerStepsSlider.value latentUpscalerStepsField.dispatchEvent(new Event("change")) } function updateLatentUpscalerStepsSlider() { if (latentUpscalerStepsField.value < 1) { latentUpscalerStepsField.value = 1 } else if (latentUpscalerStepsField.value > 50) { latentUpscalerStepsField.value = 50 } latentUpscalerStepsSlider.value = latentUpscalerStepsField.value latentUpscalerStepsSlider.dispatchEvent(new Event("change")) } latentUpscalerStepsSlider.addEventListener("input", updateLatentUpscalerSteps) latentUpscalerStepsField.addEventListener("input", updateLatentUpscalerStepsSlider) updateLatentUpscalerSteps() /********************* Guidance **************************/ function updateGuidanceScale() { guidanceScaleField.value = guidanceScaleSlider.value / 10 guidanceScaleField.dispatchEvent(new Event("change")) } function updateGuidanceScaleSlider() { if (guidanceScaleField.value < 0) { guidanceScaleField.value = 0 } else if (guidanceScaleField.value > 50) { guidanceScaleField.value = 50 } guidanceScaleSlider.value = guidanceScaleField.value * 10 guidanceScaleSlider.dispatchEvent(new Event("change")) } guidanceScaleSlider.addEventListener("input", updateGuidanceScale) guidanceScaleField.addEventListener("input", updateGuidanceScaleSlider) updateGuidanceScale() /********************* Prompt Strength *******************/ function updatePromptStrength() { promptStrengthField.value = promptStrengthSlider.value / 100 promptStrengthField.dispatchEvent(new Event("change")) } function updatePromptStrengthSlider() { if (promptStrengthField.value < 0) { promptStrengthField.value = 0 } else if (promptStrengthField.value > 0.99) { promptStrengthField.value = 0.99 } promptStrengthSlider.value = promptStrengthField.value * 100 promptStrengthSlider.dispatchEvent(new Event("change")) } promptStrengthSlider.addEventListener("input", updatePromptStrength) promptStrengthField.addEventListener("input", updatePromptStrengthSlider) updatePromptStrength() /********************* Hypernetwork Strength **********************/ function updateHypernetworkStrength() { hypernetworkStrengthField.value = hypernetworkStrengthSlider.value / 100 hypernetworkStrengthField.dispatchEvent(new Event("change")) } function updateHypernetworkStrengthSlider() { if (hypernetworkStrengthField.value < 0) { hypernetworkStrengthField.value = 0 } else if (hypernetworkStrengthField.value > 0.99) { hypernetworkStrengthField.value = 0.99 } hypernetworkStrengthSlider.value = hypernetworkStrengthField.value * 100 hypernetworkStrengthSlider.dispatchEvent(new Event("change")) } hypernetworkStrengthSlider.addEventListener("input", updateHypernetworkStrength) hypernetworkStrengthField.addEventListener("input", updateHypernetworkStrengthSlider) updateHypernetworkStrength() function updateHypernetworkStrengthContainer() { document.querySelector("#hypernetwork_strength_container").style.display = hypernetworkModelField.value === "" ? "none" : "" } hypernetworkModelField.addEventListener("change", updateHypernetworkStrengthContainer) updateHypernetworkStrengthContainer() /********************* LoRA alpha **********************/ function updateLoraAlpha() { loraAlphaField.value = loraAlphaSlider.value / 100 loraAlphaField.dispatchEvent(new Event("change")) } function updateLoraAlphaSlider() { if (loraAlphaField.value < -2) { loraAlphaField.value = -2 } else if (loraAlphaField.value > 2) { loraAlphaField.value = 2 } loraAlphaSlider.value = loraAlphaField.value * 100 loraAlphaSlider.dispatchEvent(new Event("change")) } loraAlphaSlider.addEventListener("input", updateLoraAlpha) loraAlphaField.addEventListener("input", updateLoraAlphaSlider) updateLoraAlpha() function updateLoraAlphaContainer() { document.querySelector("#lora_alpha_container").style.display = loraModelField.value === "" ? "none" : "" } loraModelField.addEventListener("change", updateLoraAlphaContainer) updateLoraAlphaContainer() /********************* JPEG/WEBP Quality **********************/ function updateOutputQuality() { outputQualityField.value = 0 | outputQualitySlider.value outputQualityField.dispatchEvent(new Event("change")) } function updateOutputQualitySlider() { if (outputQualityField.value < 10) { outputQualityField.value = 10 } else if (outputQualityField.value > 95) { outputQualityField.value = 95 } outputQualitySlider.value = 0 | outputQualityField.value outputQualitySlider.dispatchEvent(new Event("change")) } outputQualitySlider.addEventListener("input", updateOutputQuality) outputQualityField.addEventListener("input", debounce(updateOutputQualitySlider, 1500)) updateOutputQuality() function updateOutputQualityVisibility() { if (outputFormatField.value === "webp") { outputLosslessContainer.classList.remove("displayNone") if (outputLosslessField.checked) { outputQualityRow.classList.add("displayNone") } else { outputQualityRow.classList.remove("displayNone") } } else if (outputFormatField.value === "png") { outputQualityRow.classList.add("displayNone") outputLosslessContainer.classList.add("displayNone") } else { outputQualityRow.classList.remove("displayNone") outputLosslessContainer.classList.add("displayNone") } } outputFormatField.addEventListener("change", updateOutputQualityVisibility) outputLosslessField.addEventListener("change", updateOutputQualityVisibility) /********************* Zoom Slider **********************/ thumbnailSizeField.addEventListener("change", () => { ;(function(s) { for (var j = 0; j < document.styleSheets.length; j++) { let cssSheet = document.styleSheets[j] for (var i = 0; i < cssSheet.cssRules.length; i++) { var rule = cssSheet.cssRules[i] if (rule.selectorText == "div.img-preview img") { rule.style["max-height"] = s + "vh" rule.style["max-width"] = s + "vw" return } } } })(thumbnailSizeField.value) }) function onAutoScrollUpdate() { if (autoScroll.checked) { autoscrollBtn.classList.add("pressed") } else { autoscrollBtn.classList.remove("pressed") } autoscrollBtn.querySelector(".state").innerHTML = autoScroll.checked ? "ON" : "OFF" } autoscrollBtn.addEventListener("click", function() { autoScroll.checked = !autoScroll.checked autoScroll.dispatchEvent(new Event("change")) onAutoScrollUpdate() }) autoScroll.addEventListener("change", onAutoScrollUpdate) function checkRandomSeed() { if (randomSeedField.checked) { seedField.disabled = true //seedField.value = "0" // This causes the seed to be lost if the user changes their mind after toggling the checkbox } else { seedField.disabled = false } } randomSeedField.addEventListener("input", checkRandomSeed) checkRandomSeed() function loadImg2ImgFromFile() { if (initImageSelector.files.length === 0) { return } let reader = new FileReader() let file = initImageSelector.files[0] reader.addEventListener("load", function(event) { initImagePreview.src = reader.result }) if (file) { reader.readAsDataURL(file) } } initImageSelector.addEventListener("change", loadImg2ImgFromFile) loadImg2ImgFromFile() function img2imgLoad() { promptStrengthContainer.style.display = "table-row" if (!testDiffusers.checked) { samplerSelectionContainer.style.display = "none" } initImagePreviewContainer.classList.add("has-image") colorCorrectionSetting.style.display = "" initImageSizeBox.textContent = initImagePreview.naturalWidth + " x " + initImagePreview.naturalHeight imageEditor.setImage(this.src, initImagePreview.naturalWidth, initImagePreview.naturalHeight) imageInpainter.setImage(this.src, parseInt(widthField.value), parseInt(heightField.value)) } function img2imgUnload() { initImageSelector.value = null initImagePreview.src = "" maskSetting.checked = false promptStrengthContainer.style.display = "none" if (!testDiffusers.checked) { samplerSelectionContainer.style.display = "" } initImagePreviewContainer.classList.remove("has-image") colorCorrectionSetting.style.display = "none" imageEditor.setImage(null, parseInt(widthField.value), parseInt(heightField.value)) } initImagePreview.addEventListener("load", img2imgLoad) initImageClearBtn.addEventListener("click", img2imgUnload) maskSetting.addEventListener("click", function() { onDimensionChange() }) promptsFromFileBtn.addEventListener("click", function() { promptsFromFileSelector.click() }) promptsFromFileSelector.addEventListener("change", async function() { if (promptsFromFileSelector.files.length === 0) { return } let reader = new FileReader() let file = promptsFromFileSelector.files[0] reader.addEventListener("load", async function() { await parseContent(reader.result) }) if (file) { reader.readAsText(file) } }) /* setup popup handlers */ document.querySelectorAll(".popup").forEach((popup) => { popup.addEventListener("click", (event) => { if (event.target == popup) { popup.classList.remove("active") } }) var closeButton = popup.querySelector(".close-button") if (closeButton) { closeButton.addEventListener("click", () => { popup.classList.remove("active") }) } }) var tabElements = [] function selectTab(tab_id) { let tabInfo = tabElements.find((t) => t.tab.id == tab_id) if (!tabInfo.tab.classList.contains("active")) { tabElements.forEach((info) => { if (info.tab.classList.contains("active") && info.tab.parentNode === tabInfo.tab.parentNode) { info.tab.classList.toggle("active") info.content.classList.toggle("active") } }) tabInfo.tab.classList.toggle("active") tabInfo.content.classList.toggle("active") } document.dispatchEvent(new CustomEvent("tabClick", { detail: tabInfo })) } function linkTabContents(tab) { var name = tab.id.replace("tab-", "") var content = document.getElementById(`tab-content-${name}`) tabElements.push({ name: name, tab: tab, content: content, }) tab.addEventListener("click", (event) => selectTab(tab.id)) } function isTabActive(tab) { return tab.classList.contains("active") } let pauseClient = false function resumeClient() { if (pauseClient) { document.body.classList.remove("wait-pause") document.body.classList.add("pause") } return new Promise((resolve) => { let playbuttonclick = function() { resumeBtn.removeEventListener("click", playbuttonclick) resolve("resolved") } resumeBtn.addEventListener("click", playbuttonclick) }) } function splashScreen(force = false) { const splashVersion = splashScreenPopup.dataset['version'] const lastSplash = localStorage.getItem("lastSplashScreenVersion") || 0 if (testDiffusers.checked) { if (force || lastSplash < splashVersion) { splashScreenPopup.classList.add("active") localStorage.setItem("lastSplashScreenVersion", splashVersion) } } } document.getElementById("logo_img").addEventListener("click", (e) => { splashScreen(true) }) promptField.addEventListener("input", debounce(renameMakeImageButton, 1000)) pauseBtn.addEventListener("click", function() { pauseClient = true pauseBtn.style.display = "none" resumeBtn.style.display = "inline" document.body.classList.add("wait-pause") }) resumeBtn.addEventListener("click", function() { pauseClient = false resumeBtn.style.display = "none" pauseBtn.style.display = "inline" document.body.classList.remove("pause") document.body.classList.remove("wait-pause") }) function tunnelUpdate(event) { if ("cloudflare" in event) { document.getElementById("cloudflare-off").classList.add("displayNone") document.getElementById("cloudflare-on").classList.remove("displayNone") cloudflareAddressField.innerHTML = event.cloudflare document.getElementById("toggle-cloudflare-tunnel").innerHTML = "Stop" } else { document.getElementById("cloudflare-on").classList.add("displayNone") document.getElementById("cloudflare-off").classList.remove("displayNone") document.getElementById("toggle-cloudflare-tunnel").innerHTML = "Start" } } document.getElementById("toggle-cloudflare-tunnel").addEventListener("click", async function() { let command = "stop" if (document.getElementById("toggle-cloudflare-tunnel").innerHTML == "Start") { command = "start" } showToast(`Cloudflare tunnel ${command} initiated. Please wait.`) let res = await fetch("/tunnel/cloudflare/" + command, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({}), }) res = await res.json() console.log(`Cloudflare tunnel ${command} result:`, res) }) /* Pause function */ document.querySelectorAll(".tab").forEach(linkTabContents) window.addEventListener("beforeunload", function(e) { const msg = "Unsaved pictures will be lost!" let elementList = document.getElementsByClassName("imageTaskContainer") if (elementList.length != 0) { e.preventDefault() ;(e || window.event).returnValue = msg return msg } else { return true } }) createCollapsibles() prettifyInputs(document) // set the textbox as focused on start promptField.focus() promptField.selectionStart = promptField.value.length