mirror of
https://github.com/easydiffusion/easydiffusion.git
synced 2024-11-24 17:24:29 +01:00
Lora Manager
This commit is contained in:
parent
74c0ca0902
commit
4aca3c4639
@ -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("/")
|
||||
|
@ -1,4 +1,5 @@
|
||||
import logging
|
||||
import hashlib
|
||||
|
||||
log = logging.getLogger("easydiffusion")
|
||||
|
||||
|
@ -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 = "") {
|
||||
</div>
|
||||
`
|
||||
|
||||
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()
|
||||
|
@ -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 = `
|
||||
<div class="merge-model-container panel-box">
|
||||
<div class="merge-input">
|
||||
<p><label for="#mergeModelA">Select Model A:</label></p>
|
||||
<input id="mergeModelA" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
||||
<p><label for="#mergeModelB">Select Model B:</label></p>
|
||||
<input id="mergeModelB" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
||||
<br/><br/>
|
||||
<p id="merge-warning"><small><b>Important:</b> Please merge models of similar type.<br/>For e.g. <code>SD 1.4</code> models with only <code>SD 1.4/1.5</code> models,<br/><code>SD 2.0</code> with <code>SD 2.0</code>-type, and <code>SD 2.1</code> with <code>SD 2.1</code>-type models.</small></p>
|
||||
<br/>
|
||||
<table>
|
||||
<tr>
|
||||
<td><label for="#merge-filename">Output file name:</label></td>
|
||||
<td><input id="merge-filename" size=24> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Base name of the output file.<br>Mix ratio and file suffix will be appended to this.</span></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="#merge-fp">Output precision:</label></td>
|
||||
<td><select id="merge-fp">
|
||||
<option value="fp16">fp16 (smaller file size)</option>
|
||||
<option value="fp32">fp32 (larger file size)</option>
|
||||
</select>
|
||||
<i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Image generation uses fp16, so it's a good choice.<br>Use fp32 if you want to use the result models for more mixes</span></i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="#merge-format">Output file format:</label></td>
|
||||
<td><select id="merge-format">
|
||||
<option value="safetensors">Safetensors (recommended)</option>
|
||||
<option value="ckpt">CKPT/Pickle (legacy format)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
<div id="merge-log-container">
|
||||
<p><label for="#merge-log">Log messages:</label></p>
|
||||
<div id="merge-log"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="merge-config">
|
||||
<div class="tab-container">
|
||||
<span id="tab-merge-opts-single" class="tab active">
|
||||
<span>Make a single file</small></span>
|
||||
</span>
|
||||
<span id="tab-merge-opts-batch" class="tab">
|
||||
<span>Make multiple variations</small></span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div id="tab-content-merge-opts-single" class="tab-content active">
|
||||
<div class="tab-content-inner">
|
||||
<small>Saves a single merged model file, at the specified merge ratio.</small><br/><br/>
|
||||
<label for="#single-merge-ratio-slider">Merge ratio:</label>
|
||||
<input id="single-merge-ratio-slider" name="single-merge-ratio-slider" class="editor-slider" value="50" type="range" min="1" max="1000">
|
||||
<input id="single-merge-ratio" size=2 value="5">%
|
||||
<i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Model A's contribution to the mix. The rest will be from Model B.</span></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-content-merge-opts-batch" class="tab-content">
|
||||
<div class="tab-content-inner">
|
||||
<small>Saves multiple variations of the model, at different merge ratios.<br/>Each variation will be saved as a separate file.</small><br/><br/>
|
||||
<table>
|
||||
<tr><td><label for="#merge-count">Number of variations:</label></td>
|
||||
<td> <input id="merge-count" size=2 value="5"></td>
|
||||
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Number of models to create</span></i></td></tr>
|
||||
<tr><td><label for="#merge-start">Starting merge ratio:</label></td>
|
||||
<td> <input id="merge-start" size=2 value="5">%</td>
|
||||
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Smallest share of model A in the mix</span></i></td></tr>
|
||||
<tr><td><label for="#merge-step">Increment each step:</label></td>
|
||||
<td> <input id="merge-step" size=2 value="10">%</td>
|
||||
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Share of model A added into the mix per step</span></i></td></tr>
|
||||
<tr><td><label for="#merge-interpolation">Interpolation model:</label></td>
|
||||
<td> <select id="merge-interpolation">
|
||||
<option>Exact</option>
|
||||
<option>SmoothStep</option>
|
||||
<option>SmootherStep</option>
|
||||
<option>SmoothestStep</option>
|
||||
</select></td>
|
||||
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Sigmoid function to be applied to the model share before mixing</span></i></td></tr>
|
||||
</table>
|
||||
<br/>
|
||||
<small>Preview of variation ratios:</small><br/>
|
||||
<canvas id="merge-canvas" width="400" height="400"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="merge-buttons">
|
||||
<button id="merge-button" class="primaryButton">Merge models</button>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
|
||||
let loraUI=`
|
||||
<div class="panel-box lora-manager-grid">
|
||||
<div class="lora-manager-grid-selector">
|
||||
<label for="#loraModel">Select Lora:</label>
|
||||
<input id="loraModel" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
||||
</div>
|
||||
<div class="lora-manager-grid-thumbnail">
|
||||
<p style="height:2em;">Thumbnail:</p>
|
||||
<div style="position:relative; height:256px; width:256px;background-color:#222;border-radius:1em;margin-bottom:1em;">
|
||||
<i id="lora-manager-image-placeholder" class="fa-regular fa-image" style="font-size:500%;color:#555;position:absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);"></i>
|
||||
<img id="lora-manager-image" class="displayNone" style="border-radius:6px;max-height:256px;max-width:256px;"/>
|
||||
</div>
|
||||
<div style="text-align:center;">
|
||||
<button class="tertiaryButton"><i class="fa-solid fa-upload"></i> Upload new thumbnail</button>
|
||||
<button class="tertiaryButton"><i class="fa-solid fa-trash-can"></i> Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lora-manager-grid-keywords">
|
||||
<p style="height:2em;">Keywords:
|
||||
<span style="float:right;margin-bottom:4px;"><button id="lora-keyword-from-civitai" class="tertiaryButton smallButton">Import from Civitai</button></span></p>
|
||||
<textarea style="width:100%;resize:vertical;" id="lora-manager-keywords" placeholder="Put LORA specific keywords here..."></textarea>
|
||||
<p style="color:var(--small-label-color);">
|
||||
<b>LORA model keywords</b> can be used via the <code>+ Embeddings</code> button. They get added to the embedding
|
||||
keyword menu when the LORA has been selected in the image settings.
|
||||
</p>
|
||||
</div>
|
||||
<div class="lora-manager-grid-notes">
|
||||
<p style="height:2em;">Notes:</p>
|
||||
<textarea style="width:100%;resize:vertical;" id="lora-manager-notes" placeholder="Place for things you want to remember..."></textarea>
|
||||
<p id="civitai-section" class="displayNone">
|
||||
<b>Civitai model page:</b>
|
||||
<a id="civitai-model-page" target="_blank"></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
let tabHTML=`
|
||||
<div id="model-tool-tab-bar" class="tab-container tab-centered">
|
||||
<span id="tab-model-loraUI" class="tab active">
|
||||
<span><i class="fa-solid fa-key"></i> Lora Keywords</small></span>
|
||||
</span>
|
||||
<span id="tab-model-mergeUI" class="tab">
|
||||
<span><i class="fa-solid fa-code-merge"></i> Merge Models</small></span>
|
||||
</span>
|
||||
</div>
|
||||
<div id="model-tool-tab-content" class="panel-box">
|
||||
<div id="tab-content-model-loraUI" class="tab-content active">
|
||||
<div class="tab-content-inner">
|
||||
${loraUI}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tab-content-model-mergeUI" class="tab-content">
|
||||
<div class="tab-content-inner">
|
||||
${mergeUI}
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
|
||||
///////////////////// 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("<i>Aborting: maximum ratio is ≥ 100%</i>")
|
||||
addLogMessage("Reduce the number of variations or the step size")
|
||||
addLogSeparator()
|
||||
document.querySelector("#merge-count").focus()
|
||||
return
|
||||
}
|
||||
|
||||
if (document.querySelector("#merge-filename").value == "") {
|
||||
addLogMessage("<i>Aborting: No output file name specified</i>")
|
||||
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(
|
||||
"<b>Done.</b> The models have been saved to your <tt>models/stable-diffusion</tt> 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: `
|
||||
<div class="merge-container panel-box">
|
||||
<div class="merge-input">
|
||||
<p><label for="#mergeModelA">Select Model A:</label></p>
|
||||
<input id="mergeModelA" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
||||
<p><label for="#mergeModelB">Select Model B:</label></p>
|
||||
<input id="mergeModelB" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
||||
<br/><br/>
|
||||
<p id="merge-warning"><small><b>Important:</b> Please merge models of similar type.<br/>For e.g. <code>SD 1.4</code> models with only <code>SD 1.4/1.5</code> models,<br/><code>SD 2.0</code> with <code>SD 2.0</code>-type, and <code>SD 2.1</code> with <code>SD 2.1</code>-type models.</small></p>
|
||||
<br/>
|
||||
<table>
|
||||
<tr>
|
||||
<td><label for="#merge-filename">Output file name:</label></td>
|
||||
<td><input id="merge-filename" size=24> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Base name of the output file.<br>Mix ratio and file suffix will be appended to this.</span></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="#merge-fp">Output precision:</label></td>
|
||||
<td><select id="merge-fp">
|
||||
<option value="fp16">fp16 (smaller file size)</option>
|
||||
<option value="fp32">fp32 (larger file size)</option>
|
||||
</select>
|
||||
<i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Image generation uses fp16, so it's a good choice.<br>Use fp32 if you want to use the result models for more mixes</span></i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="#merge-format">Output file format:</label></td>
|
||||
<td><select id="merge-format">
|
||||
<option value="safetensors">Safetensors (recommended)</option>
|
||||
<option value="ckpt">CKPT/Pickle (legacy format)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
<div id="merge-log-container">
|
||||
<p><label for="#merge-log">Log messages:</label></p>
|
||||
<div id="merge-log"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="merge-config">
|
||||
<div class="tab-container">
|
||||
<span id="tab-merge-opts-single" class="tab active">
|
||||
<span>Make a single file</small></span>
|
||||
</span>
|
||||
<span id="tab-merge-opts-batch" class="tab">
|
||||
<span>Make multiple variations</small></span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div id="tab-content-merge-opts-single" class="tab-content active">
|
||||
<div class="tab-content-inner">
|
||||
<small>Saves a single merged model file, at the specified merge ratio.</small><br/><br/>
|
||||
<label for="#single-merge-ratio-slider">Merge ratio:</label>
|
||||
<input id="single-merge-ratio-slider" name="single-merge-ratio-slider" class="editor-slider" value="50" type="range" min="1" max="1000">
|
||||
<input id="single-merge-ratio" size=2 value="5">%
|
||||
<i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Model A's contribution to the mix. The rest will be from Model B.</span></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-content-merge-opts-batch" class="tab-content">
|
||||
<div class="tab-content-inner">
|
||||
<small>Saves multiple variations of the model, at different merge ratios.<br/>Each variation will be saved as a separate file.</small><br/><br/>
|
||||
<table>
|
||||
<tr><td><label for="#merge-count">Number of variations:</label></td>
|
||||
<td> <input id="merge-count" size=2 value="5"></td>
|
||||
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Number of models to create</span></i></td></tr>
|
||||
<tr><td><label for="#merge-start">Starting merge ratio:</label></td>
|
||||
<td> <input id="merge-start" size=2 value="5">%</td>
|
||||
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Smallest share of model A in the mix</span></i></td></tr>
|
||||
<tr><td><label for="#merge-step">Increment each step:</label></td>
|
||||
<td> <input id="merge-step" size=2 value="10">%</td>
|
||||
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Share of model A added into the mix per step</span></i></td></tr>
|
||||
<tr><td><label for="#merge-interpolation">Interpolation model:</label></td>
|
||||
<td> <select id="merge-interpolation">
|
||||
<option>Exact</option>
|
||||
<option>SmoothStep</option>
|
||||
<option>SmootherStep</option>
|
||||
<option>SmoothestStep</option>
|
||||
</select></td>
|
||||
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Sigmoid function to be applied to the model share before mixing</span></i></td></tr>
|
||||
</table>
|
||||
<br/>
|
||||
<small>Preview of variation ratios:</small><br/>
|
||||
<canvas id="merge-canvas" width="400" height="400"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="merge-buttons">
|
||||
<button id="merge-button" class="primaryButton">Merge models</button>
|
||||
</div>
|
||||
</div>`,
|
||||
}
|
||||
|
||||
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("<i>Aborting: maximum ratio is ≥ 100%</i>")
|
||||
addLogMessage("Reduce the number of variations or the step size")
|
||||
addLogSeparator()
|
||||
document.querySelector("#merge-count").focus()
|
||||
return
|
||||
}
|
||||
|
||||
if (document.querySelector("#merge-filename").value == "") {
|
||||
addLogMessage("<i>Aborting: No output file name specified</i>")
|
||||
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(
|
||||
"<b>Done.</b> The models have been saved to your <tt>models/stable-diffusion</tt> 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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user