From 12fa08d7a7e5546354df1a609cc28b6077575e39 Mon Sep 17 00:00:00 2001 From: ManInDark <61268856+ManInDark@users.noreply.github.com> Date: Tue, 8 Aug 2023 21:45:18 +0200 Subject: [PATCH 01/27] Changed cursor on logo hover to indicate that it can be clicked --- patch.patch | Bin 0 -> 618 bytes ui/media/css/main.css | 1 + 2 files changed, 1 insertion(+) create mode 100644 patch.patch diff --git a/patch.patch b/patch.patch new file mode 100644 index 0000000000000000000000000000000000000000..a452181bff804685e1f8ef1e12eedb642652c74c GIT binary patch literal 618 zcmbV~-D<)>5QWcmq3;kVLM2A63I6rQ*XU(T)C5cnYf1(2)os5?f`wiPmLb{Mvu9?` z?Cke7Rjw`ODZqB@YW!Fued3Q)Xd~kolIVyT7K4-;c8I6-L;PnwHSsCAPgI+*&ZuHv ztOa?8{1eR;X{-=DhV_O&))Xv~WiY+L!&)uUZ|py+S6WdqKvSdvT0Anf{tUl8O?04- z4f}JW)5B}+i>_1dH`5X%r0u!Mw+-<+zuUiEOFpn2ND~(LJX%xjyuY tTC3FWYWX)s2~&1tuy~8E@NBXc=Wfa_ox&H+9gKq2UdjFb+IjW5EI TB-m5 literal 0 HcmV?d00001 diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 2e7d7da9..9c84037a 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -34,6 +34,7 @@ code { width: 32px; height: 32px; transform: translateY(4px); + cursor: pointer; } #prompt { width: 100%; From 84c5a759d46d1eef14072587aa1af6ce4a5bfc26 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Mon, 14 Aug 2023 14:56:33 +0200 Subject: [PATCH 02/27] Resize slider for the advanced image popup (#1490) --- ui/index.html | 18 ++++++++--- ui/media/css/main.css | 27 +++++++++++++++- ui/media/js/main.js | 72 ++++++++++++++++++++++++------------------- 3 files changed, 80 insertions(+), 37 deletions(-) diff --git a/ui/index.html b/ui/index.html index d5ee2ae6..6811774e 100644 --- a/ui/index.html +++ b/ui/index.html @@ -301,12 +301,22 @@ ×
+ Resize:
+
+
    
- Enlarge:
-
  
