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/searchable-models.css">
|
||||||
<link rel="stylesheet" href="/media/css/image-modal.css">
|
<link rel="stylesheet" href="/media/css/image-modal.css">
|
||||||
<link rel="stylesheet" href="/media/css/plugins.css">
|
<link rel="stylesheet" href="/media/css/plugins.css">
|
||||||
|
<link rel="stylesheet" href="/media/css/animations.css">
|
||||||
<link rel="manifest" href="/media/manifest.webmanifest">
|
<link rel="manifest" href="/media/manifest.webmanifest">
|
||||||
<script src="/media/js/jquery-3.6.1.min.js"></script>
|
<script src="/media/js/jquery-3.6.1.min.js"></script>
|
||||||
<script src="/media/js/jquery-confirm.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!";
|
content: "Please restart Easy Diffusion!";
|
||||||
font-size: 10pt;
|
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
|
* @memberof Task
|
||||||
*/
|
*/
|
||||||
async post(timeout = -1) {
|
async post(timeout = -1) {
|
||||||
let res = await super.post("/filter", timeout)
|
let jsonResponse = await super.post("/filter", timeout)
|
||||||
//this._setId(jsonResponse.task)
|
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)
|
this._setStatus(TaskStatus.waiting)
|
||||||
|
return jsonResponse
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
checkReqBody() {}
|
checkReqBody() {}
|
||||||
enqueue(progressCallback) {
|
enqueue(progressCallback) {
|
||||||
@ -1087,6 +1102,51 @@
|
|||||||
yield progressCallback?.call(this, { detail: e.message })
|
yield progressCallback?.call(this, { detail: e.message })
|
||||||
throw e
|
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) {
|
static start(task, progressCallback) {
|
||||||
if (typeof task !== "object") {
|
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 IMAGE_REGEX = new RegExp("data:image/[A-Za-z]+;base64")
|
||||||
const htmlTaskMap = new WeakMap()
|
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 = {
|
const taskConfigSetup = {
|
||||||
taskConfig: {
|
taskConfig: {
|
||||||
seed: { value: ({ seed }) => seed, label: "Seed" },
|
seed: { value: ({ seed }) => seed, label: "Seed" },
|
||||||
@ -412,6 +415,7 @@ function showImages(reqBody, res, outputContainer, livePreview) {
|
|||||||
</div>
|
</div>
|
||||||
<button class="imgPreviewItemClearBtn image_clear_btn"><i class="fa-solid fa-xmark"></i></button>
|
<button class="imgPreviewItemClearBtn image_clear_btn"><i class="fa-solid fa-xmark"></i></button>
|
||||||
<span class="img_bottom_label"></span>
|
<span class="img_bottom_label"></span>
|
||||||
|
<div class="spinner displayNone"><center>${spinnerPacmanHtml}</center><div class="spinnerStatus"></div></div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
outputContainer.appendChild(imageItemElem)
|
outputContainer.appendChild(imageItemElem)
|
||||||
@ -488,6 +492,7 @@ function showImages(reqBody, res, outputContainer, livePreview) {
|
|||||||
const imageSeedLabel = imageItemElem.querySelector(".imgSeedLabel")
|
const imageSeedLabel = imageItemElem.querySelector(".imgSeedLabel")
|
||||||
imageSeedLabel.innerText = "Seed: " + req.seed
|
imageSeedLabel.innerText = "Seed: " + req.seed
|
||||||
|
|
||||||
|
const imageUndoBuffer = []
|
||||||
let buttons = [
|
let buttons = [
|
||||||
{ text: "Use as Input", on_click: onUseAsInputClick },
|
{ 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: "Make Similar Images", on_click: onMakeSimilarClick },
|
||||||
{ text: "Draw another 25 steps", on_click: onContinueDrawingClick },
|
{ text: "Draw another 25 steps", on_click: onContinueDrawingClick },
|
||||||
[
|
[
|
||||||
{ text: "Upscale", on_click: onUpscaleClick, filter: (req, img) => !req.use_upscale },
|
{ text: "Undo", on_click: onUndoFilter },
|
||||||
{ text: "Fix Faces", on_click: onFixFacesClick, filter: (req, img) => !req.use_face_correction },
|
{ 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 imgItemInfo = imageItemElem.querySelector(".imgItemInfo")
|
||||||
const img = imageItemElem.querySelector("img")
|
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) {
|
const createButton = function(btnInfo) {
|
||||||
if (Array.isArray(btnInfo)) {
|
if (Array.isArray(btnInfo)) {
|
||||||
const wrapper = document.createElement("div")
|
const wrapper = document.createElement("div")
|
||||||
@ -540,8 +553,12 @@ function showImages(reqBody, res, outputContainer, livePreview) {
|
|||||||
|
|
||||||
if (btnInfo.on_click || !isLabel) {
|
if (btnInfo.on_click || !isLabel) {
|
||||||
newButton.addEventListener("click", function(event) {
|
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) {
|
if (btnInfo.class !== undefined) {
|
||||||
@ -656,16 +673,64 @@ function enqueueImageVariationTask(req, img, reqDiff) {
|
|||||||
createTask(newTaskRequest)
|
createTask(newTaskRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUpscaleClick(req, img) {
|
function applyInlineFilter(filterName, path, filterParams, img, statusText, tools) {
|
||||||
enqueueImageVariationTask(req, img, {
|
const filterReq = {
|
||||||
use_upscale: upscaleModelField.value,
|
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) {
|
function onUndoFilter(req, img, e, tools) {
|
||||||
enqueueImageVariationTask(req, img, {
|
if (tools.undoBuffer.length === 0) {
|
||||||
use_face_correction: gfpganModelField.value,
|
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) {
|
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://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/>
|
<a href="https://linuxhint.com/increase-swap-space-linux/" target="_blank">Linux</a>.<br/>
|
||||||
3. Try restarting your computer.<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/>
|
msg += `<br/><br/>
|
||||||
<b>Reason</b>: You tried to use a LORA that was trained for a different Stable Diffusion model version!
|
<b>Reason</b>: You tried to use a LORA that was trained for a different Stable Diffusion model version!
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
@ -2171,7 +2238,10 @@ function updateEmbeddingsList(filter = "") {
|
|||||||
} else {
|
} else {
|
||||||
let subdir = html(m[1], prefix + m[0] + "/", filter)
|
let subdir = html(m[1], prefix + m[0] + "/", filter)
|
||||||
if (subdir != "") {
|
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) {
|
if (testDiffusers.checked) {
|
||||||
document.getElementById("embeddings-container").classList.remove("displayNone")
|
document.getElementById("embeddings-container").classList.remove("displayNone")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user