"
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 if (
msg.includes("RuntimeError: output with shape [320, 320] doesn't match the broadcast shape")
) {
msg += `
Reason: You tried to use a LORA that was trained for a different Stable Diffusion model version!
Suggestions:
Try to use a different model or a different LORA.`
} else if (msg.includes("'ModuleList' object has no attribute '1'")) {
msg += `
Save it in the same directory as the SDXL model file
Rename the config file so that it matches the filename of the model, with the extension of the model file replaced by yaml.
For example, if the model file is called FantasySDXL_v2.safetensors, the config file must be called FantasySDXL_v2.yaml.
")
}
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 += `
`
}
if (task.reqBody.control_image !== undefined) {
let h = 80
let w = ((task.reqBody.width * h) / task.reqBody.height) >> 0
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 || task.reqBody.control_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)
let numOutputsParallel = parseInt(numOutputsParallelField.value)
const seed = randomSeedField.checked ? Math.floor(Math.random() * (2 ** 32 - 1)) : parseInt(seedField.value)
// if (
// testDiffusers.checked &&
// document.getElementById("toggle-tensorrt-install").innerHTML == "Uninstall" &&
// document.querySelector("#convert_to_tensorrt").checked
// ) {
// // TRT enabled
// numOutputsParallel = 1 // force 1 parallel
// }
// clamp to multiple of 8
let width = parseInt(widthField.value)
let height = parseInt(heightField.value)
width = width - (width % IMAGE_STEP_SIZE)
height = height - (height % IMAGE_STEP_SIZE)
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: width,
height: height,
// 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.strict_mask_border = strictMaskBorderField.checked
}
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) {
let [modelNames, modelStrengths] = getModelInfo(loraModels)
if (modelNames.length > 0) {
modelNames = modelNames.length == 1 ? modelNames[0] : modelNames
modelStrengths = modelStrengths.length == 1 ? modelStrengths[0] : modelStrengths
newTask.reqBody.use_lora_model = modelNames
newTask.reqBody.lora_alpha = modelStrengths
}
}
if (testDiffusers.checked && document.getElementById("toggle-tensorrt-install").innerHTML == "Uninstall") {
// TRT is installed
newTask.reqBody.convert_to_tensorrt = document.querySelector("#convert_to_tensorrt").checked
let trtBuildConfig = {
batch_size_range: [
parseInt(document.querySelector("#trt-build-min-batch").value),
parseInt(document.querySelector("#trt-build-max-batch").value),
],
dimensions_range: [],
}
let sizes = [512, 768, 1024, 1280, 1536]
sizes.forEach((i) => {
let el = document.querySelector("#trt-build-res-" + i)
if (el.checked) {
trtBuildConfig["dimensions_range"].push([i, i + 256])
}
})
newTask.reqBody.trt_build_config = trtBuildConfig
}
if (controlnetModelField.value !== "" && IMAGE_REGEX.test(controlImagePreview.src)) {
newTask.reqBody.use_controlnet_model = controlnetModelField.value
newTask.reqBody.control_image = controlImagePreview.src
if (controlImageFilterField.value !== "") {
newTask.reqBody.control_filter_to_apply = controlImageFilterField.value
}
}
return newTask
}
function setEmbeddings(task) {
let prompt = task.reqBody.prompt.toLowerCase()
let negativePrompt = task.reqBody.negative_prompt.toLowerCase()
let overallPrompt = (prompt + " " + negativePrompt).replaceAll(",", "").split(" ")
let embeddingsTree = modelsOptions["embeddings"]
let embeddings = []
function extract(entries, basePath = "") {
entries.forEach((e) => {
if (Array.isArray(e)) {
let path = basePath === "" ? basePath + e[0] : basePath + "/" + e[0]
extract(e[1], path)
} else {
let path = basePath === "" ? basePath + e : basePath + "/" + e
embeddings.push([e.toLowerCase().replace(" ", "_"), path])
}
})
}
extract(embeddingsTree)
let embeddingPaths = []
embeddings.forEach((e) => {
let token = e[0]
let path = e[1]
if (overallPrompt.includes(token)) {
embeddingPaths.push(path)
}
})
if (embeddingPaths.length > 0) {
task.reqBody.use_embeddings_model = embeddingPaths
}
}
function getModelInfo(models) {
let modelInfo = models.map((e) => [e[0].value, e[1].value])
modelInfo = modelInfo.filter((e) => e[0].trim() !== "")
modelInfo = modelInfo.map((e) => [e[0], parseFloat(e[1])])
let modelNames = modelInfo.map((e) => e[0])
let modelStrengths = modelInfo.map((e) => e[1])
return [modelNames, modelStrengths]
}
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()
function onControlnetModelChange() {
let configBox = document.querySelector("#controlnet_config")
if (IMAGE_REGEX.test(controlImagePreview.src)) {
configBox.classList.remove("displayNone")
controlImageContainer.classList.remove("displayNone")
} else {
configBox.classList.add("displayNone")
controlImageContainer.classList.add("displayNone")
}
}
controlImagePreview.addEventListener("load", onControlnetModelChange)
controlImagePreview.addEventListener("unload", onControlnetModelChange)
onControlnetModelChange()
function onControlImageFilterChange() {
let filterId = controlImageFilterField.value
if (filterId.includes("openpose")) {
controlnetModelField.value = "control_v11p_sd15_openpose"
} else if (filterId === "canny") {
controlnetModelField.value = "control_v11p_sd15_canny"
} else if (filterId === "mlsd") {
controlnetModelField.value = "control_v11p_sd15_mlsd"
} else if (filterId === "mlsd") {
controlnetModelField.value = "control_v11p_sd15_mlsd"
} else if (filterId.includes("scribble")) {
controlnetModelField.value = "control_v11p_sd15_scribble"
} else if (filterId.includes("softedge")) {
controlnetModelField.value = "control_v11p_sd15_softedge"
} else if (filterId === "normal_bae") {
controlnetModelField.value = "control_v11p_sd15_normalbae"
} else if (filterId.includes("depth")) {
controlnetModelField.value = "control_v11f1p_sd15_depth"
} else if (filterId === "lineart_anime") {
controlnetModelField.value = "control_v11p_sd15s2_lineart_anime"
} else if (filterId.includes("lineart")) {
controlnetModelField.value = "control_v11p_sd15_lineart"
} else if (filterId === "shuffle") {
controlnetModelField.value = "control_v11e_sd15_shuffle"
} else if (filterId === "segment") {
controlnetModelField.value = "control_v11p_sd15_seg"
}
}
controlImageFilterField.addEventListener("change", onControlImageFilterChange)
onControlImageFilterChange()
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()
/********************* 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()
// warning: the core plugin `image-editor-improvements.js:172` replaces loadImg2ImgFromFile() with a custom version
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 = ""
strictMaskBorderSetting.style.display = maskSetting.checked ? "" : "none"
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"
strictMaskBorderSetting.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()
})
maskSetting.addEventListener("change", function() {
strictMaskBorderSetting.style.display = this.checked ? "" : "none"
})
promptsFromFileBtn.addEventListener("click", function() {
promptsFromFileSelector.click()
})
function loadControlnetImageFromFile() {
if (controlImageSelector.files.length === 0) {
return
}
let reader = new FileReader()
let file = controlImageSelector.files[0]
reader.addEventListener("load", function(event) {
controlImagePreview.src = reader.result
})
if (file) {
reader.readAsDataURL(file)
}
}
controlImageSelector.addEventListener("change", loadControlnetImageFromFile)
function controlImageLoad() {
let w = controlImagePreview.naturalWidth
let h = controlImagePreview.naturalHeight
w = w - (w % IMAGE_STEP_SIZE)
h = h - (h % IMAGE_STEP_SIZE)
addImageSizeOption(w)
addImageSizeOption(h)
widthField.value = w
heightField.value = h
widthField.dispatchEvent(new Event("change"))
heightField.dispatchEvent(new Event("change"))
}
controlImagePreview.addEventListener("load", controlImageLoad)
function controlImageUnload() {
controlImageSelector.value = null
controlImagePreview.src = ""
controlImagePreview.dispatchEvent(new Event("unload"))
}
controlImageClearBtn.addEventListener("click", controlImageUnload)
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 onPing(event) {
tunnelUpdate(event)
packagesUpdate(event)
}
function tunnelUpdate(event) {
if ("cloudflare" in event) {
document.getElementById("cloudflare-off").classList.add("displayNone")
document.getElementById("cloudflare-on").classList.remove("displayNone")
cloudflareAddressField.value = 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"
}
}
function packagesUpdate(event) {
let trtBtn = document.getElementById("toggle-tensorrt-install")
let trtInstalled = "packages_installed" in event && "tensorrt" in event["packages_installed"]
if ("packages_installing" in event && event["packages_installing"].includes("tensorrt")) {
trtBtn.innerHTML = "Installing.."
trtBtn.disabled = true
} else {
trtBtn.innerHTML = trtInstalled ? "Uninstall" : "Install"
trtBtn.disabled = false
}
if (document.getElementById("toggle-tensorrt-install").innerHTML == "Uninstall") {
document.querySelector("#enable_trt_config").classList.remove("displayNone")
document.querySelector("#trt-build-config").classList.remove("displayNone")
}
}
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)
})
document.getElementById("toggle-tensorrt-install").addEventListener("click", function(e) {
if (this.disabled === true) {
return
}
let command = this.innerHTML.toLowerCase()
let self = this
shiftOrConfirm(
e,
"Are you sure you want to " + command + " TensorRT?",
async function() {
showToast(`TensorRT ${command} started. Please wait.`)
self.disabled = true
if (command === "install") {
self.innerHTML = "Installing.."
} else if (command === "uninstall") {
self.innerHTML = "Uninstalling.."
}
if (command === "installing..") {
alert("Already installing TensorRT!")
return
}
if (command !== "install" && command !== "uninstall") {
return
}
let res = await fetch("/package/tensorrt", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
command: command,
}),
})
res = await res.json()
self.disabled = false
if (res.status === "OK") {
alert("TensorRT " + command + "ed successfully!")
self.innerHTML = command === "install" ? "Uninstall" : "Install"
} else if (res.status_code === 500) {
alert("TensorselfRT failed to " + command + ": " + res.detail)
self.innerHTML = command === "install" ? "Install" : "Uninstall"
}
console.log(`Package ${command} result:`, res)
},
false
)
})
/* Embeddings */
function updateEmbeddingsList(filter = "") {
function html(model, iconlist = [], prefix = "", filter = "") {
filter = filter.toLowerCase()
let toplevel = document.createElement("div")
let folders = document.createElement("div")
let embIcon = Object.assign(
{},
...iconlist.map((x) => ({
[x
.toLowerCase()
.split(".")
.slice(0, -1)
.join(".")]: x,
}))
)
let profileName = profileNameField.value
model?.forEach((m) => {
if (typeof m == "string") {
let token = m.toLowerCase()
if (token.search(filter) != -1) {
let button
// if (iconlist.length==0) {
// button = document.createElement("button")
// button.innerText = m
// } else {
let img = "/media/images/noimg.png"
if (token in embIcon) {
img = `/bucket/${profileName}/embeddings/${embIcon[token]}`
}
button = createModifierCard(m, [img, img], true)
// }
button.dataset["embedding"] = m
button.addEventListener("click", onButtonClick)
toplevel.appendChild(button)
}
} else {
let subdir = html(m[1], iconlist, prefix + m[0] + "/", filter)
if (typeof subdir == "object") {
let div1 = document.createElement("div")
let div2 = document.createElement("div")
div1.classList.add("collapsible-content")
div1.classList.add("embedding-category")
div1.appendChild(subdir)
div2.replaceChildren(htmlToElement(`
${prefix}${m[0]}
`), div1)
folders.appendChild(div2)
}
}
})
if (toplevel.children.length == 0 && folders.children.length == 0) {
// Empty folder
return ""
}
let result = document.createElement("div")
result.replaceChildren(toplevel, htmlToElement(' '), folders)
return result
}
function onButtonClick(e) {
let text = e.target.closest("[data-embedding]").dataset["embedding"]
const insertIntoNegative = e.shiftKey || positiveEmbeddingText.classList.contains("displayNone")
if (embeddingsModeField.value == "insert") {
if (insertIntoNegative) {
insertAtCursor(negativePromptField, text)
} else {
insertAtCursor(promptField, text)
}
} else {
let pad = ""
if (insertIntoNegative) {
if (!negativePromptField.value.endsWith(" ")) {
pad = " "
}
negativePromptField.value += pad + text
} else {
if (!promptField.value.endsWith(" ")) {
pad = " "
}
promptField.value += pad + text
}
}
}
// Usually the rendering of the Embeddings HTML takes less than a second. In case it takes longer, show a spinner
embeddingsList.innerHTML = `
`
let profileName = profileNameField.value
fetch(`/bucket/${profileName}/embeddings/`)
.then((response) => (response.status == 200 ? response.json() : []))
.then(async function(iconlist) {
embeddingsList.replaceChildren(html(modelsOptions.embeddings, iconlist, "", filter))
createCollapsibles(embeddingsList)
if (filter != "") {
embeddingsExpandAll()
}
resizeModifierCards(embeddingsCardSizeSelector.value)
})
}
function showEmbeddingDialog() {
updateEmbeddingsList()
embeddingsSearchBox.value = ""
embeddingsDialog.showModal()
}
embeddingsButton.addEventListener("click", () => {
positiveEmbeddingText.classList.remove("displayNone")
negativeEmbeddingText.classList.add("displayNone")
showEmbeddingDialog()
})
negativeEmbeddingsButton.addEventListener("click", () => {
positiveEmbeddingText.classList.add("displayNone")
negativeEmbeddingText.classList.remove("displayNone")
showEmbeddingDialog()
})
embeddingsDialogCloseBtn.addEventListener("click", (e) => {
embeddingsDialog.close()
})
embeddingsSearchBox.addEventListener("input", (e) => {
updateEmbeddingsList(embeddingsSearchBox.value)
})
embeddingsCardSizeSelector.addEventListener("change", (e) => {
resizeModifierCards(embeddingsCardSizeSelector.value)
})
modalDialogCloseOnBackdropClick(embeddingsDialog)
makeDialogDraggable(embeddingsDialog)
const collapseText = "Collapse Categories"
const expandText = "Expand Categories"
const collapseIconClasses = ["fa-solid", "fa-square-minus"]
const expandIconClasses = ["fa-solid", "fa-square-plus"]
function embeddingsCollapseAll() {
const btnElem = embeddingsCollapsiblesBtn
const iconElem = btnElem.querySelector(".embeddings-action-icon")
const textElem = btnElem.querySelector(".embeddings-action-text")
collapseAll("#embeddings-list .collapsible")
collapsiblesBtnState = false
collapseIconClasses.forEach((c) => iconElem.classList.remove(c))
expandIconClasses.forEach((c) => iconElem.classList.add(c))
textElem.innerText = expandText
}
function embeddingsExpandAll() {
const btnElem = embeddingsCollapsiblesBtn
const iconElem = btnElem.querySelector(".embeddings-action-icon")
const textElem = btnElem.querySelector(".embeddings-action-text")
expandAll("#embeddings-list .collapsible")
collapsiblesBtnState = true
expandIconClasses.forEach((c) => iconElem.classList.remove(c))
collapseIconClasses.forEach((c) => iconElem.classList.add(c))
textElem.innerText = collapseText
}
embeddingsCollapsiblesBtn.addEventListener("click", (e) => {
if (collapsiblesBtnState) {
embeddingsCollapseAll()
} else {
embeddingsExpandAll()
}
})
/* 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
// multi-models
let modelCount = 0
function addModelEntry(modelContainer, modelsList, modelType, defaultValue, strengthStep) {
let idx = modelCount++
let nameId = modelType + "_model_" + idx
let strengthId = modelType + "_alpha_" + idx
const modelElement = document.createElement("div")
modelElement.className = "model_entry"
modelElement.innerHTML = `
`
modelContainer.appendChild(modelElement)
let modelName = new ModelDropdown(modelElement.querySelector(".model_name"), modelType, "None")
let modelStrength = modelElement.querySelector(".model_strength")
let entry = [modelName, modelStrength, modelElement]
let removeBtn = document.createElement("button")
removeBtn.className = "remove_model_btn"
removeBtn.setAttribute("title", "Remove model")
removeBtn.innerHTML = ''
if (modelsList.length === 0) {
removeBtn.classList.add("displayNone")
}
removeBtn.addEventListener("click", function() {
let entryIdx = modelsList.indexOf(entry)
modelsList.splice(entryIdx, 1)
modelContainer.removeChild(modelElement)
})
modelElement.appendChild(removeBtn)
modelsList.push(entry)
return modelElement
}
function createLoraEntry() {
let container = document.querySelector("#lora_model_container .model_entries")
return addModelEntry(container, loraModels, "lora", 0.5, 0.02)
}
function createLoraEntries() {
let firstEntry = createLoraEntry()
let addLoraBtn = document.querySelector("#lora_model_container .add_model_entry")
addLoraBtn.addEventListener("click", () => {
createLoraEntry()
})
}
createLoraEntries()
// chrome-like spinners only on hover
// function showSpinnerOnlyOnHover(e) {
// e.addEventListener("mouseenter", () => {
// e.setAttribute("type", "number")
// })
// e.addEventListener("mouseleave", () => {
// e.removeAttribute("type")
// })
// e.removeAttribute("type")
// }
// document.querySelectorAll("input[type=number]").forEach(showSpinnerOnlyOnHover)
////////////////////////////// Image Size Widget //////////////////////////////////////////
function roundToMultiple(number, n) {
if (n == "") {
n = 1
}
return Math.round(number / n) * n
}
function addImageSizeOption(size) {
let sizes = Object.values(widthField.options).map((o) => o.value)
if (!sizes.includes(String(size))) {
sizes.push(String(size))
sizes.sort((a, b) => Number(a) - Number(b))
let option = document.createElement("option")
option.value = size
option.text = `${size}`
widthField.add(option, sizes.indexOf(String(size)))
heightField.add(option.cloneNode(true), sizes.indexOf(String(size)))
}
}
function setImageWidthHeight(w, h) {
let step = customWidthField.step
w = roundToMultiple(w, step)
h = roundToMultiple(h, step)
addImageSizeOption(w)
addImageSizeOption(h)
widthField.value = w
heightField.value = h
widthField.dispatchEvent(new Event("change"))
heightField.dispatchEvent(new Event("change"))
}
function enlargeImageSize(factor) {
let step = customWidthField.step
let w = roundToMultiple(widthField.value * factor, step)
let h = roundToMultiple(heightField.value * factor, step)
customWidthField.value = w
customHeightField.value = h
}
let recentResolutionsValues = []
;(function() {
///// Init resolutions dropdown
function makeResolutionButtons(listElement, resolutionList) {
listElement.innerHTML = ""
resolutionList.forEach((el) => {
let button = createElement("button", { style: "width: 8em;" }, "tertiaryButton", `${el.w}×${el.h}`)
button.addEventListener("click", () => {
customWidthField.value = el.w
customHeightField.value = el.h
hidePopup()
})
listElement.appendChild(button)
listElement.appendChild(document.createElement("br"))
})
}
enlargeButtons.querySelectorAll("button").forEach((button) =>
button.addEventListener("click", (e) => {
enlargeImageSize(parseFloat(button.dataset["factor"]))
hidePopup()
})
)
customWidthField.addEventListener("change", () => {
let w = customWidthField.value
customWidthField.value = roundToMultiple(w, customWidthField.step)
if (w != customWidthField.value) {
showToast(`Rounded width to the closest multiple of ${customWidthField.step}.`)
}
})
customHeightField.addEventListener("change", () => {
let h = customHeightField.value
customHeightField.value = roundToMultiple(h, customHeightField.step)
if (h != customHeightField.value) {
showToast(`Rounded height to the closest multiple of ${customHeightField.step}.`)
}
})
makeImageBtn.addEventListener("click", () => {
let w = widthField.value
let h = heightField.value
recentResolutionsValues = recentResolutionsValues.filter((el) => el.w != w || el.h != h)
recentResolutionsValues.unshift({ w: w, h: h })
recentResolutionsValues = recentResolutionsValues.slice(0, 8)
localStorage.recentResolutionsValues = JSON.stringify(recentResolutionsValues)
makeResolutionButtons(recentResolutionList, recentResolutionsValues)
})
const defaultResolutionsValues = [
{ w: 512, h: 512 },
{ w: 448, h: 640 },
{ w: 512, h: 768 },
{ w: 768, h: 512 },
{ w: 1024, h: 768 },
{ w: 768, h: 1024 },
{ w: 1024, h: 1024 },
{ w: 1920, h: 1080 },
]
let _jsonstring = localStorage.recentResolutionsValues
if (_jsonstring == undefined) {
recentResolutionsValues = defaultResolutionsValues
localStorage.recentResolutionsValues = JSON.stringify(recentResolutionsValues)
} else {
recentResolutionsValues = JSON.parse(localStorage.recentResolutionsValues)
}
makeResolutionButtons(recentResolutionList, recentResolutionsValues)
makeResolutionButtons(commonResolutionList, defaultResolutionsValues)
recentResolutionsValues.forEach((val) => {
addImageSizeOption(val.w)
addImageSizeOption(val.h)
})
function processClick(e) {
if (!recentResolutionsPopup.contains(e.target)) {
hidePopup()
}
}
function showPopup() {
customWidthField.value = widthField.value
customHeightField.value = heightField.value
recentResolutionsPopup.classList.remove("displayNone")
resizeSlider.value = 1
resizeSlider.dataset["w"] = widthField.value
resizeSlider.dataset["h"] = heightField.value
document.addEventListener("click", processClick)
}
function hidePopup() {
recentResolutionsPopup.classList.add("displayNone")
setImageWidthHeight(customWidthField.value, customHeightField.value)
document.removeEventListener("click", processClick)
}
recentResolutionsButton.addEventListener("click", (event) => {
if (recentResolutionsPopup.classList.contains("displayNone")) {
showPopup()
event.stopPropagation()
} else {
hidePopup()
}
})
resizeSlider.addEventListener("input", (e) => {
let w = parseInt(resizeSlider.dataset["w"])
let h = parseInt(resizeSlider.dataset["h"])
let factor = parseFloat(resizeSlider.value)
let step = customWidthField.step
customWidthField.value = roundToMultiple(w * factor * factor, step)
customHeightField.value = roundToMultiple(h * factor * factor, step)
})
resizeSlider.addEventListener("change", (e) => {
hidePopup()
})
swapWidthHeightButton.addEventListener("click", (event) => {
let temp = widthField.value
widthField.value = heightField.value
heightField.value = temp
})
})()