+
+
+ Recently used:
+
+
+
+
+ Common sizes:
+
+
+
- Recently used:
-
diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 9c84037a..745cf53c 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -1779,6 +1779,10 @@ div#recent-resolutions-popup small { opacity: 0.7; } +div#common-resolution-list button { + background: var(--background-color1); +} + td#image-size-options small { margin-right: 0px !important; } @@ -1795,6 +1799,27 @@ div#enlarge-buttons { text-align: center; } +.two-column { display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr; + gap: 0px 0.5em; + grid-auto-flow: row; + grid-template-areas: + "left-column right-column"; +} + +.left-column { + justify-self: center; + align-self: center; + grid-area: left-column; +} + +.right-column { + justify-self: center; + align-self: center; + grid-area: right-column; +} + .clickable { cursor: pointer; } @@ -1832,4 +1857,4 @@ div#enlarge-buttons { /* hack for fixing Image Modifier Improvements plugin */ #imageTagPopupContainer { position: absolute; -} \ No newline at end of file +} diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 6ed8d506..9e6252f0 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -83,9 +83,9 @@ let customHeightField = document.querySelector("#custom-height") let recentResolutionsButton = document.querySelector("#recent-resolutions-button") let recentResolutionsPopup = document.querySelector("#recent-resolutions-popup") let recentResolutionList = document.querySelector("#recent-resolution-list") -let enlarge15Button = document.querySelector("#enlarge15") -let enlarge2Button = document.querySelector("#enlarge2") -let enlarge3Button = document.querySelector("#enlarge3") +let commonResolutionList = document.querySelector("#common-resolution-list") +let resizeSlider = document.querySelector("#resize-slider") +let enlargeButtons = document.querySelector("#enlarge-buttons") let swapWidthHeightButton = document.querySelector("#swap-width-height") let smallImageWarning = document.querySelector("#small_image_warning") let initImageSelector = document.querySelector("#init_image") @@ -2792,38 +2792,25 @@ let recentResolutionsValues = [] ;(function() { ///// Init resolutions dropdown - function makeResolutionButtons() { - recentResolutionList.innerHTML = "" - recentResolutionsValues.forEach((el) => { - let button = document.createElement("button") - button.classList.add("tertiaryButton") - button.style.width = "8em" - button.innerHTML = `${el.w}×${el.h}` + + function makeResolutionButtons(listElement, resolutionList) { + listElement.innerHTML = "" + resolutionList.forEach((el) => { + let button = createElement("button", {style: "width: 8em;"}, "tertiaryButton", `${el.w}×${el.h}`) button.addEventListener("click", () => { customWidthField.value = el.w customHeightField.value = el.h hidePopup() }) - recentResolutionList.appendChild(button) - recentResolutionList.appendChild(document.createElement("br")) + listElement.appendChild(button) + listElement.appendChild(document.createElement("br")) }) - localStorage.recentResolutionsValues = JSON.stringify(recentResolutionsValues) } - enlarge15Button.addEventListener("click", () => { - enlargeImageSize(1.5) + enlargeButtons.querySelectorAll("button").forEach( button => button.addEventListener("click", e => { + enlargeImageSize(parseFloat(button.dataset["factor"])) hidePopup() - }) - - enlarge2Button.addEventListener("click", () => { - enlargeImageSize(2) - hidePopup() - }) - - enlarge3Button.addEventListener("click", () => { - enlargeImageSize(3) - hidePopup() - }) + })) customWidthField.addEventListener("change", () => { let w = customWidthField.value @@ -2850,25 +2837,29 @@ let recentResolutionsValues = [] recentResolutionsValues = recentResolutionsValues.slice(0, 8) localStorage.recentResolutionsValues = JSON.stringify(recentResolutionsValues) - makeResolutionButtons() + makeResolutionButtons(recentResolutionList, recentResolutionsValues) }) - let _jsonstring = localStorage.recentResolutionsValues - if (_jsonstring == undefined) { - recentResolutionsValues = [ + const defaultResolutionsValues = [ { w: 512, h: 512 }, - { w: 640, h: 448 }, { w: 448, h: 640 }, { w: 512, h: 768 }, { w: 768, h: 512 }, { w: 1024, h: 768 }, { w: 768, h: 1024 }, + { w: 1024, h: 1024 }, + { w: 1920, h: 1080 }, ] + let _jsonstring = localStorage.recentResolutionsValues + if (_jsonstring == undefined) { + recentResolutionsValues = defaultResolutionsValues; localStorage.recentResolutionsValues = JSON.stringify(recentResolutionsValues) } else { recentResolutionsValues = JSON.parse(localStorage.recentResolutionsValues) } - makeResolutionButtons() + + makeResolutionButtons(recentResolutionList, recentResolutionsValues) + makeResolutionButtons(commonResolutionList, defaultResolutionsValues) recentResolutionsValues.forEach((val) => { addImageSizeOption(val.w) @@ -2885,6 +2876,9 @@ let recentResolutionsValues = [] customWidthField.value = widthField.value customHeightField.value = heightField.value recentResolutionsPopup.classList.remove("displayNone") + resizeSlider.value = 1 + resizeSlider.dataset["w"] = widthField.value + resizeSlider.dataset["h"] = heightField.value document.addEventListener("click", processClick) } @@ -2903,6 +2897,20 @@ let recentResolutionsValues = [] } }) + resizeSlider.addEventListener("input", e => { + let w = parseInt(resizeSlider.dataset["w"]) + let h = parseInt(resizeSlider.dataset["h"]) + let factor = parseFloat(resizeSlider.value) + let step = customWidthField.step + + customWidthField.value = roundToMultiple(w*factor*factor, step) + customHeightField.value = roundToMultiple(h*factor*factor, step) + }) + + resizeSlider.addEventListener("change", e => { + hidePopup() + }) + swapWidthHeightButton.addEventListener("click", (event) => { let temp = widthField.value widthField.value = heightField.value From dc5748624f9d6a19ed4ddaa318d7a6eb7ef66d91 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 14 Aug 2023 18:52:20 +0530 Subject: [PATCH 03/27] sdkit 1.0.168 - fail loudly if an embedding fails to load, disable watermarks for sdxl img2img --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index a5edf5f5..1dceacb6 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.167", + "sdkit": "1.0.168", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From b5490f7712a218568fde4ed61522f1c8abb65681 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 14 Aug 2023 18:52:38 +0530 Subject: [PATCH 04/27] changelog --- CHANGES.md | 3 +++ ui/index.html | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 9e72b44f..7eae411b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,9 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog +* 3.0.1 - 14 Aug 2023 - Slider to change the image dimensions proportionally (in Image Settings). Thanks @JeLuf. +* 3.0.1 - 14 Aug 2023 - Show an error to the user if an embedding isn't compatible with the model, instead of failing silently without informing the user. Thanks @JeLuf. +* 3.0.1 - 14 Aug 2023 - Disable watermarking for SDXL img2img. Thanks @AvidGameFan. * 3.0.0 - 3 Aug 2023 - Enabled diffusers for everyone by default. The old v2 engine can be used by disabling the "Use v3 engine" option in the Settings tab. * 2.5.48 - 1 Aug 2023 - (beta-only) Full support for ControlNets. You can select a control image to guide the AI. You can pick a filter to pre-process the image, and one of the known (or custom) controlnet models. Supports `OpenPose`, `Canny`, `Straight Lines`, `Depth`, `Line Art`, `Scribble`, `Soft Edge`, `Shuffle` and `Segment`. * 2.5.47 - 30 Jul 2023 - An option to use `Strict Mask Border` while inpainting, to avoid touching areas outside the mask. But this might show a slight outline of the mask, which you will have to touch up separately. diff --git a/ui/index.html b/ui/index.html index d5ee2ae6..2c93162f 100644 --- a/ui/index.html +++ b/ui/index.html @@ -32,7 +32,7 @@

Easy Diffusion - v3.0.0 + v3.0.1

