From 4aca3c4639226188c8e20b6802b2946ccca0351d Mon Sep 17 00:00:00 2001 From: JeLuF Date: Mon, 4 Sep 2023 01:36:32 +0200 Subject: [PATCH 01/16] Lora Manager --- ui/easydiffusion/server.py | 1 - ui/easydiffusion/utils/__init__.py | 1 + ui/media/js/main.js | 168 ++++-- ui/plugins/ui/merge.plugin.js | 884 +++++++++++++++++++---------- 4 files changed, 707 insertions(+), 347 deletions(-) diff --git a/ui/easydiffusion/server.py b/ui/easydiffusion/server.py index 311b3a03..5ca4f452 100644 --- a/ui/easydiffusion/server.py +++ b/ui/easydiffusion/server.py @@ -470,7 +470,6 @@ def modify_package_internal(package_name: str, req: dict): def get_sha256_internal(obj_path): - import hashlib from easydiffusion.utils import sha256sum path = obj_path.split("/") diff --git a/ui/easydiffusion/utils/__init__.py b/ui/easydiffusion/utils/__init__.py index f6758809..a930725b 100644 --- a/ui/easydiffusion/utils/__init__.py +++ b/ui/easydiffusion/utils/__init__.py @@ -1,4 +1,5 @@ import logging +import hashlib log = logging.getLogger("easydiffusion") diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 744568ec..7634f288 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -511,10 +511,10 @@ function showImages(reqBody, res, outputContainer, livePreview) { { text: "Upscale", on_click: onUpscaleClick }, { text: "Fix Faces", on_click: onFixFacesClick }, ], - { + { text: "Use as Thumbnail", on_click: onUseAsThumbnailClick, - filter: (req, img) => "use_embeddings_model" in req, + filter: (req, img) => "use_embeddings_model" in req || "use_lora_model" in req }, ] @@ -748,25 +748,45 @@ function onUseAsThumbnailClick(req, img) { onUseAsThumbnailClick.croppr.setImage(img.src) } - let embeddings = req.use_embeddings_model.map((e) => e.split("/").pop()) - let LORA = [] + useAsThumbSelect.innerHTML="" - if ("use_lora_model" in req) { - LORA = req.use_lora_model + if ("use_embeddings_model" in req) { + let embeddings = req.use_embeddings_model.map((e) => e.split("/").pop()) + + let embOptions = document.createElement("optgroup") + embOptions.label = "Embeddings" + embOptions.replaceChildren( + ...embeddings.map((e) => { + let option = document.createElement("option") + option.innerText = e + option.dataset["type"] = "embeddings" + return option + }) + ) + useAsThumbSelect.appendChild(embOptions) } - let optgroup = document.createElement("optgroup") - optgroup.label = "Embeddings" - optgroup.replaceChildren( - ...embeddings.map((e) => { - let option = document.createElement("option") - option.innerText = e - option.dataset["type"] = "embeddings" - return option - }) - ) - useAsThumbSelect.replaceChildren(optgroup) + if ("use_lora_model" in req) { + let LORA = req.use_lora_model + if (typeof LORA == "string") { + LORA = [LORA] + } + LORA = LORA.map((e) => e.split("/").pop()) + + let loraOptions = document.createElement("optgroup") + loraOptions.label = "LORA" + loraOptions.replaceChildren( + ...LORA.map((e) => { + let option = document.createElement("option") + option.innerText = e + option.dataset["type"] = "lora" + return option + }) + ) + useAsThumbSelect.appendChild(loraOptions) + } + useAsThumbDialog.showModal() onUseAsThumbnailClick.scale = scale } @@ -782,6 +802,50 @@ useAsThumbCancelBtn.addEventListener("click", () => { useAsThumbDialog.close() }) +const Bucket = { + upload(path, blob) { + const formData = new FormData() + formData.append("file", blob) + return fetch(`bucket/${path}`, { + method: "POST", + body: formData, + }) + }, + + getImageAsDataURL(path) { + return fetch(`bucket/${path}`) + .then((response) => { + if (response.status == 200) { + return response.blob() + } else { + throw new Error("Bucket error") + } + }) + .then((blob) => { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.onload = () => resolve(reader.result) + reader.onerror = reject + reader.readAsDataURL(blob) + }) + }) + }, + + getList(path) { + return fetch(`bucket/${path}`) + .then((response) => (response.status == 200 ? response.json() : [])) + }, + + store(path, data) { + return Bucket.upload(`${path}.json`, JSON.stringify(data)) + }, + + retrieve(path) { + return fetch(`bucket/${path}.json`) + .then((response) => (response.status == 200 ? response.json() : null)) + }, +} + useAsThumbSaveBtn.addEventListener("click", (e) => { let scale = 1 / onUseAsThumbnailClick.scale let crop = onUseAsThumbnailClick.croppr.getValue() @@ -793,16 +857,11 @@ useAsThumbSaveBtn.addEventListener("click", (e) => { .then((thumb) => fetch(thumb)) .then((response) => response.blob()) .then(async function(blob) { - const formData = new FormData() - formData.append("file", blob) let options = useAsThumbSelect.selectedOptions let promises = [] for (let embedding of options) { promises.push( - fetch(`bucket/${profileName}/${embedding.dataset["type"]}/${embedding.value}.png`, { - method: "POST", - body: formData, - }) + Bucket.upload(`${profileName}/${embedding.dataset["type"]}/${embedding.value}.png`, blob) ) } return Promise.all(promises) @@ -2362,20 +2421,10 @@ function loadThumbnailImageFromFile() { } function updateEmbeddingsList(filter = "") { - function html(model, iconlist = [], prefix = "", filter = "") { + function html(model, iconMap = {}, 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) => { @@ -2383,13 +2432,9 @@ function updateEmbeddingsList(filter = "") { 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]}` + if (token in iconMap) { + img = `/bucket/${profileName}/${iconMap[token]}` } button = createModifierCard(m, [img, img], true) // } @@ -2398,7 +2443,7 @@ function updateEmbeddingsList(filter = "") { toplevel.appendChild(button) } } else { - let subdir = html(m[1], iconlist, prefix + m[0] + "/", filter) + let subdir = html(m[1], iconMap, prefix + m[0] + "/", filter) if (typeof subdir == "object") { let div1 = document.createElement("div") let div2 = document.createElement("div") @@ -2457,11 +2502,44 @@ function updateEmbeddingsList(filter = "") { ` + let loraTokens = [] 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)) + let iconMap = {} + + Bucket.getList(`${profileName}/embeddings/`) + .then((icons) => { + iconMap = Object.assign( + {}, + ...icons.map((x) => ({ + [x + .toLowerCase() + .split(".") + .slice(0, -1) + .join(".")]: `embeddings/${x}`, + })) + ) + + return Bucket.getList(`${profileName}/lora/`) + }) + .then(async function (icons) { + for (let lora of loraModelField.value.modelNames) { + let keywords = await getLoraKeywords(lora) + loraTokens = loraTokens.concat(keywords) + let loraname = lora.split("/").pop() + + if (icons.includes(`${loraname}.png`)) { + keywords.forEach((kw) => { + iconMap[kw.toLowerCase()] = `lora/${loraname}.png` + + }) + } + } + + let tokenList = [...modelsOptions.embeddings] + if (loraTokens.length != 0) { + tokenList.unshift(['LORA Keywords', loraTokens]) + } + embeddingsList.replaceChildren(html(tokenList, iconMap, "", filter)) createCollapsibles(embeddingsList) if (filter != "") { embeddingsExpandAll() diff --git a/ui/plugins/ui/merge.plugin.js b/ui/plugins/ui/merge.plugin.js index d3ddedbf..639afc07 100644 --- a/ui/plugins/ui/merge.plugin.js +++ b/ui/plugins/ui/merge.plugin.js @@ -1,6 +1,337 @@ ;(function() { "use strict" + let mergeCSS = ` + /*********** Main tab ***********/ + .tab-centered { + justify-content: center; + } + + #model-tool-tab-content { + background-color: var(--background-color3); + } + + #model-tool-tab-content .tab-content-inner { + text-align: initial; + } + + #model-tool-tab-bar .tab { + margin-bottom: 0px; + border-top-left-radius: var(--input-border-radius); + background-color: var(--background-color3); + padding: 6px 6px 0.8em 6px; + } + #tab-content-merge .tab-content-inner { + max-width: 100%; + padding: 10pt; + } + + /*********** Merge UI ***********/ + .merge-model-container { + margin-left: 15%; + margin-right: 15%; + text-align: left; + display: inline-grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: auto auto auto; + gap: 0px 0px; + grid-auto-flow: row; + grid-template-areas: + "merge-input merge-config" + "merge-buttons merge-buttons"; + } + .merge-model-container p { + margin-top: 3pt; + margin-bottom: 3pt; + } + .merge-config .tab-content { + background: var(--background-color1); + border-radius: 3pt; + } + .merge-config .tab-content-inner { + text-align: left; + } + + .merge-input { + grid-area: merge-input; + padding-left:1em; + } + .merge-config { + grid-area: merge-config; + padding:1em; + } + .merge-config input { + margin-bottom: 3px; + } + .merge-config select { + margin-bottom: 3px; + } + .merge-buttons { + grid-area: merge-buttons; + padding:1em; + text-align: center; + } + #merge-button { + padding: 8px; + width:20em; + } + div#merge-log { + height:150px; + overflow-x:hidden; + overflow-y:scroll; + background:var(--background-color1); + border-radius: 3pt; + } + div#merge-log i { + color: hsl(var(--accent-hue), 100%, calc(2*var(--accent-lightness))); + font-family: monospace; + } + .disabled { + background: var(--background-color4); + color: var(--text-color); + } + #merge-type-tabs { + border-bottom: 1px solid black; + } + #merge-log-container { + display: none; + } + .merge-model-container #merge-warning { + color: var(--small-label-color); + } + + /*********** LORA UI ***********/ + .lora-manager-grid { + display: grid; + gap: 0px 8px; + grid-auto-flow: row; + } + + @media screen and (min-width: 1501px) { + .lora-manager-grid textarea { + height:350px; + } + + .lora-manager-grid { + grid-template-columns: auto 1fr 1fr; + grid-template-rows: auto 1fr; + grid-template-areas: + "selector selector selector" + "thumbnail keywords notes"; + } + } + + @media screen and (min-width: 1001px) and (max-width: 1500px) { + .lora-manager-grid textarea { + height:250px; + } + + .lora-manager-grid { + grid-template-columns: auto auto; + grid-template-rows: auto auto auto; + grid-template-areas: + "selector selector" + "thumbnail keywords" + "thumbnail notes"; + } + + } + + @media screen and (max-width: 1000px) { + .lora-manager-grid textarea { + height:200px; + } + + .lora-manager-grid { + grid-template-columns: auto; + grid-template-rows: auto auto auto auto; + grid-template-areas: + "selector" + "keywords" + "thumbnail" + "notes"; + } + + } + + .lora-manager-grid-selector { + grid-area: selector; + justify-self: start; + } + + .lora-manager-grid-thumbnail { + grid-area: thumbnail; + justify-self: center; + } + + .lora-manager-grid-keywords { + grid-area: keywords; + } + + .lora-manager-grid-notes { + grid-area: notes; + } + + .lora-manager-grid p { + margin-bottom: 2px; + } + + + ` + + let mergeUI = ` +
+
+

+ +

+ +

+

Important: Please merge models of similar type.
For e.g. SD 1.4 models with only SD 1.4/1.5 models,
SD 2.0 with SD 2.0-type, and SD 2.1 with SD 2.1-type models.

+
+ + + + + + + + + + + + + +
Base name of the output file.
Mix ratio and file suffix will be appended to this.
+ Image generation uses fp16, so it's a good choice.
Use fp32 if you want to use the result models for more mixes
+
+
+
+
+

+
+
+
+
+
+ + Make a single file + + + Make multiple variations + +
+
+
+
+ Saves a single merged model file, at the specified merge ratio.

+ + + % + Model A's contribution to the mix. The rest will be from Model B. +
+
+
+
+ Saves multiple variations of the model, at different merge ratios.
Each variation will be saved as a separate file.


+ + + + + + + + + + + + + +
Number of models to create
% Smallest share of model A in the mix
% Share of model A added into the mix per step
Sigmoid function to be applied to the model share before mixing
+
+ Preview of variation ratios:
+ +
+
+
+
+
+ +
+
` + + + let loraUI=` +
+
+ + +
+
+

Thumbnail:

+
+ + +
+
+ + +
+
+
+

Keywords: +

+ +

+ LORA model keywords can be used via the + Embeddings button. They get added to the embedding + keyword menu when the LORA has been selected in the image settings. +

+
+
+

Notes:

+ +

+ Civitai model page: + +

+
+
` + + let tabHTML=` +
+ + Lora Keywords + + + Merge Models + +
+
+
+
+ ${loraUI} +
+
+ +
+
+ ${mergeUI} +
+
+
` + + ///////////////////// Function section function smoothstep(x) { return x * x * (3 - 2 * x) @@ -140,315 +471,266 @@ } drawDiagram(fn) } - createTab({ - id: "merge", - icon: "fa-code-merge", - label: "Merge models", - css: ` - #tab-content-merge .tab-content-inner { - max-width: 100%; - padding: 10pt; - } - .merge-container { - margin-left: 15%; - margin-right: 15%; - text-align: left; - display: inline-grid; - grid-template-columns: 1fr 1fr; - grid-template-rows: auto auto auto; - gap: 0px 0px; - grid-auto-flow: row; - grid-template-areas: - "merge-input merge-config" - "merge-buttons merge-buttons"; - } - .merge-container p { - margin-top: 3pt; - margin-bottom: 3pt; - } - .merge-config .tab-content { - background: var(--background-color1); - border-radius: 3pt; - } - .merge-config .tab-content-inner { - text-align: left; + + function initMergeUI() { + const tabSettingsSingle = document.querySelector("#tab-merge-opts-single") + const tabSettingsBatch = document.querySelector("#tab-merge-opts-batch") + linkTabContents(tabSettingsSingle) + linkTabContents(tabSettingsBatch) + + let mergeModelAField = new ModelDropdown(document.querySelector("#mergeModelA"), "stable-diffusion") + let mergeModelBField = new ModelDropdown(document.querySelector("#mergeModelB"), "stable-diffusion") + updateChart() + + // slider + const singleMergeRatioField = document.querySelector("#single-merge-ratio") + const singleMergeRatioSlider = document.querySelector("#single-merge-ratio-slider") + + function updateSingleMergeRatio() { + singleMergeRatioField.value = singleMergeRatioSlider.value / 10 + singleMergeRatioField.dispatchEvent(new Event("change")) } - .merge-input { - grid-area: merge-input; - padding-left:1em; + function updateSingleMergeRatioSlider() { + if (singleMergeRatioField.value < 0) { + singleMergeRatioField.value = 0 + } else if (singleMergeRatioField.value > 100) { + singleMergeRatioField.value = 100 + } + + singleMergeRatioSlider.value = singleMergeRatioField.value * 10 + singleMergeRatioSlider.dispatchEvent(new Event("change")) } - .merge-config { - grid-area: merge-config; - padding:1em; + + singleMergeRatioSlider.addEventListener("input", updateSingleMergeRatio) + singleMergeRatioField.addEventListener("input", updateSingleMergeRatioSlider) + updateSingleMergeRatio() + + document.querySelector(".merge-config").addEventListener("change", updateChart) + + document.querySelector("#merge-button").addEventListener("click", async function(e) { + // Build request template + let model0 = mergeModelAField.value + let model1 = mergeModelBField.value + let request = { model0: model0, model1: model1 } + request["use_fp16"] = document.querySelector("#merge-fp").value == "fp16" + let iterations = document.querySelector("#merge-count").value >> 0 + let start = parseFloat(document.querySelector("#merge-start").value) + let step = parseFloat(document.querySelector("#merge-step").value) + + if (isTabActive(tabSettingsSingle)) { + start = parseFloat(singleMergeRatioField.value) + step = 0 + iterations = 1 + addLogMessage(`merge ratio = ${start}%`) + } else { + addLogMessage(`start = ${start}%`) + addLogMessage(`step = ${step}%`) + } + + if (start + (iterations - 1) * step >= 100) { + addLogMessage("Aborting: maximum ratio is ≥ 100%") + addLogMessage("Reduce the number of variations or the step size") + addLogSeparator() + document.querySelector("#merge-count").focus() + return + } + + if (document.querySelector("#merge-filename").value == "") { + addLogMessage("Aborting: No output file name specified") + addLogSeparator() + document.querySelector("#merge-filename").focus() + return + } + + // Disable merge button + e.target.disabled = true + e.target.classList.add("disabled") + let cursor = $("body").css("cursor") + let label = document.querySelector("#merge-button").innerHTML + $("body").css("cursor", "progress") + document.querySelector("#merge-button").innerHTML = "Merging models ..." + + addLogMessage("Merging models") + addLogMessage("Model A: " + model0) + addLogMessage("Model B: " + model1) + + // Batch main loop + for (let i = 0; i < iterations; i++) { + let alpha = (start + i * step) / 100 + + if (isTabActive(tabSettingsBatch)) { + switch (document.querySelector("#merge-interpolation").value) { + case "SmoothStep": + alpha = smoothstep(alpha) + break + case "SmootherStep": + alpha = smootherstep(alpha) + break + case "SmoothestStep": + alpha = smootheststep(alpha) + break + } + } + addLogMessage(`merging batch job ${i + 1}/${iterations}, alpha = ${alpha.toFixed(5)}...`) + + request["out_path"] = document.querySelector("#merge-filename").value + request["out_path"] += "-" + alpha.toFixed(5) + "." + document.querySelector("#merge-format").value + addLogMessage(`  filename: ${request["out_path"]}`) + + // sdkit documentation: "ratio - the ratio of the second model. 1 means only the second model will be used." + request["ratio"] = 1-alpha + let res = await fetch("/model/merge", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(request), + }) + const data = await res.json() + addLogMessage(JSON.stringify(data)) + } + addLogMessage( + "Done. The models have been saved to your models/stable-diffusion folder." + ) + addLogSeparator() + // Re-enable merge button + $("body").css("cursor", cursor) + document.querySelector("#merge-button").innerHTML = label + e.target.disabled = false + e.target.classList.remove("disabled") + + // Update model list + stableDiffusionModelField.innerHTML = "" + vaeModelField.innerHTML = "" + hypernetworkModelField.innerHTML = "" + await getModels() + }) + } + + const LoraUI = { + modelField: undefined, + keywordsField: undefined, + notesField: undefined, + civitaiImportBtn: undefined, + civitaiSecion: undefined, + civitaiAnchor: undefined, + image: undefined, + imagePlaceholder: undefined, + + init() { + LoraUI.modelField = new ModelDropdown(document.querySelector("#loraModel"), "lora") + LoraUI.keywordsField = document.querySelector("#lora-manager-keywords") + LoraUI.notesField = document.querySelector("#lora-manager-notes") + LoraUI.civitaiImportBtn = document.querySelector("#lora-keyword-from-civitai") + LoraUI.civitaiSection = document.querySelector("#civitai-section") + LoraUI.civitaiAnchor = document.querySelector("#civitai-model-page") + LoraUI.image = document.querySelector("#lora-manager-image") + LoraUI.imagePlaceholder = document.querySelector("#lora-manager-image-placeholder") + + LoraUI.modelField.addEventListener("change", LoraUI.updateFields) + LoraUI.keywordsField.addEventListener("focusout", LoraUI.saveInfos) + LoraUI.notesField.addEventListener("focusout", LoraUI.saveInfos) + LoraUI.civitaiImportBtn.addEventListener("click", LoraUI.importFromCivitai) + + LoraUI.updateFields() + }, + + updateFields() { + document.getElementById("civitai-section").classList.add("displayNone") + Bucket.retrieve(`modelinfo/lora/${LoraUI.modelField.value}`) + .then((info) => { + if (info == null) { + LoraUI.keywordsField.value = "" + LoraUI.notesField.value = "" + LoraUI.hideCivitaiLink() + } else { + LoraUI.keywordsField.value = info.keywords.join("\n") + LoraUI.notesField.value = info.notes + if ("civitai" in info) { + LoraUI.showCivitaiLink(info.civitai) + } + } + }) + Bucket.getImageAsDataURL(`${profileNameField.value}/lora/${LoraUI.modelField.value}.png`) + .then((data) => { + LoraUI.image.src=data + LoraUI.image.classList.remove("displayNone") + LoraUI.imagePlaceholder.classList.add("displayNone") + }) + .catch((error) => { + console.error("Caught error:", error) + LoraUI.image.classList.add("displayNone") + LoraUI.imagePlaceholder.classList.remove("displayNone") + }) + }, + + saveInfos() { + let info = { + keywords: LoraUI.keywordsField.value + .split("\n") + .filter((x) => (x != "")), + notes: LoraUI.notesField.value, + civitai: LoraUI.civitaiSection.checkVisibility() ? LoraUI.civitaiAnchor.href : null, + } + Bucket.store(`modelinfo/lora/${LoraUI.modelField.value}`, info) + }, + + importFromCivitai() { + document.body.style["cursor"] = "progress" + fetch("/sha256/lora/"+LoraUI.modelField.value) + .then((result) => result.json()) + .then((json) => fetch("https://civitai.com/api/v1/model-versions/by-hash/" + json.digest)) + .then((result) => result.json()) + .then((json) => { + document.body.style["cursor"] = "default" + if (json == null) { + return + } + if ("trainedWords" in json) { + LoraUI.keywordsField.value = json["trainedWords"].join("\n") + } else { + showToast("No keyword info found.") + } + if ("modelId" in json) { + LoraUI.showCivitaiLink("https://civitai.com/models/" + json.modelId) + } else { + LoraUI.hideCivitaiLink() + } + + LoraUI.saveInfos() + }) + }, + + showCivitaiLink(href) { + LoraUI.civitaiSection.classList.remove("displayNone") + LoraUI.civitaiAnchor.href = href + LoraUI.civitaiAnchor.innerHTML = LoraUI.civitaiAnchor.href + }, + + hideCivitaiLink() { + LoraUI.civitaiSection.classList.add("displayNone") } - .merge-config input { - margin-bottom: 3px; - } - .merge-config select { - margin-bottom: 3px; - } - .merge-buttons { - grid-area: merge-buttons; - padding:1em; - text-align: center; - } - #merge-button { - padding: 8px; - width:20em; - } - div#merge-log { - height:150px; - overflow-x:hidden; - overflow-y:scroll; - background:var(--background-color1); - border-radius: 3pt; - } - div#merge-log i { - color: hsl(var(--accent-hue), 100%, calc(2*var(--accent-lightness))); - font-family: monospace; - } - .disabled { - background: var(--background-color4); - color: var(--text-color); - } - #merge-type-tabs { - border-bottom: 1px solid black; - } - #merge-log-container { - display: none; - } - .merge-container #merge-warning { - color: rgb(153, 153, 153); - }`, - content: ` -
-
-

- -

- -

-

Important: Please merge models of similar type.
For e.g. SD 1.4 models with only SD 1.4/1.5 models,
SD 2.0 with SD 2.0-type, and SD 2.1 with SD 2.1-type models.

-
- - - - - - - - - - - - - -
Base name of the output file.
Mix ratio and file suffix will be appended to this.
- Image generation uses fp16, so it's a good choice.
Use fp32 if you want to use the result models for more mixes
-
-
-
-
-

-
-
-
-
-
- - Make a single file - - - Make multiple variations - -
-
-
-
- Saves a single merged model file, at the specified merge ratio.

- - - % - Model A's contribution to the mix. The rest will be from Model B. -
-
-
-
- Saves multiple variations of the model, at different merge ratios.
Each variation will be saved as a separate file.


- - - - - - - - - - - - - -
Number of models to create
% Smallest share of model A in the mix
% Share of model A added into the mix per step
Sigmoid function to be applied to the model share before mixing
-
- Preview of variation ratios:
- -
-
-
-
-
- -
-
`, + } + + createTab({ + id: "merge", + icon: "fa-toolbox", + label: "Model tools", + css: mergeCSS, + content: tabHTML, onOpen: ({ firstOpen }) => { if (!firstOpen) { return } - - const tabSettingsSingle = document.querySelector("#tab-merge-opts-single") - const tabSettingsBatch = document.querySelector("#tab-merge-opts-batch") - linkTabContents(tabSettingsSingle) - linkTabContents(tabSettingsBatch) - - console.log("Activate") - let mergeModelAField = new ModelDropdown(document.querySelector("#mergeModelA"), "stable-diffusion") - let mergeModelBField = new ModelDropdown(document.querySelector("#mergeModelB"), "stable-diffusion") - updateChart() - - // slider - const singleMergeRatioField = document.querySelector("#single-merge-ratio") - const singleMergeRatioSlider = document.querySelector("#single-merge-ratio-slider") - - function updateSingleMergeRatio() { - singleMergeRatioField.value = singleMergeRatioSlider.value / 10 - singleMergeRatioField.dispatchEvent(new Event("change")) - } - - function updateSingleMergeRatioSlider() { - if (singleMergeRatioField.value < 0) { - singleMergeRatioField.value = 0 - } else if (singleMergeRatioField.value > 100) { - singleMergeRatioField.value = 100 - } - - singleMergeRatioSlider.value = singleMergeRatioField.value * 10 - singleMergeRatioSlider.dispatchEvent(new Event("change")) - } - - singleMergeRatioSlider.addEventListener("input", updateSingleMergeRatio) - singleMergeRatioField.addEventListener("input", updateSingleMergeRatioSlider) - updateSingleMergeRatio() - - document.querySelector(".merge-config").addEventListener("change", updateChart) - - document.querySelector("#merge-button").addEventListener("click", async function(e) { - // Build request template - let model0 = mergeModelAField.value - let model1 = mergeModelBField.value - let request = { model0: model0, model1: model1 } - request["use_fp16"] = document.querySelector("#merge-fp").value == "fp16" - let iterations = document.querySelector("#merge-count").value >> 0 - let start = parseFloat(document.querySelector("#merge-start").value) - let step = parseFloat(document.querySelector("#merge-step").value) - - if (isTabActive(tabSettingsSingle)) { - start = parseFloat(singleMergeRatioField.value) - step = 0 - iterations = 1 - addLogMessage(`merge ratio = ${start}%`) - } else { - addLogMessage(`start = ${start}%`) - addLogMessage(`step = ${step}%`) - } - - if (start + (iterations - 1) * step >= 100) { - addLogMessage("Aborting: maximum ratio is ≥ 100%") - addLogMessage("Reduce the number of variations or the step size") - addLogSeparator() - document.querySelector("#merge-count").focus() - return - } - - if (document.querySelector("#merge-filename").value == "") { - addLogMessage("Aborting: No output file name specified") - addLogSeparator() - document.querySelector("#merge-filename").focus() - return - } - - // Disable merge button - e.target.disabled = true - e.target.classList.add("disabled") - let cursor = $("body").css("cursor") - let label = document.querySelector("#merge-button").innerHTML - $("body").css("cursor", "progress") - document.querySelector("#merge-button").innerHTML = "Merging models ..." - - addLogMessage("Merging models") - addLogMessage("Model A: " + model0) - addLogMessage("Model B: " + model1) - - // Batch main loop - for (let i = 0; i < iterations; i++) { - let alpha = (start + i * step) / 100 - - if (isTabActive(tabSettingsBatch)) { - switch (document.querySelector("#merge-interpolation").value) { - case "SmoothStep": - alpha = smoothstep(alpha) - break - case "SmootherStep": - alpha = smootherstep(alpha) - break - case "SmoothestStep": - alpha = smootheststep(alpha) - break - } - } - addLogMessage(`merging batch job ${i + 1}/${iterations}, alpha = ${alpha.toFixed(5)}...`) - - request["out_path"] = document.querySelector("#merge-filename").value - request["out_path"] += "-" + alpha.toFixed(5) + "." + document.querySelector("#merge-format").value - addLogMessage(`  filename: ${request["out_path"]}`) - - // sdkit documentation: "ratio - the ratio of the second model. 1 means only the second model will be used." - request["ratio"] = 1-alpha - let res = await fetch("/model/merge", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(request), - }) - const data = await res.json() - addLogMessage(JSON.stringify(data)) - } - addLogMessage( - "Done. The models have been saved to your models/stable-diffusion folder." - ) - addLogSeparator() - // Re-enable merge button - $("body").css("cursor", cursor) - document.querySelector("#merge-button").innerHTML = label - e.target.disabled = false - e.target.classList.remove("disabled") - - // Update model list - stableDiffusionModelField.innerHTML = "" - vaeModelField.innerHTML = "" - hypernetworkModelField.innerHTML = "" - await getModels() - }) + initMergeUI() + LoraUI.init() + const tabMergeUI = document.querySelector("#tab-model-mergeUI") + const tabLoraUI = document.querySelector("#tab-model-loraUI") + linkTabContents(tabMergeUI) + linkTabContents(tabLoraUI) }, }) })() +async function getLoraKeywords(model) { + return Bucket.retrieve(`modelinfo/lora/${model}`) + .then((info) => info.keywords) +} From 338aef3e95d9fb4008aa26409df796da7aaf6fa1 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 14 Sep 2023 19:52:50 +0530 Subject: [PATCH 02/16] sdkit 2.0.11 - fix for gfpgan when using multiple GPUs in parallel --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 13551b0a..6538c30c 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -21,7 +21,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0", "2.0.1"), "torchvision": ("0.12.0", "0.14.1", "0.15.1", "0.15.2"), - "sdkit": "2.0.10", + "sdkit": "2.0.11", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From dd7deeba53b4324604c52ff1e96ae795c7cb2074 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 14 Sep 2023 19:53:44 +0530 Subject: [PATCH 03/16] v3.0.6 --- CHANGES.md | 1 + ui/index.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 1382322d..5f195b8f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ - **Major rewrite of the code** - We've switched to using diffusers under-the-hood, which allows us to release new features faster, and focus on making the UI and installer even easier to use. ### Detailed changelog +* 3.0.6 - 14 Sep 2023 - Fix GFPGAN (Face Fixing) when running on multiple GPUs in parallel. * 3.0.5 - 2 Sep 2023 - Support SDXL ControlNets. * 3.0.4 - 1 Sep 2023 - Fix incorrect metadata generated for embeddings, when the exact word doesn't match the case, or is part of a larger word. * 3.0.4 - 1 Sep 2023 - Simplify the installation for AMD users on Linux. Thanks @JeLuf. diff --git a/ui/index.html b/ui/index.html index b406a71f..3d02cab8 100644 --- a/ui/index.html +++ b/ui/index.html @@ -35,7 +35,7 @@

Easy Diffusion - v3.0.5 + v3.0.6

From c13d1093ee23933169bcb2bc4c1487f363576440 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 14 Sep 2023 20:01:53 +0530 Subject: [PATCH 04/16] sdkit 2.0.12 - actually use the gfpgan fix. 2.0.11 was bad --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 6538c30c..8ffb844e 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -21,7 +21,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0", "2.0.1"), "torchvision": ("0.12.0", "0.14.1", "0.15.1", "0.15.2"), - "sdkit": "2.0.11", + "sdkit": "2.0.12", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 6311b80474f1887803f1e5fa06f743ca90d41340 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Thu, 14 Sep 2023 23:15:27 +0200 Subject: [PATCH 05/16] Loramanager fixes - avoid console errors in python and JS code - suppress localhost:9000/null links --- ui/easydiffusion/bucket_manager.py | 7 ++++++- ui/plugins/ui/merge.plugin.js | 5 +++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ui/easydiffusion/bucket_manager.py b/ui/easydiffusion/bucket_manager.py index 60a4ed6c..c4f4c7e1 100644 --- a/ui/easydiffusion/bucket_manager.py +++ b/ui/easydiffusion/bucket_manager.py @@ -55,8 +55,13 @@ def init(): return bucketfiles else: - bucket_id = crud.get_bucket_by_path(db, path).id + bucket = crud.get_bucket_by_path(db, path) + if bucket == None: + raise HTTPException(status_code=404, detail="Bucket not found") + bucket_id = bucket.id bucketfile = db.query(models.BucketFile).filter(models.BucketFile.bucket_id == bucket_id, models.BucketFile.filename == filename).first() + if bucketfile == None: + raise HTTPException(status_code=404, detail="File not found") suffix = get_suffix_from_filename(filename) diff --git a/ui/plugins/ui/merge.plugin.js b/ui/plugins/ui/merge.plugin.js index 639afc07..9606d39c 100644 --- a/ui/plugins/ui/merge.plugin.js +++ b/ui/plugins/ui/merge.plugin.js @@ -645,8 +645,10 @@ } else { LoraUI.keywordsField.value = info.keywords.join("\n") LoraUI.notesField.value = info.notes - if ("civitai" in info) { + if ("civitai" in info && info["civitai"] != null) { LoraUI.showCivitaiLink(info.civitai) + } else { + LoraUI.hideCivitaiLink() } } }) @@ -657,7 +659,6 @@ LoraUI.imagePlaceholder.classList.add("displayNone") }) .catch((error) => { - console.error("Caught error:", error) LoraUI.image.classList.add("displayNone") LoraUI.imagePlaceholder.classList.remove("displayNone") }) From a12ed7533b88a2f8ad17e04b2667da42bb9842a1 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 15 Sep 2023 19:09:14 +0530 Subject: [PATCH 06/16] Fix broken embeddings dialog when the lora info couldn't be fetched --- ui/plugins/ui/merge.plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/plugins/ui/merge.plugin.js b/ui/plugins/ui/merge.plugin.js index 9606d39c..8e74cf61 100644 --- a/ui/plugins/ui/merge.plugin.js +++ b/ui/plugins/ui/merge.plugin.js @@ -733,5 +733,5 @@ })() async function getLoraKeywords(model) { return Bucket.retrieve(`modelinfo/lora/${model}`) - .then((info) => info.keywords) + .then((info) => info ? info.keywords : []) } From 86e2ac40ae3f72dc88067789a0a0e137eb8e8897 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 15 Sep 2023 19:09:45 +0530 Subject: [PATCH 07/16] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 5f195b8f..11bfacfc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ - **Major rewrite of the code** - We've switched to using diffusers under-the-hood, which allows us to release new features faster, and focus on making the UI and installer even easier to use. ### Detailed changelog +* 3.0.6 - 15 Sep 2023 - Fix broken embeddings dialog when LoRA information couldn't be fetched. * 3.0.6 - 14 Sep 2023 - Fix GFPGAN (Face Fixing) when running on multiple GPUs in parallel. * 3.0.5 - 2 Sep 2023 - Support SDXL ControlNets. * 3.0.4 - 1 Sep 2023 - Fix incorrect metadata generated for embeddings, when the exact word doesn't match the case, or is part of a larger word. From d6a02a31a72c324199b772b089d760cd7514f8cc Mon Sep 17 00:00:00 2001 From: JeLuF Date: Sun, 17 Sep 2023 22:36:50 +0200 Subject: [PATCH 08/16] LoraManager: Implement 'Upload thumbnail' button --- ui/media/js/main.js | 1 + ui/plugins/ui/model-tools.plugin.js | 770 ++++++++++++++++++++++++++++ 2 files changed, 771 insertions(+) create mode 100644 ui/plugins/ui/model-tools.plugin.js diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 7634f288..101a88e7 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -868,6 +868,7 @@ useAsThumbSaveBtn.addEventListener("click", (e) => { }) .then(() => { useAsThumbDialog.close() + document.dispatchEvent(new CustomEvent("saveThumb", { detail: useAsThumbSelect.selectedOptions })) }) .catch((error) => { console.error(error) diff --git a/ui/plugins/ui/model-tools.plugin.js b/ui/plugins/ui/model-tools.plugin.js new file mode 100644 index 00000000..09fd6499 --- /dev/null +++ b/ui/plugins/ui/model-tools.plugin.js @@ -0,0 +1,770 @@ +;(function() { + "use strict" + + let mergeCSS = ` + /*********** Main tab ***********/ + .tab-centered { + justify-content: center; + } + + #model-tool-tab-content { + background-color: var(--background-color3); + } + + #model-tool-tab-content .tab-content-inner { + text-align: initial; + } + + #model-tool-tab-bar .tab { + margin-bottom: 0px; + border-top-left-radius: var(--input-border-radius); + background-color: var(--background-color3); + padding: 6px 6px 0.8em 6px; + } + #tab-content-merge .tab-content-inner { + max-width: 100%; + padding: 10pt; + } + + /*********** Merge UI ***********/ + .merge-model-container { + margin-left: 15%; + margin-right: 15%; + text-align: left; + display: inline-grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: auto auto auto; + gap: 0px 0px; + grid-auto-flow: row; + grid-template-areas: + "merge-input merge-config" + "merge-buttons merge-buttons"; + } + .merge-model-container p { + margin-top: 3pt; + margin-bottom: 3pt; + } + .merge-config .tab-content { + background: var(--background-color1); + border-radius: 3pt; + } + .merge-config .tab-content-inner { + text-align: left; + } + + .merge-input { + grid-area: merge-input; + padding-left:1em; + } + .merge-config { + grid-area: merge-config; + padding:1em; + } + .merge-config input { + margin-bottom: 3px; + } + .merge-config select { + margin-bottom: 3px; + } + .merge-buttons { + grid-area: merge-buttons; + padding:1em; + text-align: center; + } + #merge-button { + padding: 8px; + width:20em; + } + div#merge-log { + height:150px; + overflow-x:hidden; + overflow-y:scroll; + background:var(--background-color1); + border-radius: 3pt; + } + div#merge-log i { + color: hsl(var(--accent-hue), 100%, calc(2*var(--accent-lightness))); + font-family: monospace; + } + .disabled { + background: var(--background-color4); + color: var(--text-color); + } + #merge-type-tabs { + border-bottom: 1px solid black; + } + #merge-log-container { + display: none; + } + .merge-model-container #merge-warning { + color: var(--small-label-color); + } + + /*********** LORA UI ***********/ + .lora-manager-grid { + display: grid; + gap: 0px 8px; + grid-auto-flow: row; + } + + @media screen and (min-width: 1501px) { + .lora-manager-grid textarea { + height:350px; + } + + .lora-manager-grid { + grid-template-columns: auto 1fr 1fr; + grid-template-rows: auto 1fr; + grid-template-areas: + "selector selector selector" + "thumbnail keywords notes"; + } + } + + @media screen and (min-width: 1001px) and (max-width: 1500px) { + .lora-manager-grid textarea { + height:250px; + } + + .lora-manager-grid { + grid-template-columns: auto auto; + grid-template-rows: auto auto auto; + grid-template-areas: + "selector selector" + "thumbnail keywords" + "thumbnail notes"; + } + + } + + @media screen and (max-width: 1000px) { + .lora-manager-grid textarea { + height:200px; + } + + .lora-manager-grid { + grid-template-columns: auto; + grid-template-rows: auto auto auto auto; + grid-template-areas: + "selector" + "keywords" + "thumbnail" + "notes"; + } + + } + + .lora-manager-grid-selector { + grid-area: selector; + justify-self: start; + } + + .lora-manager-grid-thumbnail { + grid-area: thumbnail; + justify-self: center; + } + + .lora-manager-grid-keywords { + grid-area: keywords; + } + + .lora-manager-grid-notes { + grid-area: notes; + } + + .lora-manager-grid p { + margin-bottom: 2px; + } + + + ` + + let mergeUI = ` +
+
+

+ +

+ +

+

Important: Please merge models of similar type.
For e.g. SD 1.4 models with only SD 1.4/1.5 models,
SD 2.0 with SD 2.0-type, and SD 2.1 with SD 2.1-type models.

+
+ + + + + + + + + + + + + +
Base name of the output file.
Mix ratio and file suffix will be appended to this.
+ Image generation uses fp16, so it's a good choice.
Use fp32 if you want to use the result models for more mixes
+
+
+
+
+

+
+
+
+
+
+ + Make a single file + + + Make multiple variations + +
+
+
+
+ Saves a single merged model file, at the specified merge ratio.

+ + + % + Model A's contribution to the mix. The rest will be from Model B. +
+
+
+
+ Saves multiple variations of the model, at different merge ratios.
Each variation will be saved as a separate file.


+ + + + + + + + + + + + + +
Number of models to create
% Smallest share of model A in the mix
% Share of model A added into the mix per step
Sigmoid function to be applied to the model share before mixing
+
+ Preview of variation ratios:
+ +
+
+
+
+
+ +
+
` + + + let loraUI=` +
+
+ + +
+
+

Thumbnail:

+
+ + +
+
+ + + +
+
+
+

Keywords: +

+ +

+ LORA model keywords can be used via the + Embeddings button. They get added to the embedding + keyword menu when the LORA has been selected in the image settings. +

+
+
+

Notes:

+ +

+ Civitai model page: + +

+
+
` + + let tabHTML=` +
+ + Lora Keywords + + + Merge Models + +
+
+
+
+ ${loraUI} +
+
+ +
+
+ ${mergeUI} +
+
+
` + + + ///////////////////// Function section + function smoothstep(x) { + return x * x * (3 - 2 * x) + } + + function smootherstep(x) { + return x * x * x * (x * (x * 6 - 15) + 10) + } + + function smootheststep(x) { + let y = -20 * Math.pow(x, 7) + y += 70 * Math.pow(x, 6) + y -= 84 * Math.pow(x, 5) + y += 35 * Math.pow(x, 4) + return y + } + function getCurrentTime() { + const now = new Date() + let hours = now.getHours() + let minutes = now.getMinutes() + let seconds = now.getSeconds() + + hours = hours < 10 ? `0${hours}` : hours + minutes = minutes < 10 ? `0${minutes}` : minutes + seconds = seconds < 10 ? `0${seconds}` : seconds + + return `${hours}:${minutes}:${seconds}` + } + + function addLogMessage(message) { + const logContainer = document.getElementById("merge-log") + logContainer.innerHTML += `${getCurrentTime()} ${message}
` + + // Scroll to the bottom of the log + logContainer.scrollTop = logContainer.scrollHeight + + document.querySelector("#merge-log-container").style.display = "block" + } + + function addLogSeparator() { + const logContainer = document.getElementById("merge-log") + logContainer.innerHTML += "
" + + logContainer.scrollTop = logContainer.scrollHeight + } + + function drawDiagram(fn) { + const SIZE = 300 + const canvas = document.getElementById("merge-canvas") + canvas.height = canvas.width = SIZE + const ctx = canvas.getContext("2d") + + // Draw coordinate system + ctx.scale(1, -1) + ctx.translate(0, -canvas.height) + ctx.lineWidth = 1 + ctx.beginPath() + + ctx.strokeStyle = "white" + ctx.moveTo(0, 0) + ctx.lineTo(0, SIZE) + ctx.lineTo(SIZE, SIZE) + ctx.lineTo(SIZE, 0) + ctx.lineTo(0, 0) + ctx.lineTo(SIZE, SIZE) + ctx.stroke() + ctx.beginPath() + ctx.setLineDash([1, 2]) + const n = SIZE / 10 + for (let i = n; i < SIZE; i += n) { + ctx.moveTo(0, i) + ctx.lineTo(SIZE, i) + ctx.moveTo(i, 0) + ctx.lineTo(i, SIZE) + } + ctx.stroke() + ctx.beginPath() + ctx.setLineDash([]) + ctx.beginPath() + ctx.strokeStyle = "black" + ctx.lineWidth = 3 + // Plot function + const numSamples = 20 + for (let i = 0; i <= numSamples; i++) { + const x = i / numSamples + const y = fn(x) + + const canvasX = x * SIZE + const canvasY = y * SIZE + + if (i === 0) { + ctx.moveTo(canvasX, canvasY) + } else { + ctx.lineTo(canvasX, canvasY) + } + } + ctx.stroke() + // Plot alpha values (yellow boxes) + let start = parseFloat(document.querySelector("#merge-start").value) + let step = parseFloat(document.querySelector("#merge-step").value) + let iterations = document.querySelector("#merge-count").value >> 0 + ctx.beginPath() + ctx.fillStyle = "yellow" + for (let i = 0; i < iterations; i++) { + const alpha = (start + i * step) / 100 + const x = alpha * SIZE + const y = fn(alpha) * SIZE + if (x <= SIZE) { + ctx.rect(x - 3, y - 3, 6, 6) + ctx.fill() + } else { + ctx.strokeStyle = "red" + ctx.moveTo(0, 0) + ctx.lineTo(0, SIZE) + ctx.lineTo(SIZE, SIZE) + ctx.lineTo(SIZE, 0) + ctx.lineTo(0, 0) + ctx.lineTo(SIZE, SIZE) + ctx.stroke() + addLogMessage("Warning: maximum ratio is ≥ 100%") + } + } + } + + function updateChart() { + let fn = (x) => x + switch (document.querySelector("#merge-interpolation").value) { + case "SmoothStep": + fn = smoothstep + break + case "SmootherStep": + fn = smootherstep + break + case "SmoothestStep": + fn = smootheststep + break + } + drawDiagram(fn) + } + + function initMergeUI() { + const tabSettingsSingle = document.querySelector("#tab-merge-opts-single") + const tabSettingsBatch = document.querySelector("#tab-merge-opts-batch") + linkTabContents(tabSettingsSingle) + linkTabContents(tabSettingsBatch) + + let mergeModelAField = new ModelDropdown(document.querySelector("#mergeModelA"), "stable-diffusion") + let mergeModelBField = new ModelDropdown(document.querySelector("#mergeModelB"), "stable-diffusion") + updateChart() + + // slider + const singleMergeRatioField = document.querySelector("#single-merge-ratio") + const singleMergeRatioSlider = document.querySelector("#single-merge-ratio-slider") + + function updateSingleMergeRatio() { + singleMergeRatioField.value = singleMergeRatioSlider.value / 10 + singleMergeRatioField.dispatchEvent(new Event("change")) + } + + function updateSingleMergeRatioSlider() { + if (singleMergeRatioField.value < 0) { + singleMergeRatioField.value = 0 + } else if (singleMergeRatioField.value > 100) { + singleMergeRatioField.value = 100 + } + + singleMergeRatioSlider.value = singleMergeRatioField.value * 10 + singleMergeRatioSlider.dispatchEvent(new Event("change")) + } + + singleMergeRatioSlider.addEventListener("input", updateSingleMergeRatio) + singleMergeRatioField.addEventListener("input", updateSingleMergeRatioSlider) + updateSingleMergeRatio() + + document.querySelector(".merge-config").addEventListener("change", updateChart) + + document.querySelector("#merge-button").addEventListener("click", async function(e) { + // Build request template + let model0 = mergeModelAField.value + let model1 = mergeModelBField.value + let request = { model0: model0, model1: model1 } + request["use_fp16"] = document.querySelector("#merge-fp").value == "fp16" + let iterations = document.querySelector("#merge-count").value >> 0 + let start = parseFloat(document.querySelector("#merge-start").value) + let step = parseFloat(document.querySelector("#merge-step").value) + + if (isTabActive(tabSettingsSingle)) { + start = parseFloat(singleMergeRatioField.value) + step = 0 + iterations = 1 + addLogMessage(`merge ratio = ${start}%`) + } else { + addLogMessage(`start = ${start}%`) + addLogMessage(`step = ${step}%`) + } + + if (start + (iterations - 1) * step >= 100) { + addLogMessage("Aborting: maximum ratio is ≥ 100%") + addLogMessage("Reduce the number of variations or the step size") + addLogSeparator() + document.querySelector("#merge-count").focus() + return + } + + if (document.querySelector("#merge-filename").value == "") { + addLogMessage("Aborting: No output file name specified") + addLogSeparator() + document.querySelector("#merge-filename").focus() + return + } + + // Disable merge button + e.target.disabled = true + e.target.classList.add("disabled") + let cursor = $("body").css("cursor") + let label = document.querySelector("#merge-button").innerHTML + $("body").css("cursor", "progress") + document.querySelector("#merge-button").innerHTML = "Merging models ..." + + addLogMessage("Merging models") + addLogMessage("Model A: " + model0) + addLogMessage("Model B: " + model1) + + // Batch main loop + for (let i = 0; i < iterations; i++) { + let alpha = (start + i * step) / 100 + + if (isTabActive(tabSettingsBatch)) { + switch (document.querySelector("#merge-interpolation").value) { + case "SmoothStep": + alpha = smoothstep(alpha) + break + case "SmootherStep": + alpha = smootherstep(alpha) + break + case "SmoothestStep": + alpha = smootheststep(alpha) + break + } + } + addLogMessage(`merging batch job ${i + 1}/${iterations}, alpha = ${alpha.toFixed(5)}...`) + + request["out_path"] = document.querySelector("#merge-filename").value + request["out_path"] += "-" + alpha.toFixed(5) + "." + document.querySelector("#merge-format").value + addLogMessage(`  filename: ${request["out_path"]}`) + + // sdkit documentation: "ratio - the ratio of the second model. 1 means only the second model will be used." + request["ratio"] = 1-alpha + let res = await fetch("/model/merge", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(request), + }) + const data = await res.json() + addLogMessage(JSON.stringify(data)) + } + addLogMessage( + "Done. The models have been saved to your models/stable-diffusion folder." + ) + addLogSeparator() + // Re-enable merge button + $("body").css("cursor", cursor) + document.querySelector("#merge-button").innerHTML = label + e.target.disabled = false + e.target.classList.remove("disabled") + + // Update model list + stableDiffusionModelField.innerHTML = "" + vaeModelField.innerHTML = "" + hypernetworkModelField.innerHTML = "" + await getModels() + }) + } + + const LoraUI = { + modelField: undefined, + keywordsField: undefined, + notesField: undefined, + civitaiImportBtn: undefined, + civitaiSecion: undefined, + civitaiAnchor: undefined, + image: undefined, + imagePlaceholder: undefined, + + init() { + LoraUI.modelField = new ModelDropdown(document.querySelector("#loraModel"), "lora") + LoraUI.keywordsField = document.querySelector("#lora-manager-keywords") + LoraUI.notesField = document.querySelector("#lora-manager-notes") + LoraUI.civitaiImportBtn = document.querySelector("#lora-keyword-from-civitai") + LoraUI.civitaiSection = document.querySelector("#civitai-section") + LoraUI.civitaiAnchor = document.querySelector("#civitai-model-page") + LoraUI.image = document.querySelector("#lora-manager-image") + LoraUI.imagePlaceholder = document.querySelector("#lora-manager-image-placeholder") + LoraUI.uploadBtn = document.querySelector("#lora-manager-upload-button") + LoraUI.uploadInput = document.querySelector("#lora-manager-upload-input") + + LoraUI.modelField.addEventListener("change", LoraUI.updateFields) + LoraUI.keywordsField.addEventListener("focusout", LoraUI.saveInfos) + LoraUI.notesField.addEventListener("focusout", LoraUI.saveInfos) + LoraUI.civitaiImportBtn.addEventListener("click", LoraUI.importFromCivitai) + + LoraUI.uploadBtn.addEventListener("click", (e) => LoraUI.uploadInput.click()) + LoraUI.uploadInput.addEventListener("change", LoraUI.uploadLoraThumb) + + document.addEventListener("saveThumb", LoraUI.updateFields) + + LoraUI.updateFields() + }, + + uploadLoraThumb(e) { + console.log(e) + if (LoraUI.uploadInput.files.length === 0) { + return + } + + let reader = new FileReader() + let file = LoraUI.uploadInput.files[0] + + reader.addEventListener("load", (event) => { + let img = document.createElement("img") + img.src = reader.result + onUseAsThumbnailClick( + { + use_lora_model: LoraUI.modelField.value, + }, + img + ) + }) + + if (file) { + reader.readAsDataURL(file) + } + }, + + updateFields() { + document.getElementById("civitai-section").classList.add("displayNone") + Bucket.retrieve(`modelinfo/lora/${LoraUI.modelField.value}`) + .then((info) => { + if (info == null) { + LoraUI.keywordsField.value = "" + LoraUI.notesField.value = "" + LoraUI.hideCivitaiLink() + } else { + LoraUI.keywordsField.value = info.keywords.join("\n") + LoraUI.notesField.value = info.notes + if ("civitai" in info && info["civitai"] != null) { + LoraUI.showCivitaiLink(info.civitai) + } else { + LoraUI.hideCivitaiLink() + } + } + }) + Bucket.getImageAsDataURL(`${profileNameField.value}/lora/${LoraUI.modelField.value}.png`) + .then((data) => { + LoraUI.image.src=data + LoraUI.image.classList.remove("displayNone") + LoraUI.imagePlaceholder.classList.add("displayNone") + }) + .catch((error) => { + LoraUI.image.classList.add("displayNone") + LoraUI.imagePlaceholder.classList.remove("displayNone") + }) + }, + + saveInfos() { + let info = { + keywords: LoraUI.keywordsField.value + .split("\n") + .filter((x) => (x != "")), + notes: LoraUI.notesField.value, + civitai: LoraUI.civitaiSection.checkVisibility() ? LoraUI.civitaiAnchor.href : null, + } + Bucket.store(`modelinfo/lora/${LoraUI.modelField.value}`, info) + }, + + importFromCivitai() { + document.body.style["cursor"] = "progress" + fetch("/sha256/lora/"+LoraUI.modelField.value) + .then((result) => result.json()) + .then((json) => fetch("https://civitai.com/api/v1/model-versions/by-hash/" + json.digest)) + .then((result) => result.json()) + .then((json) => { + document.body.style["cursor"] = "default" + if (json == null) { + return + } + if ("trainedWords" in json) { + LoraUI.keywordsField.value = json["trainedWords"].join("\n") + } else { + showToast("No keyword info found.") + } + if ("modelId" in json) { + LoraUI.showCivitaiLink("https://civitai.com/models/" + json.modelId) + } else { + LoraUI.hideCivitaiLink() + } + + LoraUI.saveInfos() + }) + }, + + showCivitaiLink(href) { + LoraUI.civitaiSection.classList.remove("displayNone") + LoraUI.civitaiAnchor.href = href + LoraUI.civitaiAnchor.innerHTML = LoraUI.civitaiAnchor.href + }, + + hideCivitaiLink() { + LoraUI.civitaiSection.classList.add("displayNone") + } + } + + createTab({ + id: "merge", + icon: "fa-toolbox", + label: "Model tools", + css: mergeCSS, + content: tabHTML, + onOpen: ({ firstOpen }) => { + if (!firstOpen) { + return + } + initMergeUI() + LoraUI.init() + const tabMergeUI = document.querySelector("#tab-model-mergeUI") + const tabLoraUI = document.querySelector("#tab-model-loraUI") + linkTabContents(tabMergeUI) + linkTabContents(tabLoraUI) + }, + }) +})() +async function getLoraKeywords(model) { + return Bucket.retrieve(`modelinfo/lora/${model}`) + .then((info) => info ? info.keywords : []) +} From 14dbebbc35eb3075bc12188f84e5ef80401df1ec Mon Sep 17 00:00:00 2001 From: JeLuF Date: Sun, 17 Sep 2023 22:37:17 +0200 Subject: [PATCH 09/16] LoraManager: Implement 'Upload thumbnail' button --- ui/plugins/ui/merge.plugin.js | 737 ---------------------------------- 1 file changed, 737 deletions(-) delete mode 100644 ui/plugins/ui/merge.plugin.js diff --git a/ui/plugins/ui/merge.plugin.js b/ui/plugins/ui/merge.plugin.js deleted file mode 100644 index 8e74cf61..00000000 --- a/ui/plugins/ui/merge.plugin.js +++ /dev/null @@ -1,737 +0,0 @@ -;(function() { - "use strict" - - let mergeCSS = ` - /*********** Main tab ***********/ - .tab-centered { - justify-content: center; - } - - #model-tool-tab-content { - background-color: var(--background-color3); - } - - #model-tool-tab-content .tab-content-inner { - text-align: initial; - } - - #model-tool-tab-bar .tab { - margin-bottom: 0px; - border-top-left-radius: var(--input-border-radius); - background-color: var(--background-color3); - padding: 6px 6px 0.8em 6px; - } - #tab-content-merge .tab-content-inner { - max-width: 100%; - padding: 10pt; - } - - /*********** Merge UI ***********/ - .merge-model-container { - margin-left: 15%; - margin-right: 15%; - text-align: left; - display: inline-grid; - grid-template-columns: 1fr 1fr; - grid-template-rows: auto auto auto; - gap: 0px 0px; - grid-auto-flow: row; - grid-template-areas: - "merge-input merge-config" - "merge-buttons merge-buttons"; - } - .merge-model-container p { - margin-top: 3pt; - margin-bottom: 3pt; - } - .merge-config .tab-content { - background: var(--background-color1); - border-radius: 3pt; - } - .merge-config .tab-content-inner { - text-align: left; - } - - .merge-input { - grid-area: merge-input; - padding-left:1em; - } - .merge-config { - grid-area: merge-config; - padding:1em; - } - .merge-config input { - margin-bottom: 3px; - } - .merge-config select { - margin-bottom: 3px; - } - .merge-buttons { - grid-area: merge-buttons; - padding:1em; - text-align: center; - } - #merge-button { - padding: 8px; - width:20em; - } - div#merge-log { - height:150px; - overflow-x:hidden; - overflow-y:scroll; - background:var(--background-color1); - border-radius: 3pt; - } - div#merge-log i { - color: hsl(var(--accent-hue), 100%, calc(2*var(--accent-lightness))); - font-family: monospace; - } - .disabled { - background: var(--background-color4); - color: var(--text-color); - } - #merge-type-tabs { - border-bottom: 1px solid black; - } - #merge-log-container { - display: none; - } - .merge-model-container #merge-warning { - color: var(--small-label-color); - } - - /*********** LORA UI ***********/ - .lora-manager-grid { - display: grid; - gap: 0px 8px; - grid-auto-flow: row; - } - - @media screen and (min-width: 1501px) { - .lora-manager-grid textarea { - height:350px; - } - - .lora-manager-grid { - grid-template-columns: auto 1fr 1fr; - grid-template-rows: auto 1fr; - grid-template-areas: - "selector selector selector" - "thumbnail keywords notes"; - } - } - - @media screen and (min-width: 1001px) and (max-width: 1500px) { - .lora-manager-grid textarea { - height:250px; - } - - .lora-manager-grid { - grid-template-columns: auto auto; - grid-template-rows: auto auto auto; - grid-template-areas: - "selector selector" - "thumbnail keywords" - "thumbnail notes"; - } - - } - - @media screen and (max-width: 1000px) { - .lora-manager-grid textarea { - height:200px; - } - - .lora-manager-grid { - grid-template-columns: auto; - grid-template-rows: auto auto auto auto; - grid-template-areas: - "selector" - "keywords" - "thumbnail" - "notes"; - } - - } - - .lora-manager-grid-selector { - grid-area: selector; - justify-self: start; - } - - .lora-manager-grid-thumbnail { - grid-area: thumbnail; - justify-self: center; - } - - .lora-manager-grid-keywords { - grid-area: keywords; - } - - .lora-manager-grid-notes { - grid-area: notes; - } - - .lora-manager-grid p { - margin-bottom: 2px; - } - - - ` - - let mergeUI = ` -
-
-

- -

- -

-

Important: Please merge models of similar type.
For e.g. SD 1.4 models with only SD 1.4/1.5 models,
SD 2.0 with SD 2.0-type, and SD 2.1 with SD 2.1-type models.

-
- - - - - - - - - - - - - -
Base name of the output file.
Mix ratio and file suffix will be appended to this.
- Image generation uses fp16, so it's a good choice.
Use fp32 if you want to use the result models for more mixes
-
-
-
-
-

-
-
-
-
-
- - Make a single file - - - Make multiple variations - -
-
-
-
- Saves a single merged model file, at the specified merge ratio.

- - - % - Model A's contribution to the mix. The rest will be from Model B. -
-
-
-
- Saves multiple variations of the model, at different merge ratios.
Each variation will be saved as a separate file.


- - - - - - - - - - - - - -
Number of models to create
% Smallest share of model A in the mix
% Share of model A added into the mix per step
Sigmoid function to be applied to the model share before mixing
-
- Preview of variation ratios:
- -
-
-
-
-
- -
-
` - - - let loraUI=` -
-
- - -
-
-

Thumbnail:

-
- - -
-
- - -
-
-
-

Keywords: -

- -

- LORA model keywords can be used via the + Embeddings button. They get added to the embedding - keyword menu when the LORA has been selected in the image settings. -

-
-
-

Notes:

- -

- Civitai model page: - -

-
-
` - - let tabHTML=` -
- - Lora Keywords - - - Merge Models - -
-
-
-
- ${loraUI} -
-
- -
-
- ${mergeUI} -
-
-
` - - - ///////////////////// Function section - function smoothstep(x) { - return x * x * (3 - 2 * x) - } - - function smootherstep(x) { - return x * x * x * (x * (x * 6 - 15) + 10) - } - - function smootheststep(x) { - let y = -20 * Math.pow(x, 7) - y += 70 * Math.pow(x, 6) - y -= 84 * Math.pow(x, 5) - y += 35 * Math.pow(x, 4) - return y - } - function getCurrentTime() { - const now = new Date() - let hours = now.getHours() - let minutes = now.getMinutes() - let seconds = now.getSeconds() - - hours = hours < 10 ? `0${hours}` : hours - minutes = minutes < 10 ? `0${minutes}` : minutes - seconds = seconds < 10 ? `0${seconds}` : seconds - - return `${hours}:${minutes}:${seconds}` - } - - function addLogMessage(message) { - const logContainer = document.getElementById("merge-log") - logContainer.innerHTML += `${getCurrentTime()} ${message}
` - - // Scroll to the bottom of the log - logContainer.scrollTop = logContainer.scrollHeight - - document.querySelector("#merge-log-container").style.display = "block" - } - - function addLogSeparator() { - const logContainer = document.getElementById("merge-log") - logContainer.innerHTML += "
" - - logContainer.scrollTop = logContainer.scrollHeight - } - - function drawDiagram(fn) { - const SIZE = 300 - const canvas = document.getElementById("merge-canvas") - canvas.height = canvas.width = SIZE - const ctx = canvas.getContext("2d") - - // Draw coordinate system - ctx.scale(1, -1) - ctx.translate(0, -canvas.height) - ctx.lineWidth = 1 - ctx.beginPath() - - ctx.strokeStyle = "white" - ctx.moveTo(0, 0) - ctx.lineTo(0, SIZE) - ctx.lineTo(SIZE, SIZE) - ctx.lineTo(SIZE, 0) - ctx.lineTo(0, 0) - ctx.lineTo(SIZE, SIZE) - ctx.stroke() - ctx.beginPath() - ctx.setLineDash([1, 2]) - const n = SIZE / 10 - for (let i = n; i < SIZE; i += n) { - ctx.moveTo(0, i) - ctx.lineTo(SIZE, i) - ctx.moveTo(i, 0) - ctx.lineTo(i, SIZE) - } - ctx.stroke() - ctx.beginPath() - ctx.setLineDash([]) - ctx.beginPath() - ctx.strokeStyle = "black" - ctx.lineWidth = 3 - // Plot function - const numSamples = 20 - for (let i = 0; i <= numSamples; i++) { - const x = i / numSamples - const y = fn(x) - - const canvasX = x * SIZE - const canvasY = y * SIZE - - if (i === 0) { - ctx.moveTo(canvasX, canvasY) - } else { - ctx.lineTo(canvasX, canvasY) - } - } - ctx.stroke() - // Plot alpha values (yellow boxes) - let start = parseFloat(document.querySelector("#merge-start").value) - let step = parseFloat(document.querySelector("#merge-step").value) - let iterations = document.querySelector("#merge-count").value >> 0 - ctx.beginPath() - ctx.fillStyle = "yellow" - for (let i = 0; i < iterations; i++) { - const alpha = (start + i * step) / 100 - const x = alpha * SIZE - const y = fn(alpha) * SIZE - if (x <= SIZE) { - ctx.rect(x - 3, y - 3, 6, 6) - ctx.fill() - } else { - ctx.strokeStyle = "red" - ctx.moveTo(0, 0) - ctx.lineTo(0, SIZE) - ctx.lineTo(SIZE, SIZE) - ctx.lineTo(SIZE, 0) - ctx.lineTo(0, 0) - ctx.lineTo(SIZE, SIZE) - ctx.stroke() - addLogMessage("Warning: maximum ratio is ≥ 100%") - } - } - } - - function updateChart() { - let fn = (x) => x - switch (document.querySelector("#merge-interpolation").value) { - case "SmoothStep": - fn = smoothstep - break - case "SmootherStep": - fn = smootherstep - break - case "SmoothestStep": - fn = smootheststep - break - } - drawDiagram(fn) - } - - function initMergeUI() { - const tabSettingsSingle = document.querySelector("#tab-merge-opts-single") - const tabSettingsBatch = document.querySelector("#tab-merge-opts-batch") - linkTabContents(tabSettingsSingle) - linkTabContents(tabSettingsBatch) - - let mergeModelAField = new ModelDropdown(document.querySelector("#mergeModelA"), "stable-diffusion") - let mergeModelBField = new ModelDropdown(document.querySelector("#mergeModelB"), "stable-diffusion") - updateChart() - - // slider - const singleMergeRatioField = document.querySelector("#single-merge-ratio") - const singleMergeRatioSlider = document.querySelector("#single-merge-ratio-slider") - - function updateSingleMergeRatio() { - singleMergeRatioField.value = singleMergeRatioSlider.value / 10 - singleMergeRatioField.dispatchEvent(new Event("change")) - } - - function updateSingleMergeRatioSlider() { - if (singleMergeRatioField.value < 0) { - singleMergeRatioField.value = 0 - } else if (singleMergeRatioField.value > 100) { - singleMergeRatioField.value = 100 - } - - singleMergeRatioSlider.value = singleMergeRatioField.value * 10 - singleMergeRatioSlider.dispatchEvent(new Event("change")) - } - - singleMergeRatioSlider.addEventListener("input", updateSingleMergeRatio) - singleMergeRatioField.addEventListener("input", updateSingleMergeRatioSlider) - updateSingleMergeRatio() - - document.querySelector(".merge-config").addEventListener("change", updateChart) - - document.querySelector("#merge-button").addEventListener("click", async function(e) { - // Build request template - let model0 = mergeModelAField.value - let model1 = mergeModelBField.value - let request = { model0: model0, model1: model1 } - request["use_fp16"] = document.querySelector("#merge-fp").value == "fp16" - let iterations = document.querySelector("#merge-count").value >> 0 - let start = parseFloat(document.querySelector("#merge-start").value) - let step = parseFloat(document.querySelector("#merge-step").value) - - if (isTabActive(tabSettingsSingle)) { - start = parseFloat(singleMergeRatioField.value) - step = 0 - iterations = 1 - addLogMessage(`merge ratio = ${start}%`) - } else { - addLogMessage(`start = ${start}%`) - addLogMessage(`step = ${step}%`) - } - - if (start + (iterations - 1) * step >= 100) { - addLogMessage("Aborting: maximum ratio is ≥ 100%") - addLogMessage("Reduce the number of variations or the step size") - addLogSeparator() - document.querySelector("#merge-count").focus() - return - } - - if (document.querySelector("#merge-filename").value == "") { - addLogMessage("Aborting: No output file name specified") - addLogSeparator() - document.querySelector("#merge-filename").focus() - return - } - - // Disable merge button - e.target.disabled = true - e.target.classList.add("disabled") - let cursor = $("body").css("cursor") - let label = document.querySelector("#merge-button").innerHTML - $("body").css("cursor", "progress") - document.querySelector("#merge-button").innerHTML = "Merging models ..." - - addLogMessage("Merging models") - addLogMessage("Model A: " + model0) - addLogMessage("Model B: " + model1) - - // Batch main loop - for (let i = 0; i < iterations; i++) { - let alpha = (start + i * step) / 100 - - if (isTabActive(tabSettingsBatch)) { - switch (document.querySelector("#merge-interpolation").value) { - case "SmoothStep": - alpha = smoothstep(alpha) - break - case "SmootherStep": - alpha = smootherstep(alpha) - break - case "SmoothestStep": - alpha = smootheststep(alpha) - break - } - } - addLogMessage(`merging batch job ${i + 1}/${iterations}, alpha = ${alpha.toFixed(5)}...`) - - request["out_path"] = document.querySelector("#merge-filename").value - request["out_path"] += "-" + alpha.toFixed(5) + "." + document.querySelector("#merge-format").value - addLogMessage(`  filename: ${request["out_path"]}`) - - // sdkit documentation: "ratio - the ratio of the second model. 1 means only the second model will be used." - request["ratio"] = 1-alpha - let res = await fetch("/model/merge", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(request), - }) - const data = await res.json() - addLogMessage(JSON.stringify(data)) - } - addLogMessage( - "Done. The models have been saved to your models/stable-diffusion folder." - ) - addLogSeparator() - // Re-enable merge button - $("body").css("cursor", cursor) - document.querySelector("#merge-button").innerHTML = label - e.target.disabled = false - e.target.classList.remove("disabled") - - // Update model list - stableDiffusionModelField.innerHTML = "" - vaeModelField.innerHTML = "" - hypernetworkModelField.innerHTML = "" - await getModels() - }) - } - - const LoraUI = { - modelField: undefined, - keywordsField: undefined, - notesField: undefined, - civitaiImportBtn: undefined, - civitaiSecion: undefined, - civitaiAnchor: undefined, - image: undefined, - imagePlaceholder: undefined, - - init() { - LoraUI.modelField = new ModelDropdown(document.querySelector("#loraModel"), "lora") - LoraUI.keywordsField = document.querySelector("#lora-manager-keywords") - LoraUI.notesField = document.querySelector("#lora-manager-notes") - LoraUI.civitaiImportBtn = document.querySelector("#lora-keyword-from-civitai") - LoraUI.civitaiSection = document.querySelector("#civitai-section") - LoraUI.civitaiAnchor = document.querySelector("#civitai-model-page") - LoraUI.image = document.querySelector("#lora-manager-image") - LoraUI.imagePlaceholder = document.querySelector("#lora-manager-image-placeholder") - - LoraUI.modelField.addEventListener("change", LoraUI.updateFields) - LoraUI.keywordsField.addEventListener("focusout", LoraUI.saveInfos) - LoraUI.notesField.addEventListener("focusout", LoraUI.saveInfos) - LoraUI.civitaiImportBtn.addEventListener("click", LoraUI.importFromCivitai) - - LoraUI.updateFields() - }, - - updateFields() { - document.getElementById("civitai-section").classList.add("displayNone") - Bucket.retrieve(`modelinfo/lora/${LoraUI.modelField.value}`) - .then((info) => { - if (info == null) { - LoraUI.keywordsField.value = "" - LoraUI.notesField.value = "" - LoraUI.hideCivitaiLink() - } else { - LoraUI.keywordsField.value = info.keywords.join("\n") - LoraUI.notesField.value = info.notes - if ("civitai" in info && info["civitai"] != null) { - LoraUI.showCivitaiLink(info.civitai) - } else { - LoraUI.hideCivitaiLink() - } - } - }) - Bucket.getImageAsDataURL(`${profileNameField.value}/lora/${LoraUI.modelField.value}.png`) - .then((data) => { - LoraUI.image.src=data - LoraUI.image.classList.remove("displayNone") - LoraUI.imagePlaceholder.classList.add("displayNone") - }) - .catch((error) => { - LoraUI.image.classList.add("displayNone") - LoraUI.imagePlaceholder.classList.remove("displayNone") - }) - }, - - saveInfos() { - let info = { - keywords: LoraUI.keywordsField.value - .split("\n") - .filter((x) => (x != "")), - notes: LoraUI.notesField.value, - civitai: LoraUI.civitaiSection.checkVisibility() ? LoraUI.civitaiAnchor.href : null, - } - Bucket.store(`modelinfo/lora/${LoraUI.modelField.value}`, info) - }, - - importFromCivitai() { - document.body.style["cursor"] = "progress" - fetch("/sha256/lora/"+LoraUI.modelField.value) - .then((result) => result.json()) - .then((json) => fetch("https://civitai.com/api/v1/model-versions/by-hash/" + json.digest)) - .then((result) => result.json()) - .then((json) => { - document.body.style["cursor"] = "default" - if (json == null) { - return - } - if ("trainedWords" in json) { - LoraUI.keywordsField.value = json["trainedWords"].join("\n") - } else { - showToast("No keyword info found.") - } - if ("modelId" in json) { - LoraUI.showCivitaiLink("https://civitai.com/models/" + json.modelId) - } else { - LoraUI.hideCivitaiLink() - } - - LoraUI.saveInfos() - }) - }, - - showCivitaiLink(href) { - LoraUI.civitaiSection.classList.remove("displayNone") - LoraUI.civitaiAnchor.href = href - LoraUI.civitaiAnchor.innerHTML = LoraUI.civitaiAnchor.href - }, - - hideCivitaiLink() { - LoraUI.civitaiSection.classList.add("displayNone") - } - } - - createTab({ - id: "merge", - icon: "fa-toolbox", - label: "Model tools", - css: mergeCSS, - content: tabHTML, - onOpen: ({ firstOpen }) => { - if (!firstOpen) { - return - } - initMergeUI() - LoraUI.init() - const tabMergeUI = document.querySelector("#tab-model-mergeUI") - const tabLoraUI = document.querySelector("#tab-model-loraUI") - linkTabContents(tabMergeUI) - linkTabContents(tabLoraUI) - }, - }) -})() -async function getLoraKeywords(model) { - return Bucket.retrieve(`modelinfo/lora/${model}`) - .then((info) => info ? info.keywords : []) -} From e8b77513742057e7c2ba5d3dc442ec55d9e9081b Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 18 Sep 2023 21:25:08 +0530 Subject: [PATCH 10/16] typo --- ui/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/index.html b/ui/index.html index 3d02cab8..96e93ca7 100644 --- a/ui/index.html +++ b/ui/index.html @@ -513,7 +513,7 @@
  • How to use
  • Writing prompts
  • -
  • Image Modifiers
  • +
  • Image Modifiers
  • Inpainting
  • Samplers
  • Summary of every UI option
  • From 87c9df5c0de6c635695f881cb8f0fa6e1ea42ede Mon Sep 17 00:00:00 2001 From: JeLuF Date: Mon, 18 Sep 2023 18:55:34 +0200 Subject: [PATCH 11/16] Remove old plugin file --- scripts/on_sd_start.bat | 1 + scripts/on_sd_start.sh | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/scripts/on_sd_start.bat b/scripts/on_sd_start.bat index ccbb97fe..51668fbd 100644 --- a/scripts/on_sd_start.bat +++ b/scripts/on_sd_start.bat @@ -34,6 +34,7 @@ call conda activate @REM remove the old version of the dev console script, if it's still present if exist "Open Developer Console.cmd" del "Open Developer Console.cmd" +if exist "ui\plugins\ui\merge.plugin.js" del "ui\plugins\ui\merge.plugin.js" @rem create the stable-diffusion folder, to work with legacy installations if not exist "stable-diffusion" mkdir stable-diffusion diff --git a/scripts/on_sd_start.sh b/scripts/on_sd_start.sh index 48161cd3..fbd39f8c 100755 --- a/scripts/on_sd_start.sh +++ b/scripts/on_sd_start.sh @@ -6,6 +6,7 @@ cp sd-ui-files/scripts/bootstrap.sh scripts/ cp sd-ui-files/scripts/check_modules.py scripts/ cp sd-ui-files/scripts/get_config.py scripts/ cp sd-ui-files/scripts/config.yaml.sample scripts/ + source ./scripts/functions.sh @@ -20,6 +21,10 @@ if [ -e "open_dev_console.sh" ]; then rm "open_dev_console.sh" fi +if [ -e "ui/plugins/ui/merge.plugin.js" ]; then + rm "ui/plugins/ui/merge.plugin.js" +fi + # set the correct installer path (current vs legacy) if [ -e "installer_files/env" ]; then export INSTALL_ENV_DIR="$(pwd)/installer_files/env" From cc2666b9d6d5f027afc17b347f5e9fd761e12e67 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Sun, 24 Sep 2023 22:06:30 +0200 Subject: [PATCH 12/16] Fix 'Swap w&h' tooltip --- ui/index.html | 4 +++- ui/media/css/main.css | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ui/index.html b/ui/index.html index 96e93ca7..a830ce70 100644 --- a/ui/index.html +++ b/ui/index.html @@ -291,7 +291,9 @@ - Swap width and height +
    + Swap width and height +