merge latest version of bucketlite branch

This commit is contained in:
JeLuF 2023-08-09 16:38:00 +02:00
commit 8dce1ed858
12 changed files with 1609 additions and 40 deletions

View File

@ -712,3 +712,31 @@ FileSaver.js is licensed under the MIT license:
SOFTWARE. SOFTWARE.
[1]: http://eligrey.com [1]: http://eligrey.com
croppr.js
=========
https://github.com/jamesssooi/Croppr.js
croppr.js is licensed under the MIT license:
MIT License
Copyright (c) 2017 James Ooi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -71,8 +71,7 @@ def init():
bucket = crud.get_bucket_by_path(db, path) bucket = crud.get_bucket_by_path(db, path)
if bucket == None: if bucket == None:
bucket_id = crud.create_bucket(db=db, bucket=schemas.BucketCreate(path=path)) bucket = crud.create_bucket(db=db, bucket=schemas.BucketCreate(path=path))
else:
bucket_id = bucket.id bucket_id = bucket.id
bucketfile = schemas.BucketFileCreate(filename=filename, data=file) bucketfile = schemas.BucketFileCreate(filename=filename, data=file)

View File

@ -19,7 +19,6 @@ def create_bucketfile(db: Session, bucketfile: schemas.BucketFileCreate, bucket_
db_bucketfile = models.BucketFile(**bucketfile.dict(), bucket_id=bucket_id) db_bucketfile = models.BucketFile(**bucketfile.dict(), bucket_id=bucket_id)
db.merge(db_bucketfile) db.merge(db_bucketfile)
db.commit() db.commit()
from pprint import pprint
db_bucketfile = db.query(models.BucketFile).filter(models.BucketFile.bucket_id==bucket_id, models.BucketFile.filename==bucketfile.filename).first() db_bucketfile = db.query(models.BucketFile).filter(models.BucketFile.bucket_id==bucket_id, models.BucketFile.filename==bucketfile.filename).first()
return db_bucketfile return db_bucketfile

View File

@ -7,7 +7,6 @@ from sqlalchemy.orm import sessionmaker
os.makedirs(app.BUCKET_DIR, exist_ok=True) os.makedirs(app.BUCKET_DIR, exist_ok=True)
SQLALCHEMY_DATABASE_URL = "sqlite:///"+os.path.join(app.BUCKET_DIR, "bucket.db") SQLALCHEMY_DATABASE_URL = "sqlite:///"+os.path.join(app.BUCKET_DIR, "bucket.db")
print("## SQLALCHEMY_DATABASE_URL = ", SQLALCHEMY_DATABASE_URL)
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}) engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

View File

