From 3dc62a88574f0f40a0400368fc934a7b4574c995 Mon Sep 17 00:00:00 2001
From: JeLuF <jf@mormo.org>
Date: Thu, 22 Jun 2023 23:48:55 +0200
Subject: [PATCH 1/5] Basic embeddings support

---
 ui/easydiffusion/model_manager.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/ui/easydiffusion/model_manager.py b/ui/easydiffusion/model_manager.py
index de2c10ac..a9b42594 100644
--- a/ui/easydiffusion/model_manager.py
+++ b/ui/easydiffusion/model_manager.py
@@ -27,6 +27,7 @@ MODEL_EXTENSIONS = {
     "realesrgan": [".pth"],
     "lora": [".ckpt", ".safetensors"],
     "codeformer": [".pth"],
+    "embeddings": [".pt", ".bin", ".safetensors"],
 }
 DEFAULT_MODELS = {
     "stable-diffusion": [
@@ -58,6 +59,9 @@ def init():
 def load_default_models(context: Context):
     set_vram_optimizations(context)
 
+    config = app.getConfig()
+    context.embeddings_path = os.path.join(app.MODELS_DIR, "embeddings")
+
     # init default model paths
     for model_type in MODELS_TO_LOAD_ON_START:
         context.model_paths[model_type] = resolve_model_to_use(model_type=model_type, fail_if_not_found=False)
@@ -318,6 +322,7 @@ def getModels():
             "hypernetwork": [],
             "lora": [],
             "codeformer": ["codeformer"],
+            "embeddings": [],
         },
     }
 
@@ -374,6 +379,7 @@ def getModels():
     listModels(model_type="hypernetwork")
     listModels(model_type="gfpgan")
     listModels(model_type="lora")
+    listModels(model_type="embeddings")
 
     if models_scanned > 0:
         log.info(f"[green]Scanned {models_scanned} models. Nothing infected[/]")

From 75c57f646d7d70640232bc028fccb277c1cc915c Mon Sep 17 00:00:00 2001
From: JeLuF <jf@mormo.org>
Date: Fri, 30 Jun 2023 08:41:15 +0200
Subject: [PATCH 2/5] embedding support popup

---
 ui/index.html         | 23 ++++++++++++++
 ui/media/css/main.css | 27 ++++++++++++++++
 ui/media/js/main.js   | 71 +++++++++++++++++++++++++++++++++++++++++++
 ui/media/js/utils.js  | 16 ++++++++++
 4 files changed, 137 insertions(+)

diff --git a/ui/index.html b/ui/index.html
index 9565c726..89f62944 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -244,6 +244,9 @@
                         <td><label for="hypernetwork_strength_slider">Hypernetwork Strength:</label></td>
                         <td> <input id="hypernetwork_strength_slider" name="hypernetwork_strength_slider" class="editor-slider" value="100" type="range" min="0" max="100"> <input id="hypernetwork_strength" name="hypernetwork_strength" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"><br/></td>
                     </tr>
+                    <tr class="pl-5"><td><label for="embeddings-button">Embedding:</label></td><td>
+                        <button id="embeddings-button" class="tertiaryButton">Add embedding to prompt</button>
+                    </td></tr>
                     <tr id="tiling_container" class="pl-5"><td><label for="tiling">Seamless Tiling:</label></td><td>
                         <select id="tiling" name="tiling">
                             <option value="none" selected>None</option>
@@ -556,6 +559,26 @@
         </div>
     </dialog>
 
+    <dialog id="embeddings-dialog">
+        <div id="embeddings-dialog-header" class="dialog-header">
+            <div id="embeddings-dialog-header-left" class="dialog-header-left">
+                <h4>Embeddings</h4>
+                <span>Add embeddings to the prompt (click) or negative prompt (shift-click)</span>
+            </div>
+            <div id="embeddings-dialog-header-right">
+                <i id="embeddings-dialog-close-button" class="fa-solid fa-xmark fa-lg"></i>
+            </div>
+        </div>
+        <div>
+            <i class="fa-solid fa-magnifying-glass"></i>
+            <input id="embeddings-search-box" type="text" spellcheck="false" autocomplete="off">
+            <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 id="embeddings-list">
+        </div>
+        </div>
+    </dialog>
+
     <div id="image-editor" class="popup image-editor-popup">
         <div>
             <i class="close-button fa-solid fa-xmark"></i>
diff --git a/ui/media/css/main.css b/ui/media/css/main.css
index 922aabff..3104fd2d 100644
--- a/ui/media/css/main.css
+++ b/ui/media/css/main.css
@@ -1631,3 +1631,30 @@ body.wait-pause {
         bottom: 0;
     }
 }
