forked from extern/easydiffusion
Fast in-place upscale and face fix buttons, with an option to undo the operations
This commit is contained in:
parent
7f32c531d7
commit
6e52680fa8
@ -17,6 +17,7 @@
|
||||
<link rel="stylesheet" href="/media/css/searchable-models.css">
|
||||
<link rel="stylesheet" href="/media/css/image-modal.css">
|
||||
<link rel="stylesheet" href="/media/css/plugins.css">
|
||||
<link rel="stylesheet" href="/media/css/animations.css">
|
||||
<link rel="manifest" href="/media/manifest.webmanifest">
|
||||
<script src="/media/js/jquery-3.6.1.min.js"></script>
|
||||
<script src="/media/js/jquery-confirm.min.js"></script>
|
||||
|
68
ui/media/css/animations.css
Normal file
68
ui/media/css/animations.css
Normal file
@ -0,0 +1,68 @@
|
||||
@keyframes ldio-8f673ktaleu-1 {
|
||||
0% { transform: rotate(0deg) }
|
||||
50% { transform: rotate(-45deg) }
|
||||
100% { transform: rotate(0deg) }
|
||||
}
|
||||
@keyframes ldio-8f673ktaleu-2 {
|
||||
0% { transform: rotate(180deg) }
|
||||
50% { transform: rotate(225deg) }
|
||||
100% { transform: rotate(180deg) }
|
||||
}
|
||||
.ldio-8f673ktaleu > div:nth-child(2) {
|
||||
transform: translate(-15px,0);
|
||||
}
|
||||
.ldio-8f673ktaleu > div:nth-child(2) div {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
width: 60px;
|
||||
height: 30px;
|
||||
border-radius: 60px 60px 0 0;
|
||||
background: #f3b72e;
|
||||
animation: ldio-8f673ktaleu-1 1s linear infinite;
|
||||
transform-origin: 30px 30px
|
||||
}
|
||||
.ldio-8f673ktaleu > div:nth-child(2) div:nth-child(2) {
|
||||
animation: ldio-8f673ktaleu-2 1s linear infinite
|
||||
}
|
||||
.ldio-8f673ktaleu > div:nth-child(2) div:nth-child(3) {
|
||||
transform: rotate(-90deg);
|
||||
animation: none;
|
||||
}@keyframes ldio-8f673ktaleu-3 {
|
||||
0% { transform: translate(95px,0); opacity: 0 }
|
||||
20% { opacity: 1 }
|
||||
100% { transform: translate(35px,0); opacity: 1 }
|
||||
}
|
||||
.ldio-8f673ktaleu > div:nth-child(1) {
|
||||
display: block;
|
||||
}
|
||||
.ldio-8f673ktaleu > div:nth-child(1) div {
|
||||
position: absolute;
|
||||
top: 46px;
|
||||
left: -4px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #3869c5;
|
||||
animation: ldio-8f673ktaleu-3 1s linear infinite
|
||||
}
|
||||
.ldio-8f673ktaleu > div:nth-child(1) div:nth-child(1) { animation-delay: -0.67s }
|
||||
.ldio-8f673ktaleu > div:nth-child(1) div:nth-child(2) { animation-delay: -0.33s }
|
||||
.ldio-8f673ktaleu > div:nth-child(1) div:nth-child(3) { animation-delay: 0s }
|
||||
.loadingio-spinner-bean-eater-x0y3u8qky4n {
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
background: none;
|
||||
}
|
||||
.ldio-8f673ktaleu {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
transform: translateZ(0) scale(0.58);
|
||||
backface-visibility: hidden;
|
||||
transform-origin: 0 0; /* see note above */
|
||||
}
|
||||
.ldio-8f673ktaleu div { box-sizing: content-box; }
|
||||
/* generated by https://loading.io/ */
|
@ -1753,3 +1753,20 @@ body.wait-pause {
|
||||
content: "Please restart Easy Diffusion!";
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
.imgContainer .spinner {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
background: var(--background-color3);
|
||||
opacity: 0.8;
|
||||
border-radius: 5px;
|
||||
padding: 4pt;
|
||||
}
|
||||
.imgContainer .spinnerStatus {
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
@ -1056,11 +1056,26 @@
|
||||
* @memberof Task
|
||||
*/
|
||||
async post(timeout = -1) {
|
||||
let res = await super.post("/filter", timeout)
|
||||
//this._setId(jsonResponse.task)
|
||||
let jsonResponse = await super.post("/filter", timeout)
|
||||
if (typeof jsonResponse?.task !== "number") {
|
||||
console.warn("Endpoint error response: ", jsonResponse)
|
||||
const event = Object.assign({ task: this }, jsonResponse)
|
||||
await eventSource.fireEvent(EVENT_UNEXPECTED_RESPONSE, event)
|
||||
if ("continueWith" in event) {
|
||||
jsonResponse = await Promise.resolve(event.continueWith)
|
||||
}
|
||||
if (typeof jsonResponse?.task !== "number") {
|
||||
const err = new Error(jsonResponse?.detail || "Endpoint response does not contains a task ID.")
|
||||
this.abort(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
this._setId(jsonResponse.task)
|
||||
if (jsonResponse.stream) {
|
||||
this.streamUrl = jsonResponse.stream
|
||||
}
|
||||
this._setStatus(TaskStatus.waiting)
|
||||
|
||||
return res
|
||||
return jsonResponse
|
||||
}
|
||||
checkReqBody() {}
|
||||
enqueue(progressCallback) {
|
||||
@ -1087,6 +1102,51 @@
|
||||
yield progressCallback?.call(this, { detail: e.message })
|
||||
throw e
|
||||
}
|
||||
|
||||
try {
|
||||
// Wait for task to start on server.
|
||||
yield this.waitUntil({
|
||||
callback: function() {
|
||||
return progressCallback?.call(this, {})
|
||||
},
|
||||
status: TaskStatus.processing,
|
||||
})
|
||||
} catch (e) {
|
||||
this.abort(err)
|
||||
throw e
|
||||
}
|
||||
|
||||
// Task started!
|
||||
// Open the reader.
|
||||
const reader = this.reader
|
||||
const task = this
|
||||
reader.onError = function(response) {
|
||||
if (progressCallback) {
|
||||
task.abort(new Error(response.statusText))
|
||||
return progressCallback.call(task, { response, reader })
|
||||
}
|
||||
return Task.prototype.onError.call(task, response)
|
||||
}
|
||||
yield progressCallback?.call(this, { reader })
|
||||
|
||||
//Start streaming the results.
|
||||
const streamGenerator = reader.open()
|
||||
let value = undefined
|
||||
let done = undefined
|
||||
yield progressCallback?.call(this, { stream: streamGenerator })
|
||||
do {
|
||||
;({ value, done } = yield streamGenerator.next())
|
||||
if (typeof value !== "object") {
|
||||
continue
|
||||
}
|
||||
if (value.status !== undefined) {
|
||||
yield progressCallback?.call(this, value)
|
||||
if (value.status === "succeeded" || value.status === "failed") {
|
||||
done = true
|
||||
}
|
||||
}
|
||||
} while (!done)
|
||||
return value
|
||||
}
|
||||
static start(task, progressCallback) {
|
||||
if (typeof task !== "object") {
|
||||
|
@ -5,6 +5,9 @@ const MIN_GPUS_TO_SHOW_SELECTION = 2
|
||||
const IMAGE_REGEX = new RegExp("data:image/[A-Za-z]+;base64")
|
||||
const htmlTaskMap = new WeakMap()
|
||||
|
||||
const spinnerPacmanHtml =
|
||||
'<div class="loadingio-spinner-bean-eater-x0y3u8qky4n"><div class="ldio-8f673ktaleu"><div><div></div><div></div><div></div></div><div><div></div><div></div><div></div></div></div></div>'
|
||||
|
||||
const taskConfigSetup = {
|
||||
taskConfig: {
|
||||
seed: { value: ({ seed }) => seed, label: "Seed" },
|
||||
@ -412,6 +415,7 @@ function showImages(reqBody, res, outputContainer, livePreview) {
|
||||
</div>
|
||||
<button class="imgPreviewItemClearBtn image_clear_btn"><i class="fa-solid fa-xmark"></i></button>
|
||||
<span class="img_bottom_label"></span>
|
||||
<div class="spinner displayNone"><center>${spinnerPacmanHtml}</center><div class="spinnerStatus"></div></div>
|
||||
</div>
|
||||
`
|
||||
outputContainer.appendChild(imageItemElem)
|
||||
@ -488,6 +492,7 @@ function showImages(reqBody, res, outputContainer, livePreview) {
|
||||
const imageSeedLabel = imageItemElem.querySelector(".imgSeedLabel")
|
||||
imageSeedLabel.innerText = "Seed: " + req.seed
|
||||
|
||||
const imageUndoBuffer = []
|
||||
let buttons = [
|
||||
{ text: "Use as Input", on_click: onUseAsInputClick },
|
||||
[
|
||||
@ -505,8 +510,9 @@ function showImages(reqBody, res, outputContainer, livePreview) {
|
||||
{ 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 },
|
||||
{ text: "Undo", on_click: onUndoFilter },
|
||||
{ text: "Upscale", on_click: onUpscaleClick },
|
||||
{ text: "Fix Faces", on_click: onFixFacesClick },
|
||||
],
|
||||
]
|
||||
|
||||
@ -515,6 +521,13 @@ function showImages(reqBody, res, outputContainer, livePreview) {
|
||||
|
||||
const imgItemInfo = imageItemElem.querySelector(".imgItemInfo")
|
||||
const img = imageItemElem.querySelector("img")
|
||||
const spinner = imageItemElem.querySelector(".spinner")
|
||||
const spinnerStatus = imageItemElem.querySelector(".spinnerStatus")
|
||||
const tools = {
|
||||
spinner: spinner,
|
||||
spinnerStatus: spinnerStatus,
|
||||
undoBuffer: imageUndoBuffer,
|
||||
}
|
||||
const createButton = function(btnInfo) {
|
||||
if (Array.isArray(btnInfo)) {
|
||||
const wrapper = document.createElement("div")
|
||||
@ -540,8 +553,12 @@ function showImages(reqBody, res, outputContainer, livePreview) {
|
||||
|
||||
if (btnInfo.on_click || !isLabel) {
|
||||
newButton.addEventListener("click", function(event) {
|
||||
btnInfo.on_click(req, img, event)
|
||||
btnInfo.on_click.bind(newButton)(req, img, event, tools)
|
||||
})
|
||||
if (btnInfo.on_click === onUndoFilter) {
|
||||
tools["undoButton"] = newButton
|
||||
newButton.classList.add("displayNone")
|
||||
}
|
||||
}
|
||||
|
||||
if (btnInfo.class !== undefined) {
|
||||
@ -656,16 +673,64 @@ function enqueueImageVariationTask(req, img, reqDiff) {
|
||||
createTask(newTaskRequest)
|
||||
}
|
||||
|
||||
function onUpscaleClick(req, img) {
|
||||
enqueueImageVariationTask(req, img, {
|
||||
use_upscale: upscaleModelField.value,
|
||||
function applyInlineFilter(filterName, path, filterParams, img, statusText, tools) {
|
||||
const filterReq = {
|
||||
image: img.src,
|
||||
filter: filterName,
|
||||
model_paths: {},
|
||||
filter_params: filterParams,
|
||||
}
|
||||
filterReq.model_paths[filterName] = path
|
||||
|
||||
tools.spinnerStatus.innerText = statusText
|
||||
tools.spinner.classList.remove("displayNone")
|
||||
|
||||
SD.filter(filterReq, (e) => {
|
||||
if (e.status === "succeeded") {
|
||||
let prevImg = img.src
|
||||
img.src = e.output[0]
|
||||
tools.spinner.classList.add("displayNone")
|
||||
tools.undoButton.classList.remove("displayNone")
|
||||
|
||||
if (prevImg.length > 0) {
|
||||
tools.undoBuffer.push(prevImg)
|
||||
}
|
||||
} else if (e.status == "failed") {
|
||||
alert("Error running upscale: " + e.detail)
|
||||
tools.spinner.classList.add("displayNone")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onFixFacesClick(req, img) {
|
||||
enqueueImageVariationTask(req, img, {
|
||||
use_face_correction: gfpganModelField.value,
|
||||
})
|
||||
function onUndoFilter(req, img, e, tools) {
|
||||
if (tools.undoBuffer.length === 0) {
|
||||
this.classList.add("displayNone")
|
||||
return
|
||||
}
|
||||
|
||||
let src = tools.undoBuffer.pop()
|
||||
if (src.length > 0) {
|
||||
img.src = src
|
||||
}
|
||||
|
||||
if (tools.undoBuffer.length === 0) {
|
||||
this.classList.add("displayNone")
|
||||
}
|
||||
}
|
||||
|
||||
function onUpscaleClick(req, img, e, tools) {
|
||||
let path = upscaleModelField.value
|
||||
let scale = parseInt(upscaleAmountField.value)
|
||||
let filterName = path.toLowerCase().includes("realesrgan") ? "realesrgan" : "latent_upscaler"
|
||||
let statusText = "Upscaling by " + scale + "x using " + filterName
|
||||
applyInlineFilter(filterName, path, { scale: scale }, img, statusText, tools)
|
||||
}
|
||||
|
||||
function onFixFacesClick(req, img, e, tools) {
|
||||
let path = gfpganModelField.value
|
||||
let filterName = path.toLowerCase().includes("gfpgan") ? "gfpgan" : "codeformer"
|
||||
let statusText = "Fixing faces with " + filterName
|
||||
applyInlineFilter(filterName, path, {}, img, statusText, tools)
|
||||
}
|
||||
|
||||
function onContinueDrawingClick(req, img) {
|
||||
@ -909,7 +974,9 @@ function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) {
|
||||
<a href="https://www.ibm.com/docs/en/opw/8.2.0?topic=tuning-optional-increasing-paging-file-size-windows-computers" target="_blank">Windows</a> or
|
||||
<a href="https://linuxhint.com/increase-swap-space-linux/" target="_blank">Linux</a>.<br/>
|
||||
3. Try restarting your computer.<br/>`
|
||||
} else if (msg.includes("RuntimeError: output with shape [320, 320] doesn't match the broadcast shape")) {
|
||||
} else if (
|
||||
msg.includes("RuntimeError: output with shape [320, 320] doesn't match the broadcast shape")
|
||||
) {
|
||||
msg += `<br/><br/>
|
||||
<b>Reason</b>: You tried to use a LORA that was trained for a different Stable Diffusion model version!
|
||||
<br/><br/>
|
||||
@ -2171,7 +2238,10 @@ function updateEmbeddingsList(filter = "") {
|
||||
} else {
|
||||
let subdir = html(m[1], prefix + m[0] + "/", filter)
|
||||
if (subdir != "") {
|
||||
folders += `<div class="embedding-category"><h4 class="collapsible">${prefix}${m[0]}</h4><div class="collapsible-content">` + subdir + '</div></div>'
|
||||
folders +=
|
||||
`<div class="embedding-category"><h4 class="collapsible">${prefix}${m[0]}</h4><div class="collapsible-content">` +
|
||||
subdir +
|
||||
"</div></div>"
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -2293,7 +2363,6 @@ embeddingsCollapsiblesBtn.addEventListener("click", (e) => {
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
if (testDiffusers.checked) {
|
||||
document.getElementById("embeddings-container").classList.remove("displayNone")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user