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 : []) -}