From 30ca98b597e2ad13a3b7d77018c378e01ad5d9f8 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 14 Aug 2023 19:02:56 +0530 Subject: [PATCH 05/27] sdkit 1.0.169 - don't clip-skip with SDXL, isn't supported yet --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 1dceacb6..ce26cdf6 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.168", + "sdkit": "1.0.169", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 0adb7831e7aabc97eee87ce9338d5e0bcb8ac771 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 12:53:16 +0530 Subject: [PATCH 06/27] Use the correct nvidia wheels path --- ui/easydiffusion/package_manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/easydiffusion/package_manager.py b/ui/easydiffusion/package_manager.py index de64b66c..72479379 100644 --- a/ui/easydiffusion/package_manager.py +++ b/ui/easydiffusion/package_manager.py @@ -12,9 +12,9 @@ from easydiffusion import app manifest = { "tensorrt": { "install": [ - "nvidia-cudnn --pre --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com", - "tensorrt-libs --pre --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com", - "tensorrt --pre --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com", + "nvidia-cudnn --pre --extra-index-url=https://pypi.nvidia.com --trusted-host pypi.nvidia.com", + "tensorrt-libs --pre --extra-index-url=https://pypi.nvidia.com --trusted-host pypi.nvidia.com", + "tensorrt --pre --extra-index-url=https://pypi.nvidia.com --trusted-host pypi.nvidia.com", ], "uninstall": ["tensorrt"], # TODO also uninstall tensorrt-libs and nvidia-cudnn, but do it upon restarting (avoid 'file in use' error) From 6a216be5cbeb6af1e38a29139d9717400219ecdf Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 13:31:08 +0530 Subject: [PATCH 07/27] Force-clean the local git repo, even if it has some unmerged changes --- scripts/on_env_start.bat | 2 ++ scripts/on_env_start.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/scripts/on_env_start.bat b/scripts/on_env_start.bat index 43f7e2b7..d0e915e2 100644 --- a/scripts/on_env_start.bat +++ b/scripts/on_env_start.bat @@ -46,6 +46,8 @@ if "%update_branch%"=="" ( @cd sd-ui-files + @call git add -A . + @call git stash @call git reset --hard @call git -c advice.detachedHead=false checkout "%update_branch%" @call git pull diff --git a/scripts/on_env_start.sh b/scripts/on_env_start.sh index 02428ce5..6fd61c26 100755 --- a/scripts/on_env_start.sh +++ b/scripts/on_env_start.sh @@ -29,6 +29,8 @@ if [ -f "scripts/install_status.txt" ] && [ `grep -c sd_ui_git_cloned scripts/in cd sd-ui-files + git add -A . + git stash git reset --hard git -c advice.detachedHead=false checkout "$update_branch" git pull From e98bd70871de82b6ba443a5a2efbc97ec339a31e Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 16:00:47 +0530 Subject: [PATCH 08/27] sdkit 1.0.170 - fix VAE in low vram mode --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index ce26cdf6..12a26622 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.169", + "sdkit": "1.0.170", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 253d0dbd5e3681f855de3644fe60daa743202715 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 16:02:10 +0530 Subject: [PATCH 09/27] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 7eae411b..472b533b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog +* 3.0.1 - 15 Aug 2023 - Fix for custom VAEs not working in `low` VRAM usage mode. * 3.0.1 - 14 Aug 2023 - Slider to change the image dimensions proportionally (in Image Settings). Thanks @JeLuf. * 3.0.1 - 14 Aug 2023 - Show an error to the user if an embedding isn't compatible with the model, instead of failing silently without informing the user. Thanks @JeLuf. * 3.0.1 - 14 Aug 2023 - Disable watermarking for SDXL img2img. Thanks @AvidGameFan. From 6777459e628b8d480df83afe030c0479f5fdbba5 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 16:49:51 +0530 Subject: [PATCH 10/27] sdkit 1.0.171 - fix embeddings in low vram usage mode --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 12a26622..f01009f7 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.170", + "sdkit": "1.0.171", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 4975f8167efc0d0637708231c7cd80e892ae4646 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 16:50:21 +0530 Subject: [PATCH 11/27] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 472b533b..80039bfb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog +* 3.0.1 - 15 Aug 2023 - Fix textual inversion embeddings not working in `low` VRAM usage mode. * 3.0.1 - 15 Aug 2023 - Fix for custom VAEs not working in `low` VRAM usage mode. * 3.0.1 - 14 Aug 2023 - Slider to change the image dimensions proportionally (in Image Settings). Thanks @JeLuf. * 3.0.1 - 14 Aug 2023 - Show an error to the user if an embedding isn't compatible with the model, instead of failing silently without informing the user. Thanks @JeLuf. From 34de4fe8fed115df79417eef2574b7f6c4922300 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 16:57:51 +0530 Subject: [PATCH 12/27] Remove warning about embeddings in low vram mode, works now --- ui/media/js/main.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 9e6252f0..00aed0cc 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -2559,17 +2559,7 @@ function updateEmbeddingsList(filter = "") { } } - // Remove after fixing https://github.com/huggingface/diffusers/issues/3922 - let warning = "" - if (vramUsageLevelField.value == "low") { - warning = ` -
- Warning: Your GPU memory profile is set to "Low". Embeddings currently only work in "Balanced" mode! -
` - } - // END of remove block - - embeddingsList.innerHTML = warning + html(modelsOptions.embeddings, "", filter) + embeddingsList.innerHTML = html(modelsOptions.embeddings, "", filter) embeddingsList.querySelectorAll("button").forEach((b) => { b.addEventListener("click", onButtonClick) }) From 01c1c77564a7e9711cfea45254994c3642759882 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 16:58:12 +0530 Subject: [PATCH 13/27] formatting --- ui/media/js/main.js | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 00aed0cc..74119841 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -2786,7 +2786,7 @@ let recentResolutionsValues = [] function makeResolutionButtons(listElement, resolutionList) { listElement.innerHTML = "" resolutionList.forEach((el) => { - let button = createElement("button", {style: "width: 8em;"}, "tertiaryButton", `${el.w}×${el.h}`) + let button = createElement("button", { style: "width: 8em;" }, "tertiaryButton", `${el.w}×${el.h}`) button.addEventListener("click", () => { customWidthField.value = el.w customHeightField.value = el.h @@ -2797,10 +2797,12 @@ let recentResolutionsValues = [] }) } - enlargeButtons.querySelectorAll("button").forEach( button => button.addEventListener("click", e => { - enlargeImageSize(parseFloat(button.dataset["factor"])) - hidePopup() - })) + enlargeButtons.querySelectorAll("button").forEach((button) => + button.addEventListener("click", (e) => { + enlargeImageSize(parseFloat(button.dataset["factor"])) + hidePopup() + }) + ) customWidthField.addEventListener("change", () => { let w = customWidthField.value @@ -2831,18 +2833,18 @@ let recentResolutionsValues = [] }) const defaultResolutionsValues = [ - { w: 512, h: 512 }, - { w: 448, h: 640 }, - { w: 512, h: 768 }, - { w: 768, h: 512 }, - { w: 1024, h: 768 }, - { w: 768, h: 1024 }, - { w: 1024, h: 1024 }, - { w: 1920, h: 1080 }, - ] + { w: 512, h: 512 }, + { w: 448, h: 640 }, + { w: 512, h: 768 }, + { w: 768, h: 512 }, + { w: 1024, h: 768 }, + { w: 768, h: 1024 }, + { w: 1024, h: 1024 }, + { w: 1920, h: 1080 }, + ] let _jsonstring = localStorage.recentResolutionsValues if (_jsonstring == undefined) { - recentResolutionsValues = defaultResolutionsValues; + recentResolutionsValues = defaultResolutionsValues localStorage.recentResolutionsValues = JSON.stringify(recentResolutionsValues) } else { recentResolutionsValues = JSON.parse(localStorage.recentResolutionsValues) @@ -2887,17 +2889,17 @@ let recentResolutionsValues = [] } }) - resizeSlider.addEventListener("input", e => { + resizeSlider.addEventListener("input", (e) => { let w = parseInt(resizeSlider.dataset["w"]) let h = parseInt(resizeSlider.dataset["h"]) let factor = parseFloat(resizeSlider.value) let step = customWidthField.step - customWidthField.value = roundToMultiple(w*factor*factor, step) - customHeightField.value = roundToMultiple(h*factor*factor, step) + customWidthField.value = roundToMultiple(w * factor * factor, step) + customHeightField.value = roundToMultiple(h * factor * factor, step) }) - resizeSlider.addEventListener("change", e => { + resizeSlider.addEventListener("change", (e) => { hidePopup() }) From a2e7bfb30e27e12beeb7a8512e58e43ade29178f Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 19:05:46 +0530 Subject: [PATCH 14/27] sdkit 1.0.172 - fix broken tiling after diffusers 0.19.2 upgrade --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index f01009f7..cea2f336 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.171", + "sdkit": "1.0.172", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 074c566826e9eb60c6796073049abbc2bae9c9b7 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 19:06:48 +0530 Subject: [PATCH 15/27] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 80039bfb..8fdbde7f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog +* 3.0.1 - 15 Aug 2023 - Fix broken seamless tiling. * 3.0.1 - 15 Aug 2023 - Fix textual inversion embeddings not working in `low` VRAM usage mode. * 3.0.1 - 15 Aug 2023 - Fix for custom VAEs not working in `low` VRAM usage mode. * 3.0.1 - 14 Aug 2023 - Slider to change the image dimensions proportionally (in Image Settings). Thanks @JeLuf. From a1854d37343373d83ab25297ab6076a7eeb762c5 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Wed, 16 Aug 2023 06:53:12 +0200 Subject: [PATCH 16/27] 'Use for Controlnet', Drag'n'Drop (#1501) * Drop area for Controlnet * 'Use for Controlnet', DND --- ui/media/js/main.js | 5 ++ .../ui/image-editor-improvements.plugin.js | 59 +++++++++++-------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 74119841..ea2af7e3 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -517,6 +517,7 @@ function showImages(reqBody, res, outputContainer, livePreview) { const imageRedoBuffer = [] let buttons = [ { text: "Use as Input", on_click: onUseAsInputClick }, + { text: "Use for Controlnet", on_click: onUseForControlnetClick }, [ { html: ' Download Image', @@ -627,6 +628,10 @@ function onUseAsInputClick(req, img) { maskSetting.checked = false } +function onUseForControlnetClick(req, img) { + controlImagePreview.src = img.src +} + function getDownloadFilename(img, suffix) { const imageSeed = img.getAttribute("data-seed") const imagePrompt = img.getAttribute("data-prompt") diff --git a/ui/plugins/ui/image-editor-improvements.plugin.js b/ui/plugins/ui/image-editor-improvements.plugin.js index fbc7ad80..672ed77b 100644 --- a/ui/plugins/ui/image-editor-improvements.plugin.js +++ b/ui/plugins/ui/image-editor-improvements.plugin.js @@ -191,19 +191,32 @@ function createDropAreas(container) { // Create two drop areas - const dropAreaI2I = document.createElement("div") - dropAreaI2I.setAttribute("id", "drop-area-I2I") - dropAreaI2I.setAttribute("class", "drop-area") - dropAreaI2I.innerHTML = "Use as Image2Image source" + const dropAreaI2I = createElement("div", {id: "drop-area-I2I"}, ["drop-area"], "Use as Image2Image source") container.appendChild(dropAreaI2I) - const dropAreaMD = document.createElement("div") - dropAreaMD.setAttribute("id", "drop-area-MD") - dropAreaMD.setAttribute("class", "drop-area") - dropAreaMD.innerHTML = "Extract embedded metadata" + const dropAreaMD = createElement("div", {id: "drop-area-MD"}, ["drop-area"], "Extract embedded metadata") container.appendChild(dropAreaMD) + const dropAreaCN = createElement("div", {id: "drop-area-CN"}, ["drop-area"], "Use as Controlnet image") + container.appendChild(dropAreaCN) + // Add event listeners to drop areas + dropAreaCN.addEventListener("dragenter", function(event) { + event.preventDefault() + dropAreaCN.style.backgroundColor = 'darkGreen' + }) + dropAreaCN.addEventListener("dragleave", function(event) { + event.preventDefault() + dropAreaCN.style.backgroundColor = '' + }) + dropAreaCN.addEventListener("drop", function(event) { + event.stopPropagation() + event.preventDefault() + hideDropAreas() + + getImageFromDropEvent(event, e => controlImagePreview.src=e) + }) + dropAreaI2I.addEventListener("dragenter", function(event) { event.preventDefault() dropAreaI2I.style.backgroundColor = 'darkGreen' @@ -212,16 +225,12 @@ event.preventDefault() dropAreaI2I.style.backgroundColor = '' }) - - dropAreaI2I.addEventListener("drop", function(event) { - event.stopPropagation(); - event.preventDefault(); - hideDropAreas() - + + function getImageFromDropEvent(event, callback) { // Find the first image file, uri, or moz-url in the items list - let imageItem = null; + let imageItem = null for (let i = 0; i < event.dataTransfer.items.length; i++) { - let item = event.dataTransfer.items[i]; + let item = event.dataTransfer.items[i] if (item.kind === 'file' && item.type.startsWith('image/')) { imageItem = item; break; @@ -258,18 +267,22 @@ // Create a FileReader object to read the dropped file as a data URL let reader = new FileReader(); reader.onload = function(e) { - // Set the src attribute of the img element to the data URL - imageObj.src = e.target.result; + callback(e.target.result) }; reader.readAsDataURL(file); } else { // If the item is a URL, retrieve it and use it to load the image - imageItem.getAsString(function(url) { - // Set the src attribute of the img element to the URL - imageObj.src = url; - }); + imageItem.getAsString(callback) } - } + } + } + + dropAreaI2I.addEventListener("drop", function(event) { + event.stopPropagation() + event.preventDefault() + hideDropAreas() + + getImageFromDropEvent(event, e => imageObj.src=e) }) dropAreaMD.addEventListener("dragenter", function(event) { From 7c012df1d5c126fcf08e2c79b6821eab9fd316b7 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 16 Aug 2023 15:59:07 +0530 Subject: [PATCH 17/27] sdkit 1.0.173 - SDXL LoRA support --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index cea2f336..dce05de0 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.172", + "sdkit": "1.0.173", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From b4cc21ea89f0e7a75e9e5777228f6752733b5a65 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 16 Aug 2023 16:02:35 +0530 Subject: [PATCH 18/27] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 8fdbde7f..98cb0e74 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog +* 3.0.1 - 15 Aug 2023 - Fix broken LoRA with SDXL. * 3.0.1 - 15 Aug 2023 - Fix broken seamless tiling. * 3.0.1 - 15 Aug 2023 - Fix textual inversion embeddings not working in `low` VRAM usage mode. * 3.0.1 - 15 Aug 2023 - Fix for custom VAEs not working in `low` VRAM usage mode. From ac1c65fba11c3d79e64f42509c51372a76364285 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 17 Aug 2023 10:54:47 +0530 Subject: [PATCH 19/27] Move the extraction logic for embeddings-from-prompt, from sdkit to ED's UI --- ui/easydiffusion/model_manager.py | 3 --- ui/easydiffusion/types.py | 2 ++ ui/media/js/main.js | 37 +++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/ui/easydiffusion/model_manager.py b/ui/easydiffusion/model_manager.py index 845e9126..841b0cd2 100644 --- a/ui/easydiffusion/model_manager.py +++ b/ui/easydiffusion/model_manager.py @@ -65,9 +65,6 @@ def load_default_models(context: Context): runtime.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) diff --git a/ui/easydiffusion/types.py b/ui/easydiffusion/types.py index fe936ca2..eeffcb72 100644 --- a/ui/easydiffusion/types.py +++ b/ui/easydiffusion/types.py @@ -73,6 +73,7 @@ class TaskData(BaseModel): use_hypernetwork_model: Union[str, List[str]] = None use_lora_model: Union[str, List[str]] = None use_controlnet_model: Union[str, List[str]] = None + use_embeddings_model: Union[str, List[str]] = None filters: List[str] = [] filter_params: Dict[str, Dict[str, Any]] = {} control_filter_to_apply: Union[str, List[str]] = None @@ -200,6 +201,7 @@ def convert_legacy_render_req_to_new(old_req: dict): model_paths["hypernetwork"] = old_req.get("use_hypernetwork_model") model_paths["lora"] = old_req.get("use_lora_model") model_paths["controlnet"] = old_req.get("use_controlnet_model") + model_paths["embeddings"] = old_req.get("use_embeddings_model") model_paths["gfpgan"] = old_req.get("use_face_correction", "") model_paths["gfpgan"] = model_paths["gfpgan"] if "gfpgan" in model_paths["gfpgan"].lower() else None diff --git a/ui/media/js/main.js b/ui/media/js/main.js index ea2af7e3..c29850c2 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -845,6 +845,7 @@ function makeImage() { reqBody: Object.assign({ prompt: prompt }, taskTemplate.reqBody), }) ) + newTaskRequests.forEach(setEmbeddings) newTaskRequests.forEach(createTask) updateInitialText() @@ -1493,6 +1494,42 @@ function getCurrentUserRequest() { return newTask } +function setEmbeddings(task) { + let prompt = task.reqBody.prompt.toLowerCase() + let negativePrompt = task.reqBody.negative_prompt.toLowerCase() + let overallPrompt = (prompt + " " + negativePrompt).split(" ") + + let embeddingsTree = modelsOptions["embeddings"] + let embeddings = [] + function extract(entries, basePath = "") { + entries.forEach((e) => { + if (Array.isArray(e)) { + let path = basePath === "" ? basePath + e[0] : basePath + "/" + e[0] + extract(e[1], path) + } else { + let path = basePath === "" ? basePath + e : basePath + "/" + e + embeddings.push([e.toLowerCase().replace(" ", "_"), path]) + } + }) + } + extract(embeddingsTree) + + let embeddingPaths = [] + + embeddings.forEach((e) => { + let token = e[0] + let path = e[1] + + if (overallPrompt.includes(token)) { + embeddingPaths.push(path) + } + }) + + if (embeddingPaths.length > 0) { + task.reqBody.use_embeddings_model = embeddingPaths + } +} + function getModelInfo(models) { let modelInfo = models.map((e) => [e[0].value, e[1].value]) modelInfo = modelInfo.filter((e) => e[0].trim() !== "") From edd10bcfe73ff4b8efcc3a41df7d82086c9aa093 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 17 Aug 2023 10:55:26 +0530 Subject: [PATCH 20/27] sdkit 1.0.174 - embedding support for SDXL models, refactor embeddings to use the standard context.models API in sdkit --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index dce05de0..10f1420c 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.173", + "sdkit": "1.0.174", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 097dc99e7734c36cd62f5470186fccebf8507e86 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 17 Aug 2023 11:01:13 +0530 Subject: [PATCH 21/27] changelog --- CHANGES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 98cb0e74..471a0c80 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,7 +22,8 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog -* 3.0.1 - 15 Aug 2023 - Fix broken LoRA with SDXL. +* 3.0.1 - 17 Aug 2023 - Fix broken embeddings with SDXL. +* 3.0.1 - 16 Aug 2023 - Fix broken LoRA with SDXL. * 3.0.1 - 15 Aug 2023 - Fix broken seamless tiling. * 3.0.1 - 15 Aug 2023 - Fix textual inversion embeddings not working in `low` VRAM usage mode. * 3.0.1 - 15 Aug 2023 - Fix for custom VAEs not working in `low` VRAM usage mode. From 2baad73bb98d0f2abb1811000504bec15f225c5a Mon Sep 17 00:00:00 2001 From: JeLuF Date: Thu, 17 Aug 2023 07:38:18 +0200 Subject: [PATCH 22/27] Error messages for SDXL yaml files (#1504) * Error messages for SDXL embeddings and SDXL yaml files * Embeddings are supported now with SDXL --- ui/media/js/main.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index c29850c2..fad67ab4 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -1039,15 +1039,18 @@ function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { Suggestions:
Try to use a different model or a different LORA.` - } else if (msg.includes("Tensor on device cuda:0 is not on the expected device meta")) { + } else if (msg.includes("'ModuleList' object has no attribute '1'")) { msg += `

- Reason: Due to some software issues, embeddings currently don't work with the "Low" memory profile. + Reason: SDXL models need a yaml config file.

Suggestions:
- 1. Set the memory profile to "Balanced"
- 2. Remove the embeddings from the prompt and the negative prompt
- 3. Check whether the plugins you're using change the memory profile automatically.` +
    +
  1. Download the config file
  2. +
  3. Save it in the same directory as the SDXL model file
  4. +
  5. Rename the config file so that it matches the filename of the model, with the extension of the model file replaced by yaml. + For example, if the model file is called FantasySDXL_v2.safetensors, the config file must be called FantasySDXL_v2.yaml. +
` } } else { msg = `Unexpected Read Error:
StepUpdate: ${JSON.stringify(stepUpdate, undefined, 4)}
` From 23a0a48b814bc4c52df602e6e6999d3bca802ade Mon Sep 17 00:00:00 2001 From: JeLuF Date: Thu, 17 Aug 2023 07:48:00 +0200 Subject: [PATCH 23/27] Warn when no controlnet model is chosen (#1503) * Warn when no controlnet model is chosen * Update main.js --- ui/media/css/main.css | 3 +++ ui/media/js/main.js | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 745cf53c..48f30f5b 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -1461,6 +1461,9 @@ button#save-system-settings-btn { cursor: pointer;; } +.validation-failed { + border: solid 2px red; +} /* SCROLLBARS */ :root { --scrollbar-width: 14px; diff --git a/ui/media/js/main.js b/ui/media/js/main.js index fad67ab4..162ea3be 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -821,12 +821,25 @@ function makeImage() { } if (!randomSeedField.checked && seedField.value == "") { alert('The "Seed" field must not be empty.') + seedField.classList.add("validation-failed") return } + seedField.classList.remove("validation-failed") + if (numInferenceStepsField.value == "") { alert('The "Inference Steps" field must not be empty.') + numInferenceStepsField.classList.add("validation-failed") return } + numInferenceStepsField.classList.remove("validation-failed") + + if (controlnetModelField.value === "" && IMAGE_REGEX.test(controlImagePreview.src)) { + alert("Please choose a ControlNet model, to use the ControlNet image.") + document.getElementById("controlnet_model").classList.add("validation-failed") + return + } + document.getElementById("controlnet_model").classList.remove("validation-failed") + if (numOutputsTotalField.value == "" || numOutputsTotalField.value == 0) { numOutputsTotalField.value = 1 } From 285792f69229b7edc1ff59c402b32ef3fe233292 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Thu, 17 Aug 2023 07:48:47 +0200 Subject: [PATCH 24/27] Controlnet thumb in taskConfig (#1502) --- ui/media/css/main.css | 12 ++++++++++-- ui/media/js/main.js | 28 +++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 48f30f5b..bac1122d 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -1419,6 +1419,10 @@ div.task-fs-initimage { display: none; position: absolute; } +div.task-fs-initimage img { + max-height: 70vH; + max-width: 70vW; +} div.task-initimg:hover div.task-fs-initimage { display: block; position: absolute; @@ -1434,9 +1438,13 @@ div.top-right { right: 8px; } +button.useForControlnetBtn { + margin-top: 6px; +} + #small_image_warning { - font-size: smaller; - color: var(--status-orange); + font-size: smaller; + color: var(--status-orange); } button#save-system-settings-btn { diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 162ea3be..69224dbc 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -1217,6 +1217,27 @@ async function onTaskStart(task) { /* Hover effect for the init image in the task list */ function createInitImageHover(taskEntry) { + taskEntry.querySelectorAll(".task-initimg").forEach( thumb => { + let thumbimg = thumb.querySelector("img") + let img = createElement("img", {src: thumbimg.src}) + thumb.querySelector(".task-fs-initimage").appendChild(img) + let div = createElement("div", undefined, ["top-right"]) + div.innerHTML = ` + +
+ ` + div.querySelector(".useAsInputBtn").addEventListener("click", e => { + e.preventDefault() + onUseAsInputClick(null, img) + }) + div.querySelector(".useForControlnetBtn").addEventListener("click", e => { + e.preventDefault() + controlImagePreview.src = img.src + }) + thumb.querySelector(".task-fs-initimage").appendChild(div) + }) + return + var $tooltip = $(taskEntry.querySelector(".task-fs-initimage")) var img = document.createElement("img") img.src = taskEntry.querySelector("div.task-initimg > img").src @@ -1281,6 +1302,11 @@ function createTask(task) { let w = ((task.reqBody.width * h) / task.reqBody.height) >> 0 taskConfig += `
` } + if (task.reqBody.control_image !== undefined) { + let h = 80 + let w = ((task.reqBody.width * h) / task.reqBody.height) >> 0 + taskConfig += `
` + } taskConfig += `
${createTaskConfig(task)}
` @@ -1331,7 +1357,7 @@ function createTask(task) { startY = e.target.closest(".imageTaskContainer").offsetTop }) - if (task.reqBody.init_image !== undefined) { + if (task.reqBody.init_image !== undefined || task.reqBody.control_image !== undefined) { createInitImageHover(taskEntry) } From 7270b5fe0c5ed5aba3c72ae8dbd880cfbbe207f3 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Thu, 17 Aug 2023 08:03:05 +0200 Subject: [PATCH 25/27] Thumbnails for Embeddings (#1483) * sqlalchemy * sqlalchemy * sqlalchemy bucket v1 * Bucket API * Move easydb files to its own folders * show images * add croppr * croppr ui * profile, thumbnail croppr size limit * fill list * add upload * Use modifiers card, resize thumbs * Remove debugging code * remove unused variable --- 3rd-PARTY-LICENSES | 28 + scripts/check_modules.py | 2 + ui/easydiffusion/app.py | 1 + ui/easydiffusion/bucket_manager.py | 102 +++ ui/easydiffusion/easydb/crud.py | 24 + ui/easydiffusion/easydb/database.py | 14 + ui/easydiffusion/easydb/models.py | 25 + ui/easydiffusion/easydb/schemas.py | 36 + ui/index.html | 39 + ui/main.py | 3 +- ui/media/css/croppr.css | 58 ++ ui/media/css/main.css | 55 ++ ui/media/images/noimg.png | Bin 0 -> 1338 bytes ui/media/js/auto-save.js | 2 + ui/media/js/croppr.js | 1189 +++++++++++++++++++++++++++ ui/media/js/main.js | 224 ++++- ui/media/js/parameters.js | 11 + ui/media/js/utils.js | 42 + 18 files changed, 1834 insertions(+), 21 deletions(-) create mode 100644 ui/easydiffusion/bucket_manager.py create mode 100644 ui/easydiffusion/easydb/crud.py create mode 100644 ui/easydiffusion/easydb/database.py create mode 100644 ui/easydiffusion/easydb/models.py create mode 100644 ui/easydiffusion/easydb/schemas.py create mode 100644 ui/media/css/croppr.css create mode 100644 ui/media/images/noimg.png create mode 100755 ui/media/js/croppr.js diff --git a/3rd-PARTY-LICENSES b/3rd-PARTY-LICENSES index bd29393a..78bfe3bb 100644 --- a/3rd-PARTY-LICENSES +++ b/3rd-PARTY-LICENSES @@ -712,3 +712,31 @@ FileSaver.js is licensed under the MIT license: SOFTWARE. [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. diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 10f1420c..df752a02 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -25,6 +25,8 @@ modules_to_check = { "fastapi": "0.85.1", "pycloudflared": "0.2.0", "ruamel.yaml": "0.17.21", + "sqlalchemy": "2.0.19", + "python-multipart": "0.0.6", # "xformers": "0.0.16", } modules_to_log = ["torch", "torchvision", "sdkit", "stable-diffusion-sdkit"] diff --git a/ui/easydiffusion/app.py b/ui/easydiffusion/app.py index aa1c5ba7..e2c190f8 100644 --- a/ui/easydiffusion/app.py +++ b/ui/easydiffusion/app.py @@ -38,6 +38,7 @@ SD_UI_DIR = os.getenv("SD_UI_PATH", None) CONFIG_DIR = os.path.abspath(os.path.join(SD_UI_DIR, "..", "scripts")) MODELS_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "models")) +BUCKET_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "bucket")) USER_PLUGINS_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "plugins")) CORE_PLUGINS_DIR = os.path.abspath(os.path.join(SD_UI_DIR, "plugins")) diff --git a/ui/easydiffusion/bucket_manager.py b/ui/easydiffusion/bucket_manager.py new file mode 100644 index 00000000..60a4ed6c --- /dev/null +++ b/ui/easydiffusion/bucket_manager.py @@ -0,0 +1,102 @@ +from typing import List + +from fastapi import Depends, FastAPI, HTTPException, Response, File +from sqlalchemy.orm import Session + +from easydiffusion.easydb import crud, models, schemas +from easydiffusion.easydb.database import SessionLocal, engine + +from requests.compat import urlparse + +import base64, json + +MIME_TYPES = { + "jpg": "image/jpeg", + "jpeg": "image/jpeg", + "gif": "image/gif", + "png": "image/png", + "webp": "image/webp", + "js": "text/javascript", + "htm": "text/html", + "html": "text/html", + "css": "text/css", + "json": "application/json", + "mjs": "application/json", + "yaml": "application/yaml", + "svg": "image/svg+xml", + "txt": "text/plain", +} + +def init(): + from easydiffusion.server import server_api + + models.BucketBase.metadata.create_all(bind=engine) + + + # Dependency + def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + + @server_api.get("/bucket/{obj_path:path}") + def bucket_get_object(obj_path: str, db: Session = Depends(get_db)): + filename = get_filename_from_url(obj_path) + path = get_path_from_url(obj_path) + + if filename==None: + bucket = crud.get_bucket_by_path(db, path=path) + if bucket == None: + raise HTTPException(status_code=404, detail="Bucket not found") + bucketfiles = db.query(models.BucketFile).with_entities(models.BucketFile.filename).filter(models.BucketFile.bucket_id == bucket.id).all() + bucketfiles = [ x.filename for x in bucketfiles ] + return bucketfiles + + else: + bucket_id = crud.get_bucket_by_path(db, path).id + bucketfile = db.query(models.BucketFile).filter(models.BucketFile.bucket_id == bucket_id, models.BucketFile.filename == filename).first() + + suffix = get_suffix_from_filename(filename) + + return Response(content=bucketfile.data, media_type=MIME_TYPES.get(suffix, "application/octet-stream")) + + @server_api.post("/bucket/{obj_path:path}") + def bucket_post_object(obj_path: str, file: bytes = File(), db: Session = Depends(get_db)): + filename = get_filename_from_url(obj_path) + path = get_path_from_url(obj_path) + bucket = crud.get_bucket_by_path(db, path) + + if bucket == None: + bucket = crud.create_bucket(db=db, bucket=schemas.BucketCreate(path=path)) + bucket_id = bucket.id + + bucketfile = schemas.BucketFileCreate(filename=filename, data=file) + result = crud.create_bucketfile(db=db, bucketfile=bucketfile, bucket_id=bucket_id) + result.data = base64.encodestring(result.data) + return result + + + @server_api.post("/buckets/{bucket_id}/items/", response_model=schemas.BucketFile) + def create_bucketfile_in_bucket( + bucket_id: int, bucketfile: schemas.BucketFileCreate, db: Session = Depends(get_db) + ): + bucketfile.data = base64.decodestring(bucketfile.data) + result = crud.create_bucketfile(db=db, bucketfile=bucketfile, bucket_id=bucket_id) + result.data = base64.encodestring(result.data) + return result + + +def get_filename_from_url(url): + path = urlparse(url).path + name = path[path.rfind('/')+1:] + return name or None + +def get_path_from_url(url): + path = urlparse(url).path + path = path[0:path.rfind('/')] + return path or None + +def get_suffix_from_filename(filename): + return filename[filename.rfind('.')+1:] diff --git a/ui/easydiffusion/easydb/crud.py b/ui/easydiffusion/easydb/crud.py new file mode 100644 index 00000000..65bea255 --- /dev/null +++ b/ui/easydiffusion/easydb/crud.py @@ -0,0 +1,24 @@ +from sqlalchemy.orm import Session + +from easydiffusion.easydb import models, schemas + + +def get_bucket_by_path(db: Session, path: str): + return db.query(models.Bucket).filter(models.Bucket.path == path).first() + + +def create_bucket(db: Session, bucket: schemas.BucketCreate): + db_bucket = models.Bucket(path=bucket.path) + db.add(db_bucket) + db.commit() + db.refresh(db_bucket) + return db_bucket + + +def create_bucketfile(db: Session, bucketfile: schemas.BucketFileCreate, bucket_id: int): + db_bucketfile = models.BucketFile(**bucketfile.dict(), bucket_id=bucket_id) + db.merge(db_bucketfile) + db.commit() + db_bucketfile = db.query(models.BucketFile).filter(models.BucketFile.bucket_id==bucket_id, models.BucketFile.filename==bucketfile.filename).first() + return db_bucketfile + diff --git a/ui/easydiffusion/easydb/database.py b/ui/easydiffusion/easydb/database.py new file mode 100644 index 00000000..6cb43ecb --- /dev/null +++ b/ui/easydiffusion/easydb/database.py @@ -0,0 +1,14 @@ +import os +from easydiffusion import app + +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker + +os.makedirs(app.BUCKET_DIR, exist_ok=True) +SQLALCHEMY_DATABASE_URL = "sqlite:///"+os.path.join(app.BUCKET_DIR, "bucket.db") + +engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +BucketBase = declarative_base() diff --git a/ui/easydiffusion/easydb/models.py b/ui/easydiffusion/easydb/models.py new file mode 100644 index 00000000..04834951 --- /dev/null +++ b/ui/easydiffusion/easydb/models.py @@ -0,0 +1,25 @@ +from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, BLOB +from sqlalchemy.orm import relationship + +from easydiffusion.easydb.database import BucketBase + + +class Bucket(BucketBase): + __tablename__ = "bucket" + + id = Column(Integer, primary_key=True, index=True) + path = Column(String, unique=True, index=True) + + bucketfiles = relationship("BucketFile", back_populates="bucket") + + +class BucketFile(BucketBase): + __tablename__ = "bucketfile" + + filename = Column(String, index=True, primary_key=True) + bucket_id = Column(Integer, ForeignKey("bucket.id"), primary_key=True) + + data = Column(BLOB, index=False) + + bucket = relationship("Bucket", back_populates="bucketfiles") + diff --git a/ui/easydiffusion/easydb/schemas.py b/ui/easydiffusion/easydb/schemas.py new file mode 100644 index 00000000..68bc04e2 --- /dev/null +++ b/ui/easydiffusion/easydb/schemas.py @@ -0,0 +1,36 @@ +from typing import List, Union + +from pydantic import BaseModel + + +class BucketFileBase(BaseModel): + filename: str + data: bytes + + +class BucketFileCreate(BucketFileBase): + pass + + +class BucketFile(BucketFileBase): + bucket_id: int + + class Config: + orm_mode = True + + +class BucketBase(BaseModel): + path: str + + +class BucketCreate(BucketBase): + pass + + +class Bucket(BucketBase): + id: int + bucketfiles: List[BucketFile] = [] + + class Config: + orm_mode = True + diff --git a/ui/index.html b/ui/index.html index d3eafd5c..3d366498 100644 --- a/ui/index.html +++ b/ui/index.html @@ -18,12 +18,14 @@ + +
@@ -689,6 +691,15 @@ + +  
@@ -696,6 +707,34 @@
+ +
+
+

Use as thumbnail

+ Use a pictures as thumbnail for embeddings, LORAs, etc. +
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+ + +
+
+
+
+