@ -18,12 +18,14 @@
<link rel="stylesheet" href="/media/css/image-modal.css"> <link rel="stylesheet" href="/media/css/image-modal.css">
<link rel="stylesheet" href="/media/css/plugins.css"> <link rel="stylesheet" href="/media/css/plugins.css">
<link rel="stylesheet" href="/media/css/animations.css"> <link rel="stylesheet" href="/media/css/animations.css">
<link rel="stylesheet" href="/media/css/croppr.css" rel="stylesheet"/>
<link rel="manifest" href="/media/manifest.webmanifest"> <link rel="manifest" href="/media/manifest.webmanifest">
<script src="/media/js/jquery-3.6.1.min.js"></script> <script src="/media/js/jquery-3.6.1.min.js"></script>
<script src="/media/js/jquery-confirm.min.js"></script> <script src="/media/js/jquery-confirm.min.js"></script>
<script src="/media/js/jszip.min.js"></script> <script src="/media/js/jszip.min.js"></script>
<script src="/media/js/FileSaver.min.js"></script> <script src="/media/js/FileSaver.min.js"></script>
<script src="/media/js/marked.min.js"></script> <script src="/media/js/marked.min.js"></script>
<script src="/media/js/croppr.js"></script>
</head> </head>
<body> <body>
<div id="container"> <div id="container">
@ -686,6 +688,15 @@
</button> </button>
<i class="fa-solid fa-magnifying-glass"></i> <i class="fa-solid fa-magnifying-glass"></i>
<input id="embeddings-search-box" type="text" spellcheck="false" autocomplete="off" placeholder="Search..."> <input id="embeddings-search-box" type="text" spellcheck="false" autocomplete="off" placeholder="Search...">
<label for="embedding-card-size-selector"><small>Thumbnail Size:</small></label>
<select id="embedding-card-size-selector" name="embedding-card-size-selector">
<option value="-2">0</option>
<option value="-1">1</option>
<option value="0">2</option>
<option value="1" selected>3</option>
<option value="2">4</option>
<option value="3">5</option>
</select>
<span style="float:right;"><label>Mode:</label>&nbsp;<select id="embeddings-mode"><option value="insert">Insert at cursor position</option><option value="append">Append at the end</option></select> <span style="float:right;"><label>Mode:</label>&nbsp;<select id="embeddings-mode"><option value="insert">Insert at cursor position</option><option value="append">Append at the end</option></select>
</div> </div>
<div id="embeddings-list"> <div id="embeddings-list">
@ -693,6 +704,34 @@
</div> </div>
</dialog> </dialog>
<dialog id="use-as-thumb-dialog">
<div id="use-as-thumb-dialog-header" class="dialog-header">
<div id="use-as-thumb-dialog-header-left" class="dialog-header-left">
<h4>Use as thumbnail</h4>
<span>Use a pictures as thumbnail for embeddings, LORAs, etc.</span>
</div>
<div id="use-as-thumb-dialog-header-right">
<i id="use-as-thumb-dialog-close-button" class="fa-solid fa-xmark fa-lg"></i>
</div>
</div>
<div>
<div class="use-as-thumb-grid">
<div class="use-as-thumb-preview">
<div id="use-as-thumb-img-container"><img id="use-as-thumb-image" src="/media/images/noimg.png" width="512" height="512"></div>
</div>
<div class="use-as-thumb-select">
<label for="use-as-thumb-select">Use the thumbnail for:</label><br>
<select id="use-as-thumb-select" size="16" multiple>
</select>
</div>
<div class="use-as-thumb-buttons">
<button class="tertiaryButton" id="use-as-thumb-save">Save thumbnail</button>
<button class="tertiaryButton" id="use-as-thumb-cancel">Cancel</button>
</div>
</div>
</div>
</dialog>
<div id="image-editor" class="popup image-editor-popup"> <div id="image-editor" class="popup image-editor-popup">
<div> <div>
<i class="close-button fa-solid fa-xmark"></i> <i class="close-button fa-solid fa-xmark"></i>

58
ui/media/css/croppr.css Normal file
View File

@ -0,0 +1,58 @@
.croppr-container * {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
}
.croppr-container img {
vertical-align: middle;
max-width: 100%;
}
.croppr {
position: relative;
display: inline-block;
}
.croppr-overlay {
background: rgba(0,0,0,0.5);
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
cursor: crosshair;
}
.croppr-region {
border: 1px dashed rgba(0, 0, 0, 0.5);
position: absolute;
z-index: 3;
cursor: move;
top: 0;
}
.croppr-imageClipped {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2;
pointer-events: none;
}
.croppr-handle {
border: 1px solid black;
background-color: white;
width: 10px;
height: 10px;
position: absolute;
z-index: 4;
top: 0;
}

View File

@ -1650,6 +1650,35 @@ body.wait-pause {
} }
} }
.spinner-container {
width: 80px;
height: 100px;
margin: 100px auto;
margin-top: 30vH;
}
.spinner-block {
position: relative;
box-sizing: border-box;
float: left;
margin: 0 10px 10px 0;
width: 12px;
height: 12px;
border-radius: 3px;
background: var(--accent-color);
}
.spinner-block:nth-child(4n+1) { animation: spinner-wave 2s ease .0s infinite; }
.spinner-block:nth-child(4n+2) { animation: spinner-wave 2s ease .2s infinite; }
.spinner-block:nth-child(4n+3) { animation: spinner-wave 2s ease .4s infinite; }
.spinner-block:nth-child(4n+4) { animation: spinner-wave 2s ease .6s infinite; margin-right: 0; }
@keyframes spinner-wave {
0% { top: 0; opacity: 1; }
50% { top: 30px; opacity: .2; }
100% { top: 0; opacity: 1; }
}
#embeddings-dialog { #embeddings-dialog {
overflow: clip; overflow: clip;
} }
@ -1741,6 +1770,32 @@ body.wait-pause {
float: right; float: right;
} }
.use-as-thumb-grid { display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: 1fr auto;
gap: 8px 8px;
grid-auto-flow: row;
grid-template-areas:
"uat-preview uat-select"
"uat-preview uat-buttons";
}
.use-as-thumb-preview {
justify-self: center;
align-self: center;
grid-area: uat-preview;
}
.use-as-thumb-select {
grid-area: uat-select;
}
.use-as-thumb-buttons {
justify-self: center;
grid-area: uat-buttons;
}
.diffusers-disabled-on-startup .diffusers-restart-needed { .diffusers-disabled-on-startup .diffusers-restart-needed {
font-size: 0; font-size: 0;
} }
@ -1832,6 +1887,7 @@ div#enlarge-buttons {
#imageTagPopupContainer { #imageTagPopupContainer {
position: absolute; position: absolute;
} }
/* Gallery CSS */ /* Gallery CSS */
#imagecontainer { #imagecontainer {
display: flex; display: flex;