+
+#embeddings-button {
+    background-color: var(--background-color3);
+}
+
+#embeddings-button:hover {
+    background-color: var(--button-hover-background);
+}
+
+#embeddings-dialog {
+    overflow: clip;
+}
+
+#embeddings-list {
+    height: 70vH;
+    width: 40vW;
+    overflow-y: scroll;
+}
+
+#embeddings-list button {
+    margin-top: 2px;
+    margin-bottom: 2px;
+}
+
+#embeddings-list::-webkit-scrollbar-thumb {
+  background: var(--background-color3);
+}
diff --git a/ui/media/js/main.js b/ui/media/js/main.js
index 20299b10..48080281 100644
--- a/ui/media/js/main.js
+++ b/ui/media/js/main.js
@@ -116,6 +116,12 @@ let streamImageProgressField = document.querySelector("#stream_image_progress")
 let thumbnailSizeField = document.querySelector("#thumbnail_size-input")
 let autoscrollBtn = document.querySelector("#auto_scroll_btn")
 let autoScroll = document.querySelector("#auto_scroll")
+let embeddingsButton = document.querySelector("#embeddings-button")
+let embeddingsDialog = document.querySelector("#embeddings-dialog")
+let embeddingsDialogCloseBtn = embeddingsDialog.querySelector("#embeddings-dialog-close-button")
+let embeddingsSearchBox = document.querySelector("#embeddings-search-box")
+let embeddingsList = document.querySelector("#embeddings-list")
+let embeddingsModeField = document.querySelector("#embeddings-mode")
 
 let makeImageBtn = document.querySelector("#makeImage")
 let stopImageBtn = document.querySelector("#stopImage")
@@ -2129,6 +2135,71 @@ document.getElementById("toggle-cloudflare-tunnel").addEventListener("click", as
     console.log(`Cloudflare tunnel ${command} result:`, res)
 })
 
+/* Embeddings */
+
+function updateEmbeddingsList(filter="") {
+    function html(model, prefix="", filter="") {
+        filter = filter.toLowerCase()
+        let toplevel=""
+        let folders=""
+       
+        model?.forEach( m => {
+            if (typeof(m) == "string") {
+                if (m.toLowerCase().search(filter)!=-1) {
+                    toplevel += `<button data-embedding="${m}">${m}</button> `
+                }
+            } else {
+                let subdir = html(m[1], prefix+m[0]+"/", filter)
+                if (subdir != "") {
+                   folders += `<h4>${prefix}${m[0]}</h4>` + subdir
+                }
+            }
+        })
+        return toplevel + folders
+    }
+
+    function onButtonClick(e) {
+        let text = e.target.dataset["embedding"]
+        console.log(e.shiftKey, text)
+
+        if (embeddingsModeField.value == "insert") {
+            if (e.shiftKey) {
+                insertAtCursor(negativePromptField, text)
+            } else {
+                insertAtCursor(promptField, text)
+            }
+        } else {
+            let pad=""
+            if (e.shiftKey) {
+                if (!negativePromptField.value.endsWith(" ")) {
+                    pad = " "
+                }
+                negativePromptField += pad + text
+            } else {
+                if (!promptField.value.endsWith(" ")) {
+                    pad = " "
+                }
+                promptField += pad + text
+            }
+        }
+    }
+
+    embeddingsList.innerHTML = html(modelsOptions.embeddings, "", filter)
+    embeddingsList.querySelectorAll("button").forEach( (b) => { b.addEventListener("click", onButtonClick)})
+}
+
+embeddingsButton.addEventListener("click", () => { 
+    updateEmbeddingsList()
+    embeddingsSearchBox.value=""
+    embeddingsDialog.showModal() 
+})
+embeddingsDialogCloseBtn.addEventListener("click", (e) => {
+    embeddingsDialog.close()
+})
+embeddingsSearchBox.addEventListener("input", (e) => {
+    updateEmbeddingsList(embeddingsSearchBox.value)
+})
+
 /* Pause function */
 document.querySelectorAll(".tab").forEach(linkTabContents)
 
diff --git a/ui/media/js/utils.js b/ui/media/js/utils.js
index 4c65bd00..cabbc9eb 100644
--- a/ui/media/js/utils.js
+++ b/ui/media/js/utils.js
@@ -997,6 +997,22 @@ async function getStorageData(key) {
     });
 }
 
+function insertAtCursor(field, text) {
+    if (field.selectionStart || field.selectionStart == "0") {
+        var startPos = field.selectionStart
+        var endPos = field.selectionEnd
+        var before = field.value.substring(0, startPos)
+        var after = field.value.substring(endPos, field.value.length)
+
+        if (!before.endsWith(" ")) { before += " " }
+        if (!after.startsWith(" ")) { after = " "+after }
+
+        field.value = before + text + after
+    } else {
+        field.value += text
+    }
+}
+
 // indexedDB debug functions
 async function getAllKeys() {
     return openDB().then(db => {

From fa6716345d56464bcc809136e57785dd1082ee18 Mon Sep 17 00:00:00 2001
From: JeLuF <jf@mormo.org>
Date: Fri, 30 Jun 2023 20:17:46 +0200
Subject: [PATCH 3/5] hide embeddings UI if not test_diffusers

---
 ui/index.html             | 2 +-
 ui/media/js/main.js       | 4 ++++
 ui/media/js/parameters.js | 1 +
 3 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/ui/index.html b/ui/index.html
index 5b6c5300..3ae54e0e 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -244,7 +244,7 @@
                         <td><label for="hypernetwork_strength_slider">Hypernetwork Strength:</label></td>
                         <td> <input id="hypernetwork_strength_slider" name="hypernetwork_strength_slider" class="editor-slider" value="100" type="range" min="0" max="100"> <input id="hypernetwork_strength" name="hypernetwork_strength" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"><br/></td>
                     </tr>
-                    <tr class="pl-5"><td><label for="embeddings-button">Embedding:</label></td><td>
+                    <tr id="embeddings-container" class="pl-5 displayNone"><td><label for="embeddings-button">Embedding:</label></td><td>
                         <button id="embeddings-button" class="tertiaryButton">Add embedding to prompt</button>
                     </td></tr>
                     <tr id="tiling_container" class="pl-5"><td><label for="tiling">Seamless Tiling:</label></td><td>
diff --git a/ui/media/js/main.js b/ui/media/js/main.js
index 48080281..c5f5bc52 100644
--- a/ui/media/js/main.js
+++ b/ui/media/js/main.js
@@ -2200,6 +2200,10 @@ embeddingsSearchBox.addEventListener("input", (e) => {
     updateEmbeddingsList(embeddingsSearchBox.value)
 })
 
+if (testDiffusers.checked) {
+    document.getElementById("embeddings-container").classList.remove("displayNone")
+}
+
 /* Pause function */
 document.querySelectorAll(".tab").forEach(linkTabContents)
 
diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js
index cd86be63..cfe4f3a8 100644
--- a/ui/media/js/parameters.js
+++ b/ui/media/js/parameters.js
@@ -441,6 +441,7 @@ async function getAppConfig() {
                 option.disabled = true
             })
             document.querySelector("#clip_skip_config").classList.remove("displayNone")
+            document.querySelector("#embeddings-container").classList.remove("displayNone")
         }
 
         console.log("get config status response", config)

From 0d9d01c9f5efd6335b8d4c63d518ed064b4de5e3 Mon Sep 17 00:00:00 2001
From: JeLuF <jf@mormo.org>
Date: Fri, 30 Jun 2023 23:28:24 +0200
Subject: [PATCH 4/5] backdrop and drag handlers

---
 ui/index.html       | 2 +-
 ui/media/js/main.js | 4 ++++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/ui/index.html b/ui/index.html
index ced88a08..979a7de9 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -577,7 +577,7 @@
         </div>
         <div>
             <i class="fa-solid fa-magnifying-glass"></i>
-            <input id="embeddings-search-box" type="text" spellcheck="false" autocomplete="off">
+            <input id="embeddings-search-box" type="text" spellcheck="false" autocomplete="off" placeholder="Search...">
             <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 id="embeddings-list">
diff --git a/ui/media/js/main.js b/ui/media/js/main.js
index 0cf6c075..9798c0e9 100644
--- a/ui/media/js/main.js
+++ b/ui/media/js/main.js
@@ -2202,6 +2202,10 @@ embeddingsSearchBox.addEventListener("input", (e) => {
     updateEmbeddingsList(embeddingsSearchBox.value)
 })
 
+modalDialogCloseOnBackdropClick(embeddingsDialog)
+makeDialogDraggable(embeddingsDialog)
+
+
 if (testDiffusers.checked) {
     document.getElementById("embeddings-container").classList.remove("displayNone")
 }

From 21946ff824192a9c83edf92a7c7ab79e7af90f2d Mon Sep 17 00:00:00 2001
From: JeLuF <jf@mormo.org>
Date: Fri, 7 Jul 2023 22:49:21 +0200
Subject: [PATCH 5/5] Update ui/media/js/main.js

Co-authored-by: rbertus2000 <91765399+rbertus2000@users.noreply.github.com>
---
 ui/media/js/main.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/ui/media/js/main.js b/ui/media/js/main.js
index 9798c0e9..eee65cb8 100644
--- a/ui/media/js/main.js
+++ b/ui/media/js/main.js
@@ -2176,12 +2176,12 @@ function updateEmbeddingsList(filter="") {
                 if (!negativePromptField.value.endsWith(" ")) {
                     pad = " "
                 }
-                negativePromptField += pad + text
+                negativePromptField.value += pad + text
             } else {
                 if (!promptField.value.endsWith(" ")) {
                     pad = " "
                 }
-                promptField += pad + text
+                promptField.value += pad + text
             }
         }
     }