View File

@ -45,6 +45,7 @@ const SETTINGS_IDS_LIST = [
"sound_toggle", "sound_toggle",
"vram_usage_level", "vram_usage_level",
"confirm_dangerous_actions", "confirm_dangerous_actions",
"profileName",
"metadata_output_format", "metadata_output_format",
"auto_save_settings", "auto_save_settings",
"apply_color_correction", "apply_color_correction",
@ -55,6 +56,7 @@ const SETTINGS_IDS_LIST = [
"tree_toggle", "tree_toggle",
"json_toggle", "json_toggle",
"extract_lora_from_prompt", "extract_lora_from_prompt",
"embedding-card-size-selector",
] ]
const IGNORE_BY_DEFAULT = ["prompt"] const IGNORE_BY_DEFAULT = ["prompt"]

1189
ui/media/js/croppr.js Executable file

File diff suppressed because it is too large Load Diff

View File

@ -141,6 +141,7 @@ let embeddingsDialogCloseBtn = embeddingsDialog.querySelector("#embeddings-dialo
let embeddingsSearchBox = document.querySelector("#embeddings-search-box") let embeddingsSearchBox = document.querySelector("#embeddings-search-box")
let embeddingsList = document.querySelector("#embeddings-list") let embeddingsList = document.querySelector("#embeddings-list")
let embeddingsModeField = document.querySelector("#embeddings-mode") let embeddingsModeField = document.querySelector("#embeddings-mode")
let embeddingsCardSizeSelector = document.querySelector("#embedding-card-size-selector")
let positiveEmbeddingText = document.querySelector("#positive-embedding-text") let positiveEmbeddingText = document.querySelector("#positive-embedding-text")
let negativeEmbeddingText = document.querySelector("#negative-embedding-text") let negativeEmbeddingText = document.querySelector("#negative-embedding-text")
@ -170,6 +171,12 @@ let saveAllTreeToggle = document.querySelector("#tree_toggle")
let saveAllJSONToggle = document.querySelector("#json_toggle") let saveAllJSONToggle = document.querySelector("#json_toggle")
let saveAllFoldersOption = document.querySelector("#download-add-folders") let saveAllFoldersOption = document.querySelector("#download-add-folders")
let splashScreenPopup = document.querySelector("#splash-screen") let splashScreenPopup = document.querySelector("#splash-screen")
let useAsThumbDialog = document.querySelector("#use-as-thumb-dialog")
let useAsThumbDialogCloseBtn = document.querySelector("#use-as-thumb-dialog-close-button")
let useAsThumbImageContainer = document.querySelector("#use-as-thumb-img-container")
let useAsThumbSelect = document.querySelector("#use-as-thumb-select")
let useAsThumbSaveBtn = document.querySelector("#use-as-thumb-save")
let useAsThumbCancelBtn = document.querySelector("#use-as-thumb-cancel")
let maskSetting = document.querySelector("#enable_mask") let maskSetting = document.querySelector("#enable_mask")
@ -678,19 +685,126 @@ function onMakeSimilarClick(req, img) {
createTask(newTaskRequest) createTask(newTaskRequest)
} }
// gets a flat list of all models of a certain type, ignoring directories
function getAllModelNames(type) {
function f(tree) {
if (tree == undefined) {
return []
}
let result=[];
tree.forEach( e => {
if (typeof(e) == "object") {
result = result.concat( f(e[1]))
} else {
result.push(e)
}
});
return result
}
return f(modelsOptions[type])
}
function onUseAsThumbnailClick(req, img) { function onUseAsThumbnailClick(req, img) {
console.log(req) let scale = 1
console.log(img) let targetWidth = img.naturalWidth
let embedding = prompt("Embedding name") let targetHeight = img.naturalHeight
fetch(img.src) let resize = false
onUseAsThumbnailClick.img = img
if ( typeof(onUseAsThumbnailClick.croppr) == 'undefined' ) {
onUseAsThumbnailClick.croppr = new Croppr("#use-as-thumb-image", { aspectRatio: 1, minSize: [384,384,'px'], startSize: [512, 512, 'px'], returnMode:"real" })
}
if (img.naturalWidth > img.naturalHeight) {
if (img.naturalWidth > 768) {
scale = 768 / img.naturalWidth
targetWidth = 768
targetHeight = (img.naturalHeight*scale)>>>0
resize = true
}
} else {
if (img.naturalHeight > 768) {
scale = 768 / img.naturalHeight
targetHeight = 768
targetWidth = (img.naturalWidth*scale)>>>0
resize = true
}
}
onUseAsThumbnailClick.croppr.options.minSize = {width: 384*scale>>>0, height: 384*scale>>>0}
onUseAsThumbnailClick.croppr.options.startSize = {width: 512*scale>>>0, height: 512*scale>>>0}
if (resize) {
const canvas = document.createElement('canvas')
canvas.width = targetWidth
canvas.height = targetHeight
const ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0, targetWidth, targetHeight)
onUseAsThumbnailClick.croppr.setImage(canvas.toDataURL('image/png'))
} else {
onUseAsThumbnailClick.croppr.setImage(img.src)
}
let embeddings = getAllModelNames("embeddings").filter( e => req.prompt.includes(e) || req.negative_prompt.includes(e) )
let LORA = []
if ("use_lora_model" in req) {
LORA=req.use_lora_model
}
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)
useAsThumbDialog.showModal()
onUseAsThumbnailClick.scale = scale
}
modalDialogCloseOnBackdropClick(useAsThumbDialog)
makeDialogDraggable(useAsThumbDialog)
useAsThumbDialogCloseBtn.addEventListener("click", () => {
useAsThumbDialog.close()
})
useAsThumbCancelBtn.addEventListener("click", () => {
useAsThumbDialog.close()
})
useAsThumbSaveBtn.addEventListener("click", (e) => {
let scale = 1/onUseAsThumbnailClick.scale
let crop = onUseAsThumbnailClick.croppr.getValue()
let len = Math.max(crop.width*scale, 384)
let profileName = profileNameField.value
cropImageDataUrl(onUseAsThumbnailClick.img.src, crop.x*scale, crop.y*scale, len, len)
.then(thumb => fetch(thumb))
.then(response => response.blob()) .then(response => response.blob())
.then(async function(blob) { .then(async function(blob) {
const formData = new FormData() const formData = new FormData()
formData.append("file", blob) formData.append("file", blob)
const response = await fetch(`bucket/embeddings/${embedding}.jpg`, { method: 'POST', body: formData }); let options = useAsThumbSelect.selectedOptions
console.log(response) let promises = []
}) for (let embedding of options) {
promises.push(fetch(`bucket/${profileName}/${embedding.dataset["type"]}/${embedding.value}.png`, { method: 'POST', body: formData }))
} }
return Promise.all(promises)
}).then(() => {
useAsThumbDialog.close()
})
.catch(error => {
console.error(error)
showToast("Couldn't save thumbnail.<br>"+error)
})
})
function enqueueImageVariationTask(req, img, reqDiff) { function enqueueImageVariationTask(req, img, reqDiff) {
const imageSeed = img.getAttribute("data-seed") const imageSeed = img.getAttribute("data-seed")
@ -2524,40 +2638,53 @@ document.getElementById("toggle-tensorrt-install").addEventListener("click", fun
/* Embeddings */ /* Embeddings */
let icl = []
function updateEmbeddingsList(filter = "") { function updateEmbeddingsList(filter = "") {
function html(model, iconlist = [], prefix = "", filter = "") { function html(model, iconlist = [], prefix = "", filter = "") {
filter = filter.toLowerCase() filter = filter.toLowerCase()
let toplevel = "" let toplevel = document.createElement("div")
let folders = "" let folders = document.createElement("div")
console.log(iconlist)
let embIcon = Object.assign({}, ...iconlist.map( x=> ({[x.toLowerCase().split('.').slice(0,-1).join('.')]:x}))) let embIcon = Object.assign({}, ...iconlist.map( x=> ({[x.toLowerCase().split('.').slice(0,-1).join('.')]:x})))
let profileName = profileNameField.value
model?.forEach((m) => { model?.forEach((m) => {
if (typeof m == "string") { if (typeof m == "string") {
let token=m.toLowerCase() let token=m.toLowerCase()
if (token.search(filter) != -1) { 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' let img = '/media/images/noimg.png'
if (token in embIcon) { if (token in embIcon) {
img = `/bucket/embeddings/${embIcon[token]}` img = `/bucket/${profileName}/embeddings/${embIcon[token]}`
} }
toplevel += `<button data-embedding="${m}"><img src="${img}" height="128" width="128"><br>${m}</button> ` button = createModifierCard(m, [img,img], true)
}
button.dataset["embedding"] = m
button.addEventListener("click", onButtonClick)
toplevel.appendChild(button)
} }
} else { } else {
let subdir = html(m[1], iconlist, prefix + m[0] + "/", filter) let subdir = html(m[1], iconlist, prefix + m[0] + "/", filter)
if (subdir != "") { if (typeof(subdir) == "object") {
folders += let div1 = document.createElement("div")
`<div class="embedding-category"><h4 class="collapsible">${prefix}${m[0]}</h4><div class="collapsible-content">` + let div2 = document.createElement("div")
subdir + div1.classList.add("collapsible-content")
"</div></div>" div1.classList.add("embedding-category")
div1.appendChild(subdir)
div2.replaceChildren(htmlToElement(`<h4 class="collapsible">${prefix}${m[0]}</h4>`), div1)
folders.appendChild(div2)
} }
} }
}) })
return toplevel + folders let result = document.createElement("div")
result.replaceChildren(toplevel, htmlToElement('<br style="clear: both;">'), folders)
return result
} }
function onButtonClick(e) { function onButtonClick(e) {
let text = e.target.closest("button").dataset["embedding"] let text = e.target.closest("[data-embedding]").dataset["embedding"]
const insertIntoNegative = e.shiftKey || positiveEmbeddingText.classList.contains("displayNone") const insertIntoNegative = e.shiftKey || positiveEmbeddingText.classList.contains("displayNone")
if (embeddingsModeField.value == "insert") { if (embeddingsModeField.value == "insert") {
@ -2582,8 +2709,18 @@ function updateEmbeddingsList(filter = "") {
} }
} }
// Usually the rendering of the Embeddings HTML takes less than a second. In case it takes longer, show a spinner
embeddingsList.innerHTML = `
<div class="spinner-container">
<div class="spinner-block"></div> <div class="spinner-block"></div> <div class="spinner-block"></div> <div class="spinner-block"></div>
<div class="spinner-block"></div> <div class="spinner-block"></div> <div class="spinner-block"></div> <div class="spinner-block"></div>
<div class="spinner-block"></div> <div class="spinner-block"></div> <div class="spinner-block"></div> <div class="spinner-block"></div>
<div class="spinner-block"></div> <div class="spinner-block"></div> <div class="spinner-block"></div> <div class="spinner-block"></div>
</div>
`
// Remove after fixing https://github.com/huggingface/diffusers/issues/3922 // Remove after fixing https://github.com/huggingface/diffusers/issues/3922
let warning = "" let warning = "<div></div>"
if (vramUsageLevelField.value == "low") { if (vramUsageLevelField.value == "low") {
warning = ` warning = `
<div style="border-color: var(--accent-color); border-width: 4px; border-radius: 1em; border-style: solid; background: black; text-align: center; padding: 1em; margin: 1em; "> <div style="border-color: var(--accent-color); border-width: 4px; border-radius: 1em; border-style: solid; background: black; text-align: center; padding: 1em; margin: 1em; ">
@ -2592,17 +2729,17 @@ function updateEmbeddingsList(filter = "") {
} }
// END of remove block // END of remove block
fetch("/bucket/embeddings/") let profileName = profileNameField.value
.then(response => response.json()) fetch(`/bucket/${profileName}/embeddings/`)
.then(iconlist => { .then(response => response.status==200 ? response.json(): [])
embeddingsList.innerHTML = warning + html(modelsOptions.embeddings, iconlist, "", filter) .then(async function(iconlist) {
embeddingsList.querySelectorAll("button").forEach((b) => {
b.addEventListener("click", onButtonClick) embeddingsList.replaceChildren(htmlToElement(warning), html(modelsOptions.embeddings, iconlist, "", filter))
})
createCollapsibles(embeddingsList) createCollapsibles(embeddingsList)
if (filter != "") { if (filter != "") {
embeddingsExpandAll() embeddingsExpandAll()
} }
resizeModifierCards(embeddingsCardSizeSelector.value)
}) })
} }
@ -2611,23 +2748,33 @@ function showEmbeddingDialog() {
embeddingsSearchBox.value = "" embeddingsSearchBox.value = ""
embeddingsDialog.showModal() embeddingsDialog.showModal()
} }
embeddingsButton.addEventListener("click", () => { embeddingsButton.addEventListener("click", () => {
positiveEmbeddingText.classList.remove("displayNone") positiveEmbeddingText.classList.remove("displayNone")
negativeEmbeddingText.classList.add("displayNone") negativeEmbeddingText.classList.add("displayNone")
showEmbeddingDialog() showEmbeddingDialog()
}) })
negativeEmbeddingsButton.addEventListener("click", () => { negativeEmbeddingsButton.addEventListener("click", () => {
positiveEmbeddingText.classList.add("displayNone") positiveEmbeddingText.classList.add("displayNone")
negativeEmbeddingText.classList.remove("displayNone") negativeEmbeddingText.classList.remove("displayNone")
showEmbeddingDialog() showEmbeddingDialog()
}) })
embeddingsDialogCloseBtn.addEventListener("click", (e) => { embeddingsDialogCloseBtn.addEventListener("click", (e) => {
embeddingsDialog.close() embeddingsDialog.close()
}) })
embeddingsSearchBox.addEventListener("input", (e) => { embeddingsSearchBox.addEventListener("input", (e) => {
updateEmbeddingsList(embeddingsSearchBox.value) updateEmbeddingsList(embeddingsSearchBox.value)
}) })
embeddingsCardSizeSelector.addEventListener("change", (e) => {
resizeModifierCards(embeddingsCardSizeSelector.value)
})
modalDialogCloseOnBackdropClick(embeddingsDialog) modalDialogCloseOnBackdropClick(embeddingsDialog)
makeDialogDraggable(embeddingsDialog) makeDialogDraggable(embeddingsDialog)

View File

@ -194,6 +194,16 @@ var PARAMETERS = [
icon: "fa-check-double", icon: "fa-check-double",
default: true, default: true,
}, },
{
id: "profileName",
type: ParameterType.custom,
label: "Profile Name",
note: "Name of the profile for model manager settings, e.g. thumbnails for embeddings. Use this to have different settings for different users.",
render: (parameter) => {
return `<input id="${parameter.id}" name="${parameter.id}" value="default" size="12">`
},
icon: "fa-user-gear",
},
{ {
id: "listen_to_network", id: "listen_to_network",
type: ParameterType.checkbox, type: ParameterType.checkbox,
@ -410,6 +420,7 @@ let useBetaChannelField = document.querySelector("#use_beta_channel")
let uiOpenBrowserOnStartField = document.querySelector("#ui_open_browser_on_start") let uiOpenBrowserOnStartField = document.querySelector("#ui_open_browser_on_start")
let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions") let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions")
let testDiffusers = document.querySelector("#test_diffusers") let testDiffusers = document.querySelector("#test_diffusers")
let profileNameField = document.querySelector("#profileName")
let saveSettingsBtn = document.querySelector("#save-system-settings-btn") let saveSettingsBtn = document.querySelector("#save-system-settings-btn")

View File

@ -1097,6 +1097,48 @@ async function deleteKeys(keyToDelete) {
} }
} }
/**
* @param {String} Data URL of the image
* @param {Integer} Top left X-coordinate of the crop area
* @param {Integer} Top left Y-coordinate of the crop area
* @param {Integer} Width of the crop area
* @param {Integer} Height of the crop area
* @return {String}
*/
function cropImageDataUrl(dataUrl, x, y, width, height) {
return new Promise((resolve, reject) => {
const image = new Image()
image.src = dataUrl
image.onload = () => {
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
const ctx = canvas.getContext('2d')
ctx.drawImage(image, x, y, width, height, 0, 0, width, height)
const croppedDataUrl = canvas.toDataURL('image/png')
resolve(croppedDataUrl)
}
image.onerror = (error) => {
reject(error)
}
})
}
/**
* @param {String} HTML representing a single element
* @return {Element}
*/
function htmlToElement(html) {
var template = document.createElement('template');
html = html.trim(); // Never return a text node of whitespace as the result
template.innerHTML = html;
return template.content.firstChild;
}
function modalDialogCloseOnBackdropClick(dialog) { function modalDialogCloseOnBackdropClick(dialog) {
dialog.addEventListener('mousedown', function (event) { dialog.addEventListener('mousedown', function (event) {
// Firefox creates an event with clientX|Y = 0|0 when choosing an <option>. // Firefox creates an event with clientX|Y = 0|0 when choosing an <option>.