From cbc48e31e16a8b72f27246ef6ce14a4303e15ac7 Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Sat, 19 Nov 2022 19:25:28 -0800 Subject: [PATCH 01/36] Fix duplicate custom modifiers activation states Fixing activation state for custom modifier cards sharing the same tag where only one of the cards gets (de)activated. --- ui/media/js/image-modifiers.js | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/ui/media/js/image-modifiers.js b/ui/media/js/image-modifiers.js index 8cf26a49..aada0e70 100644 --- a/ui/media/js/image-modifiers.js +++ b/ui/media/js/image-modifiers.js @@ -90,9 +90,7 @@ function createModifierGroup(modifierGroup, initiallyExpanded) { if (activeTags.map(x => x.name).includes(modifierName)) { // remove modifier from active array activeTags = activeTags.filter(x => x.name != modifierName) - modifierCard.classList.remove(activeCardClass) - - modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+' + toggleCardState(modifierName, false) } else { // add modifier to active array activeTags.push({ @@ -101,10 +99,7 @@ function createModifierGroup(modifierGroup, initiallyExpanded) { 'originElement': modifierCard, 'previews': modifierPreviews }) - - modifierCard.classList.add(activeCardClass) - - modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-' + toggleCardState(modifierName, true) } refreshTagsList() @@ -220,8 +215,7 @@ function refreshTagsList() { let idx = activeTags.indexOf(tag) if (idx !== -1 && activeTags[idx].originElement !== undefined) { - activeTags[idx].originElement.classList.remove(activeCardClass) - activeTags[idx].originElement.querySelector('.modifier-card-image-overlay').innerText = '+' + toggleCardState(activeTags[idx].name, false) activeTags.splice(idx, 1) refreshTagsList() @@ -234,6 +228,22 @@ function refreshTagsList() { editorModifierTagsList.appendChild(brk) } +function toggleCardState(modifierName, makeActive) { + document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(card => { + const name = card.querySelector('.modifier-card-label').innerText + if (modifierName == name) { + if(makeActive) { + card.classList.add(activeCardClass) + card.querySelector('.modifier-card-image-overlay').innerText = '-' + } + else{ + card.classList.remove(activeCardClass) + card.querySelector('.modifier-card-image-overlay').innerText = '+' + } + } + }) +} + function changePreviewImages(val) { const previewImages = document.querySelectorAll('.modifier-card-image-container img') From 0a21a69a9f6f551289ea06790af3e97e0704fd4c Mon Sep 17 00:00:00 2001 From: Marc-Andre Ferland Date: Sun, 20 Nov 2022 13:04:22 -0500 Subject: [PATCH 02/36] Updated facexlib fix for usage on multi-gpu. --- ui/sd_internal/runtime.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/ui/sd_internal/runtime.py b/ui/sd_internal/runtime.py index e217965d..6622e444 100644 --- a/ui/sd_internal/runtime.py +++ b/ui/sd_internal/runtime.py @@ -28,6 +28,8 @@ from gfpgan import GFPGANer from basicsr.archs.rrdbnet_arch import RRDBNet from realesrgan import RealESRGANer +from threading import Lock + import uuid logging.set_verbosity_error() @@ -35,7 +37,7 @@ logging.set_verbosity_error() # consts config_yaml = "optimizedSD/v1-inference.yaml" filename_regex = re.compile('[^a-zA-Z0-9]') -force_gfpgan_to_cuda0 = True # workaround: gfpgan currently works only on cuda:0 +gfpgan_temp_device_lock = Lock() # workaround: gfpgan currently can only start on one device at a time. # api stuff from sd_internal import device_manager @@ -253,12 +255,6 @@ def move_to_cpu(model): def load_model_gfpgan(): if thread_data.gfpgan_file is None: raise ValueError(f'Thread gfpgan_file is undefined.') - - # hack for a bug in facexlib: https://github.com/xinntao/facexlib/pull/19/files - from facexlib.detection import retinaface - retinaface.device = torch.device(thread_data.device) - print('forced retinaface.device to', thread_data.device) - model_path = thread_data.gfpgan_file + ".pth" thread_data.model_gfpgan = GFPGANer(device=torch.device(thread_data.device), model_path=model_path, upscale=1, arch='clean', channel_multiplier=2, bg_upsampler=None) print('loaded', thread_data.gfpgan_file, 'to', thread_data.model_gfpgan.device, 'precision', thread_data.precision) @@ -314,15 +310,23 @@ def apply_filters(filter_name, image_data, model_path=None): image_data.to(thread_data.device) if filter_name == 'gfpgan': - if model_path is not None and model_path != thread_data.gfpgan_file: - thread_data.gfpgan_file = model_path - load_model_gfpgan() - elif not thread_data.model_gfpgan: - load_model_gfpgan() - if thread_data.model_gfpgan is None: raise Exception('Model "gfpgan" not loaded.') - print('enhance with', thread_data.gfpgan_file, 'on', thread_data.model_gfpgan.device, 'precision', thread_data.precision) - _, _, output = thread_data.model_gfpgan.enhance(image_data[:,:,::-1], has_aligned=False, only_center_face=False, paste_back=True) - image_data = output[:,:,::-1] + # This lock is only ever used here. No need to use timeout for the request. Should never deadlock. + with gfpgan_temp_device_lock: # Wait for any other devices to complete before starting. + # hack for a bug in facexlib: https://github.com/xinntao/facexlib/pull/19/files + from facexlib.detection import retinaface + retinaface.device = torch.device(thread_data.device) + print('forced retinaface.device to', thread_data.device) + + if model_path is not None and model_path != thread_data.gfpgan_file: + thread_data.gfpgan_file = model_path + load_model_gfpgan() + elif not thread_data.model_gfpgan: + load_model_gfpgan() + if thread_data.model_gfpgan is None: raise Exception('Model "gfpgan" not loaded.') + + print('enhance with', thread_data.gfpgan_file, 'on', thread_data.model_gfpgan.device, 'precision', thread_data.precision) + _, _, output = thread_data.model_gfpgan.enhance(image_data[:,:,::-1], has_aligned=False, only_center_face=False, paste_back=True) + image_data = output[:,:,::-1] if filter_name == 'real_esrgan': if model_path is not None and model_path != thread_data.real_esrgan_file: From 49535deb2ec0683da02e150c89c183b491e1f71a Mon Sep 17 00:00:00 2001 From: JeLuF Date: Tue, 22 Nov 2022 21:27:36 +0100 Subject: [PATCH 03/36] Confirm 'Clear All' and 'Stop Task' Ask for a confimation before clearing the results pane or stopping a render task. The dialog can be skipped by holding down the shift key while clicking on the button. --- 3rd-PARTY-LICENSES | 27 +++++++++++++++++++++++ CHANGES.md | 2 ++ ui/index.html | 2 ++ ui/media/css/jquery-confirm.min.css | 9 ++++++++ ui/media/js/jquery-confirm.min.js | 10 +++++++++ ui/media/js/main.js | 34 ++++++++++++++++++++++++----- 6 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 3rd-PARTY-LICENSES create mode 100644 ui/media/css/jquery-confirm.min.css create mode 100644 ui/media/js/jquery-confirm.min.js diff --git a/3rd-PARTY-LICENSES b/3rd-PARTY-LICENSES new file mode 100644 index 00000000..0cf2619f --- /dev/null +++ b/3rd-PARTY-LICENSES @@ -0,0 +1,27 @@ +jquery-confirm +============== +https://craftpip.github.io/jquery-confirm/ + +jquery-confirm is licensed under the MIT license: + + The MIT License (MIT) + + Copyright (c) 2019 Boniface Pereira + + 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/CHANGES.md b/CHANGES.md index 2b5ae762..65b2864f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,8 +19,10 @@ - Configuration to prevent the browser from opening on startup - Lots of minor bug fixes - A `What's New?` tab in the UI +- Ask for a confimation before clearing the results pane or stopping a render task. The dialog can be skipped by holding down the shift key while clicking on the button. ### Detailed changelog +* 2.4.13 - 22 Nov 2022 - shiftOrConfirm for red buttons * 2.4.13 - 21 Nov 2022 - Change the modifier weight via mouse wheel, drag to reorder selected modifiers, and some more modifier-related fixes. Thanks @patriceac * 2.4.12 - 21 Nov 2022 - Another fix for improving how long images take to generate. Reduces the time taken for an enqueued task to start processing. * 2.4.11 - 21 Nov 2022 - Installer improvements: avoid crashing if the username contains a space or special characters, allow moving/renaming the folder after installation on Windows, whitespace fix on git apply diff --git a/ui/index.html b/ui/index.html index 25a94a13..50de5ce2 100644 --- a/ui/index.html +++ b/ui/index.html @@ -12,7 +12,9 @@ + + diff --git a/ui/media/css/jquery-confirm.min.css b/ui/media/css/jquery-confirm.min.css new file mode 100644 index 00000000..400f0b8d --- /dev/null +++ b/ui/media/css/jquery-confirm.min.css @@ -0,0 +1,9 @@ +/*! + * jquery-confirm v3.3.2 (http://craftpip.github.io/jquery-confirm/) + * Author: boniface pereira + * Website: www.craftpip.com + * Contact: hey@craftpip.com + * + * Copyright 2013-2017 jquery-confirm + * Licensed under MIT (https://github.com/craftpip/jquery-confirm/blob/master/LICENSE) + */@-webkit-keyframes jconfirm-spin{from{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes jconfirm-spin{from{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}body[class*=jconfirm-no-scroll-]{overflow:hidden!important}.jconfirm{position:fixed;top:0;left:0;right:0;bottom:0;z-index:99999999;font-family:inherit;overflow:hidden}.jconfirm .jconfirm-bg{position:fixed;top:0;left:0;right:0;bottom:0;-webkit-transition:opacity .4s;transition:opacity .4s}.jconfirm .jconfirm-bg.jconfirm-bg-h{opacity:0!important}.jconfirm .jconfirm-scrollpane{-webkit-perspective:500px;perspective:500px;-webkit-perspective-origin:center;perspective-origin:center;display:table;width:100%;height:100%}.jconfirm .jconfirm-row{display:table-row;width:100%}.jconfirm .jconfirm-cell{display:table-cell;vertical-align:middle}.jconfirm .jconfirm-holder{max-height:100%;padding:50px 0}.jconfirm .jconfirm-box-container{-webkit-transition:-webkit-transform;transition:-webkit-transform;transition:transform;transition:transform,-webkit-transform}.jconfirm .jconfirm-box-container.jconfirm-no-transition{-webkit-transition:none!important;transition:none!important}.jconfirm .jconfirm-box{background:white;border-radius:4px;position:relative;outline:0;padding:15px 15px 0;overflow:hidden;margin-left:auto;margin-right:auto}@-webkit-keyframes type-blue{1%,100%{border-color:#3498db}50%{border-color:#5faee3}}@keyframes type-blue{1%,100%{border-color:#3498db}50%{border-color:#5faee3}}@-webkit-keyframes type-green{1%,100%{border-color:#2ecc71}50%{border-color:#54d98c}}@keyframes type-green{1%,100%{border-color:#2ecc71}50%{border-color:#54d98c}}@-webkit-keyframes type-red{1%,100%{border-color:#e74c3c}50%{border-color:#ed7669}}@keyframes type-red{1%,100%{border-color:#e74c3c}50%{border-color:#ed7669}}@-webkit-keyframes type-orange{1%,100%{border-color:#f1c40f}50%{border-color:#f4d03f}}@keyframes type-orange{1%,100%{border-color:#f1c40f}50%{border-color:#f4d03f}}@-webkit-keyframes type-purple{1%,100%{border-color:#9b59b6}50%{border-color:#b07cc6}}@keyframes type-purple{1%,100%{border-color:#9b59b6}50%{border-color:#b07cc6}}@-webkit-keyframes type-dark{1%,100%{border-color:#34495e}50%{border-color:#46627f}}@keyframes type-dark{1%,100%{border-color:#34495e}50%{border-color:#46627f}}.jconfirm .jconfirm-box.jconfirm-type-animated{-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.jconfirm .jconfirm-box.jconfirm-type-blue{border-top:solid 7px #3498db;-webkit-animation-name:type-blue;animation-name:type-blue}.jconfirm .jconfirm-box.jconfirm-type-green{border-top:solid 7px #2ecc71;-webkit-animation-name:type-green;animation-name:type-green}.jconfirm .jconfirm-box.jconfirm-type-red{border-top:solid 7px #e74c3c;-webkit-animation-name:type-red;animation-name:type-red}.jconfirm .jconfirm-box.jconfirm-type-orange{border-top:solid 7px #f1c40f;-webkit-animation-name:type-orange;animation-name:type-orange}.jconfirm .jconfirm-box.jconfirm-type-purple{border-top:solid 7px #9b59b6;-webkit-animation-name:type-purple;animation-name:type-purple}.jconfirm .jconfirm-box.jconfirm-type-dark{border-top:solid 7px #34495e;-webkit-animation-name:type-dark;animation-name:type-dark}.jconfirm .jconfirm-box.loading{height:120px}.jconfirm .jconfirm-box.loading:before{content:'';position:absolute;left:0;background:white;right:0;top:0;bottom:0;border-radius:10px;z-index:1}.jconfirm .jconfirm-box.loading:after{opacity:.6;content:'';height:30px;width:30px;border:solid 3px transparent;position:absolute;left:50%;margin-left:-15px;border-radius:50%;-webkit-animation:jconfirm-spin 1s infinite linear;animation:jconfirm-spin 1s infinite linear;border-bottom-color:dodgerblue;top:50%;margin-top:-15px;z-index:2}.jconfirm .jconfirm-box div.jconfirm-closeIcon{height:20px;width:20px;position:absolute;top:10px;right:10px;cursor:pointer;opacity:.6;text-align:center;font-size:27px!important;line-height:14px!important;display:none;z-index:1}.jconfirm .jconfirm-box div.jconfirm-closeIcon:empty{display:none}.jconfirm .jconfirm-box div.jconfirm-closeIcon .fa{font-size:16px}.jconfirm .jconfirm-box div.jconfirm-closeIcon .glyphicon{font-size:16px}.jconfirm .jconfirm-box div.jconfirm-closeIcon .zmdi{font-size:16px}.jconfirm .jconfirm-box div.jconfirm-closeIcon:hover{opacity:1}.jconfirm .jconfirm-box div.jconfirm-title-c{display:block;font-size:22px;line-height:20px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default;padding-bottom:15px}.jconfirm .jconfirm-box div.jconfirm-title-c.jconfirm-hand{cursor:move}.jconfirm .jconfirm-box div.jconfirm-title-c .jconfirm-icon-c{font-size:inherit;display:inline-block;vertical-align:middle}.jconfirm .jconfirm-box div.jconfirm-title-c .jconfirm-icon-c i{vertical-align:middle}.jconfirm .jconfirm-box div.jconfirm-title-c .jconfirm-icon-c:empty{display:none}.jconfirm .jconfirm-box div.jconfirm-title-c .jconfirm-title{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;font-size:inherit;font-family:inherit;display:inline-block;vertical-align:middle}.jconfirm .jconfirm-box div.jconfirm-title-c .jconfirm-title:empty{display:none}.jconfirm .jconfirm-box div.jconfirm-content-pane{margin-bottom:15px;height:auto;-webkit-transition:height .4s ease-in;transition:height .4s ease-in;display:inline-block;width:100%;position:relative;overflow-x:hidden;overflow-y:auto}.jconfirm .jconfirm-box div.jconfirm-content-pane.no-scroll{overflow-y:hidden}.jconfirm .jconfirm-box div.jconfirm-content-pane::-webkit-scrollbar{width:3px}.jconfirm .jconfirm-box div.jconfirm-content-pane::-webkit-scrollbar-track{background:rgba(0,0,0,0.1)}.jconfirm .jconfirm-box div.jconfirm-content-pane::-webkit-scrollbar-thumb{background:#666;border-radius:3px}.jconfirm .jconfirm-box div.jconfirm-content-pane .jconfirm-content{overflow:auto}.jconfirm .jconfirm-box div.jconfirm-content-pane .jconfirm-content img{max-width:100%;height:auto}.jconfirm .jconfirm-box div.jconfirm-content-pane .jconfirm-content:empty{display:none}.jconfirm .jconfirm-box .jconfirm-buttons{padding-bottom:11px}.jconfirm .jconfirm-box .jconfirm-buttons>button{margin-bottom:4px;margin-left:2px;margin-right:2px}.jconfirm .jconfirm-box .jconfirm-buttons button{display:inline-block;padding:6px 12px;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border-radius:4px;min-height:1em;-webkit-transition:opacity .1s ease,background-color .1s ease,color .1s ease,background .1s ease,-webkit-box-shadow .1s ease;transition:opacity .1s ease,background-color .1s ease,color .1s ease,background .1s ease,-webkit-box-shadow .1s ease;transition:opacity .1s ease,background-color .1s ease,color .1s ease,box-shadow .1s ease,background .1s ease;transition:opacity .1s ease,background-color .1s ease,color .1s ease,box-shadow .1s ease,background .1s ease,-webkit-box-shadow .1s ease;-webkit-tap-highlight-color:transparent;border:0;background-image:none}.jconfirm .jconfirm-box .jconfirm-buttons button.btn-blue{background-color:#3498db;color:#FFF;text-shadow:none;-webkit-transition:background .2s;transition:background .2s}.jconfirm .jconfirm-box .jconfirm-buttons button.btn-blue:hover{background-color:#2980b9;color:#FFF}.jconfirm .jconfirm-box .jconfirm-buttons button.btn-green{background-color:#2ecc71;color:#FFF;text-shadow:none;-webkit-transition:background .2s;transition:background .2s}.jconfirm .jconfirm-box .jconfirm-buttons button.btn-green:hover{background-color:#27ae60;color:#FFF}.jconfirm .jconfirm-box .jconfirm-buttons button.btn-red{background-color:#e74c3c;color:#FFF;text-shadow:none;-webkit-transition:background .2s;transition:background .2s}.jconfirm .jconfirm-box .jconfirm-buttons button.btn-red:hover{background-color:#c0392b;color:#FFF}.jconfirm .jconfirm-box .jconfirm-buttons button.btn-orange{background-color:#f1c40f;color:#FFF;text-shadow:none;-webkit-transition:background .2s;transition:background .2s}.jconfirm .jconfirm-box .jconfirm-buttons button.btn-orange:hover{background-color:#f39c12;color:#FFF}.jconfirm .jconfirm-box .jconfirm-buttons button.btn-default{background-color:#ecf0f1;color:#000;text-shadow:none;-webkit-transition:background .2s;transition:background .2s}.jconfirm .jconfirm-box .jconfirm-buttons button.btn-default:hover{background-color:#bdc3c7;color:#000}.jconfirm .jconfirm-box .jconfirm-buttons button.btn-purple{background-color:#9b59b6;color:#FFF;text-shadow:none;-webkit-transition:background .2s;transition:background .2s}.jconfirm .jconfirm-box .jconfirm-buttons button.btn-purple:hover{background-color:#8e44ad;color:#FFF}.jconfirm .jconfirm-box .jconfirm-buttons button.btn-dark{background-color:#34495e;color:#FFF;text-shadow:none;-webkit-transition:background .2s;transition:background .2s}.jconfirm .jconfirm-box .jconfirm-buttons button.btn-dark:hover{background-color:#2c3e50;color:#FFF}.jconfirm .jconfirm-box.jconfirm-type-red .jconfirm-title-c .jconfirm-icon-c{color:#e74c3c!important}.jconfirm .jconfirm-box.jconfirm-type-blue .jconfirm-title-c .jconfirm-icon-c{color:#3498db!important}.jconfirm .jconfirm-box.jconfirm-type-green .jconfirm-title-c .jconfirm-icon-c{color:#2ecc71!important}.jconfirm .jconfirm-box.jconfirm-type-purple .jconfirm-title-c .jconfirm-icon-c{color:#9b59b6!important}.jconfirm .jconfirm-box.jconfirm-type-orange .jconfirm-title-c .jconfirm-icon-c{color:#f1c40f!important}.jconfirm .jconfirm-box.jconfirm-type-dark .jconfirm-title-c .jconfirm-icon-c{color:#34495e!important}.jconfirm .jconfirm-clear{clear:both}.jconfirm.jconfirm-rtl{direction:rtl}.jconfirm.jconfirm-rtl div.jconfirm-closeIcon{left:5px;right:auto}.jconfirm.jconfirm-white .jconfirm-bg,.jconfirm.jconfirm-light .jconfirm-bg{background-color:#444;opacity:.2}.jconfirm.jconfirm-white .jconfirm-box,.jconfirm.jconfirm-light .jconfirm-box{-webkit-box-shadow:0 2px 6px rgba(0,0,0,0.2);box-shadow:0 2px 6px rgba(0,0,0,0.2);border-radius:5px}.jconfirm.jconfirm-white .jconfirm-box .jconfirm-title-c .jconfirm-icon-c,.jconfirm.jconfirm-light .jconfirm-box .jconfirm-title-c .jconfirm-icon-c{margin-right:8px;margin-left:0}.jconfirm.jconfirm-white .jconfirm-box .jconfirm-buttons,.jconfirm.jconfirm-light .jconfirm-box .jconfirm-buttons{float:right}.jconfirm.jconfirm-white .jconfirm-box .jconfirm-buttons button,.jconfirm.jconfirm-light .jconfirm-box .jconfirm-buttons button{text-transform:uppercase;font-size:14px;font-weight:bold;text-shadow:none}.jconfirm.jconfirm-white .jconfirm-box .jconfirm-buttons button.btn-default,.jconfirm.jconfirm-light .jconfirm-box .jconfirm-buttons button.btn-default{-webkit-box-shadow:none;box-shadow:none;color:#333}.jconfirm.jconfirm-white .jconfirm-box .jconfirm-buttons button.btn-default:hover,.jconfirm.jconfirm-light .jconfirm-box .jconfirm-buttons button.btn-default:hover{background:#ddd}.jconfirm.jconfirm-white.jconfirm-rtl .jconfirm-title-c .jconfirm-icon-c,.jconfirm.jconfirm-light.jconfirm-rtl .jconfirm-title-c .jconfirm-icon-c{margin-left:8px;margin-right:0}.jconfirm.jconfirm-black .jconfirm-bg,.jconfirm.jconfirm-dark .jconfirm-bg{background-color:darkslategray;opacity:.4}.jconfirm.jconfirm-black .jconfirm-box,.jconfirm.jconfirm-dark .jconfirm-box{-webkit-box-shadow:0 2px 6px rgba(0,0,0,0.2);box-shadow:0 2px 6px rgba(0,0,0,0.2);background:#444;border-radius:5px;color:white}.jconfirm.jconfirm-black .jconfirm-box .jconfirm-title-c .jconfirm-icon-c,.jconfirm.jconfirm-dark .jconfirm-box .jconfirm-title-c .jconfirm-icon-c{margin-right:8px;margin-left:0}.jconfirm.jconfirm-black .jconfirm-box .jconfirm-buttons,.jconfirm.jconfirm-dark .jconfirm-box .jconfirm-buttons{float:right}.jconfirm.jconfirm-black .jconfirm-box .jconfirm-buttons button,.jconfirm.jconfirm-dark .jconfirm-box .jconfirm-buttons button{border:0;background-image:none;text-transform:uppercase;font-size:14px;font-weight:bold;text-shadow:none;-webkit-transition:background .1s;transition:background .1s;color:white}.jconfirm.jconfirm-black .jconfirm-box .jconfirm-buttons button.btn-default,.jconfirm.jconfirm-dark .jconfirm-box .jconfirm-buttons button.btn-default{-webkit-box-shadow:none;box-shadow:none;color:#fff;background:0}.jconfirm.jconfirm-black .jconfirm-box .jconfirm-buttons button.btn-default:hover,.jconfirm.jconfirm-dark .jconfirm-box .jconfirm-buttons button.btn-default:hover{background:#666}.jconfirm.jconfirm-black.jconfirm-rtl .jconfirm-title-c .jconfirm-icon-c,.jconfirm.jconfirm-dark.jconfirm-rtl .jconfirm-title-c .jconfirm-icon-c{margin-left:8px;margin-right:0}.jconfirm .jconfirm-box.hilight.jconfirm-hilight-shake{-webkit-animation:shake .82s cubic-bezier(0.36,0.07,0.19,0.97) both;animation:shake .82s cubic-bezier(0.36,0.07,0.19,0.97) both;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.jconfirm .jconfirm-box.hilight.jconfirm-hilight-glow{-webkit-animation:glow .82s cubic-bezier(0.36,0.07,0.19,0.97) both;animation:glow .82s cubic-bezier(0.36,0.07,0.19,0.97) both;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@-webkit-keyframes shake{10%,90%{-webkit-transform:translate3d(-2px,0,0);transform:translate3d(-2px,0,0)}20%,80%{-webkit-transform:translate3d(4px,0,0);transform:translate3d(4px,0,0)}30%,50%,70%{-webkit-transform:translate3d(-8px,0,0);transform:translate3d(-8px,0,0)}40%,60%{-webkit-transform:translate3d(8px,0,0);transform:translate3d(8px,0,0)}}@keyframes shake{10%,90%{-webkit-transform:translate3d(-2px,0,0);transform:translate3d(-2px,0,0)}20%,80%{-webkit-transform:translate3d(4px,0,0);transform:translate3d(4px,0,0)}30%,50%,70%{-webkit-transform:translate3d(-8px,0,0);transform:translate3d(-8px,0,0)}40%,60%{-webkit-transform:translate3d(8px,0,0);transform:translate3d(8px,0,0)}}@-webkit-keyframes glow{0%,100%{-webkit-box-shadow:0 0 0 red;box-shadow:0 0 0 red}50%{-webkit-box-shadow:0 0 30px red;box-shadow:0 0 30px red}}@keyframes glow{0%,100%{-webkit-box-shadow:0 0 0 red;box-shadow:0 0 0 red}50%{-webkit-box-shadow:0 0 30px red;box-shadow:0 0 30px red}}.jconfirm{-webkit-perspective:400px;perspective:400px}.jconfirm .jconfirm-box{opacity:1;-webkit-transition-property:all;transition-property:all}.jconfirm .jconfirm-box.jconfirm-animation-top,.jconfirm .jconfirm-box.jconfirm-animation-left,.jconfirm .jconfirm-box.jconfirm-animation-right,.jconfirm .jconfirm-box.jconfirm-animation-bottom,.jconfirm .jconfirm-box.jconfirm-animation-opacity,.jconfirm .jconfirm-box.jconfirm-animation-zoom,.jconfirm .jconfirm-box.jconfirm-animation-scale,.jconfirm .jconfirm-box.jconfirm-animation-none,.jconfirm .jconfirm-box.jconfirm-animation-rotate,.jconfirm .jconfirm-box.jconfirm-animation-rotatex,.jconfirm .jconfirm-box.jconfirm-animation-rotatey,.jconfirm .jconfirm-box.jconfirm-animation-scaley,.jconfirm .jconfirm-box.jconfirm-animation-scalex{opacity:0}.jconfirm .jconfirm-box.jconfirm-animation-rotate{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.jconfirm .jconfirm-box.jconfirm-animation-rotatex{-webkit-transform:rotateX(90deg);transform:rotateX(90deg);-webkit-transform-origin:center;transform-origin:center}.jconfirm .jconfirm-box.jconfirm-animation-rotatexr{-webkit-transform:rotateX(-90deg);transform:rotateX(-90deg);-webkit-transform-origin:center;transform-origin:center}.jconfirm .jconfirm-box.jconfirm-animation-rotatey{-webkit-transform:rotatey(90deg);transform:rotatey(90deg);-webkit-transform-origin:center;transform-origin:center}.jconfirm .jconfirm-box.jconfirm-animation-rotateyr{-webkit-transform:rotatey(-90deg);transform:rotatey(-90deg);-webkit-transform-origin:center;transform-origin:center}.jconfirm .jconfirm-box.jconfirm-animation-scaley{-webkit-transform:scaley(1.5);transform:scaley(1.5);-webkit-transform-origin:center;transform-origin:center}.jconfirm .jconfirm-box.jconfirm-animation-scalex{-webkit-transform:scalex(1.5);transform:scalex(1.5);-webkit-transform-origin:center;transform-origin:center}.jconfirm .jconfirm-box.jconfirm-animation-top{-webkit-transform:translate(0px,-100px);transform:translate(0px,-100px)}.jconfirm .jconfirm-box.jconfirm-animation-left{-webkit-transform:translate(-100px,0px);transform:translate(-100px,0px)}.jconfirm .jconfirm-box.jconfirm-animation-right{-webkit-transform:translate(100px,0px);transform:translate(100px,0px)}.jconfirm .jconfirm-box.jconfirm-animation-bottom{-webkit-transform:translate(0px,100px);transform:translate(0px,100px)}.jconfirm .jconfirm-box.jconfirm-animation-zoom{-webkit-transform:scale(1.2);transform:scale(1.2)}.jconfirm .jconfirm-box.jconfirm-animation-scale{-webkit-transform:scale(0.5);transform:scale(0.5)}.jconfirm .jconfirm-box.jconfirm-animation-none{visibility:hidden}.jconfirm.jconfirm-supervan .jconfirm-bg{background-color:rgba(54,70,93,0.95)}.jconfirm.jconfirm-supervan .jconfirm-box{background-color:transparent}.jconfirm.jconfirm-supervan .jconfirm-box.jconfirm-type-blue{border:0}.jconfirm.jconfirm-supervan .jconfirm-box.jconfirm-type-green{border:0}.jconfirm.jconfirm-supervan .jconfirm-box.jconfirm-type-red{border:0}.jconfirm.jconfirm-supervan .jconfirm-box.jconfirm-type-orange{border:0}.jconfirm.jconfirm-supervan .jconfirm-box.jconfirm-type-purple{border:0}.jconfirm.jconfirm-supervan .jconfirm-box.jconfirm-type-dark{border:0}.jconfirm.jconfirm-supervan .jconfirm-box div.jconfirm-closeIcon{color:white}.jconfirm.jconfirm-supervan .jconfirm-box div.jconfirm-title-c{text-align:center;color:white;font-size:28px;font-weight:normal}.jconfirm.jconfirm-supervan .jconfirm-box div.jconfirm-title-c>*{padding-bottom:25px}.jconfirm.jconfirm-supervan .jconfirm-box div.jconfirm-title-c .jconfirm-icon-c{margin-right:8px;margin-left:0}.jconfirm.jconfirm-supervan .jconfirm-box div.jconfirm-content-pane{margin-bottom:25px}.jconfirm.jconfirm-supervan .jconfirm-box div.jconfirm-content{text-align:center;color:white}.jconfirm.jconfirm-supervan .jconfirm-box .jconfirm-buttons{text-align:center}.jconfirm.jconfirm-supervan .jconfirm-box .jconfirm-buttons button{font-size:16px;border-radius:2px;background:#303f53;text-shadow:none;border:0;color:white;padding:10px;min-width:100px}.jconfirm.jconfirm-supervan.jconfirm-rtl .jconfirm-box div.jconfirm-title-c .jconfirm-icon-c{margin-left:8px;margin-right:0}.jconfirm.jconfirm-material .jconfirm-bg{background-color:rgba(0,0,0,0.67)}.jconfirm.jconfirm-material .jconfirm-box{background-color:white;-webkit-box-shadow:0 7px 8px -4px rgba(0,0,0,0.2),0 13px 19px 2px rgba(0,0,0,0.14),0 5px 24px 4px rgba(0,0,0,0.12);box-shadow:0 7px 8px -4px rgba(0,0,0,0.2),0 13px 19px 2px rgba(0,0,0,0.14),0 5px 24px 4px rgba(0,0,0,0.12);padding:30px 25px 10px 25px}.jconfirm.jconfirm-material .jconfirm-box .jconfirm-title-c .jconfirm-icon-c{margin-right:8px;margin-left:0}.jconfirm.jconfirm-material .jconfirm-box div.jconfirm-closeIcon{color:rgba(0,0,0,0.87)}.jconfirm.jconfirm-material .jconfirm-box div.jconfirm-title-c{color:rgba(0,0,0,0.87);font-size:22px;font-weight:bold}.jconfirm.jconfirm-material .jconfirm-box div.jconfirm-content{color:rgba(0,0,0,0.87)}.jconfirm.jconfirm-material .jconfirm-box .jconfirm-buttons{text-align:right}.jconfirm.jconfirm-material .jconfirm-box .jconfirm-buttons button{text-transform:uppercase;font-weight:500}.jconfirm.jconfirm-material.jconfirm-rtl .jconfirm-title-c .jconfirm-icon-c{margin-left:8px;margin-right:0}.jconfirm.jconfirm-bootstrap .jconfirm-bg{background-color:rgba(0,0,0,0.21)}.jconfirm.jconfirm-bootstrap .jconfirm-box{background-color:white;-webkit-box-shadow:0 3px 8px 0 rgba(0,0,0,0.2);box-shadow:0 3px 8px 0 rgba(0,0,0,0.2);border:solid 1px rgba(0,0,0,0.4);padding:15px 0 0}.jconfirm.jconfirm-bootstrap .jconfirm-box .jconfirm-title-c .jconfirm-icon-c{margin-right:8px;margin-left:0}.jconfirm.jconfirm-bootstrap .jconfirm-box div.jconfirm-closeIcon{color:rgba(0,0,0,0.87)}.jconfirm.jconfirm-bootstrap .jconfirm-box div.jconfirm-title-c{color:rgba(0,0,0,0.87);font-size:22px;font-weight:bold;padding-left:15px;padding-right:15px}.jconfirm.jconfirm-bootstrap .jconfirm-box div.jconfirm-content{color:rgba(0,0,0,0.87);padding:0 15px}.jconfirm.jconfirm-bootstrap .jconfirm-box .jconfirm-buttons{text-align:right;padding:10px;margin:-5px 0 0;border-top:solid 1px #ddd;overflow:hidden;border-radius:0 0 4px 4px}.jconfirm.jconfirm-bootstrap .jconfirm-box .jconfirm-buttons button{font-weight:500}.jconfirm.jconfirm-bootstrap.jconfirm-rtl .jconfirm-title-c .jconfirm-icon-c{margin-left:8px;margin-right:0}.jconfirm.jconfirm-modern .jconfirm-bg{background-color:slategray;opacity:.6}.jconfirm.jconfirm-modern .jconfirm-box{background-color:white;-webkit-box-shadow:0 7px 8px -4px rgba(0,0,0,0.2),0 13px 19px 2px rgba(0,0,0,0.14),0 5px 24px 4px rgba(0,0,0,0.12);box-shadow:0 7px 8px -4px rgba(0,0,0,0.2),0 13px 19px 2px rgba(0,0,0,0.14),0 5px 24px 4px rgba(0,0,0,0.12);padding:30px 30px 15px}.jconfirm.jconfirm-modern .jconfirm-box div.jconfirm-closeIcon{color:rgba(0,0,0,0.87);top:15px;right:15px}.jconfirm.jconfirm-modern .jconfirm-box div.jconfirm-title-c{color:rgba(0,0,0,0.87);font-size:24px;font-weight:bold;text-align:center;margin-bottom:10px}.jconfirm.jconfirm-modern .jconfirm-box div.jconfirm-title-c .jconfirm-icon-c{-webkit-transition:-webkit-transform .5s;transition:-webkit-transform .5s;transition:transform .5s;transition:transform .5s,-webkit-transform .5s;-webkit-transform:scale(0);transform:scale(0);display:block;margin-right:0;margin-left:0;margin-bottom:10px;font-size:69px;color:#aaa}.jconfirm.jconfirm-modern .jconfirm-box div.jconfirm-content{text-align:center;font-size:15px;color:#777;margin-bottom:25px}.jconfirm.jconfirm-modern .jconfirm-box .jconfirm-buttons{text-align:center}.jconfirm.jconfirm-modern .jconfirm-box .jconfirm-buttons button{font-weight:bold;text-transform:uppercase;-webkit-transition:background .1s;transition:background .1s;padding:10px 20px}.jconfirm.jconfirm-modern .jconfirm-box .jconfirm-buttons button+button{margin-left:4px}.jconfirm.jconfirm-modern.jconfirm-open .jconfirm-box .jconfirm-title-c .jconfirm-icon-c{-webkit-transform:scale(1);transform:scale(1)} \ No newline at end of file diff --git a/ui/media/js/jquery-confirm.min.js b/ui/media/js/jquery-confirm.min.js new file mode 100644 index 00000000..a2498bba --- /dev/null +++ b/ui/media/js/jquery-confirm.min.js @@ -0,0 +1,10 @@ +/*! + * jquery-confirm v3.3.2 (http://craftpip.github.io/jquery-confirm/) + * Author: Boniface Pereira + * Website: www.craftpip.com + * Contact: hey@craftpip.com + * + * Copyright 2013-2017 jquery-confirm + * Licensed under MIT (https://github.com/craftpip/jquery-confirm/blob/master/LICENSE) + */ +if(typeof jQuery==="undefined"){throw new Error("jquery-confirm requires jQuery");}var jconfirm,Jconfirm;(function($,window){$.fn.confirm=function(options,option2){if(typeof options==="undefined"){options={};}if(typeof options==="string"){options={content:options,title:(option2)?option2:false};}$(this).each(function(){var $this=$(this);if($this.attr("jc-attached")){console.warn("jConfirm has already been attached to this element ",$this[0]);return;}$this.on("click",function(e){e.preventDefault();var jcOption=$.extend({},options);if($this.attr("data-title")){jcOption.title=$this.attr("data-title");}if($this.attr("data-content")){jcOption.content=$this.attr("data-content");}if(typeof jcOption.buttons=="undefined"){jcOption.buttons={};}jcOption["$target"]=$this;if($this.attr("href")&&Object.keys(jcOption.buttons).length==0){var buttons=$.extend(true,{},jconfirm.pluginDefaults.defaultButtons,(jconfirm.defaults||{}).defaultButtons||{});var firstBtn=Object.keys(buttons)[0];jcOption.buttons=buttons;jcOption.buttons[firstBtn].action=function(){location.href=$this.attr("href");};}jcOption.closeIcon=false;var instance=$.confirm(jcOption);});$this.attr("jc-attached",true);});return $(this);};$.confirm=function(options,option2){if(typeof options==="undefined"){options={};}if(typeof options==="string"){options={content:options,title:(option2)?option2:false};}var putDefaultButtons=!(options.buttons==false);if(typeof options.buttons!="object"){options.buttons={};}if(Object.keys(options.buttons).length==0&&putDefaultButtons){var buttons=$.extend(true,{},jconfirm.pluginDefaults.defaultButtons,(jconfirm.defaults||{}).defaultButtons||{});options.buttons=buttons;}return jconfirm(options);};$.alert=function(options,option2){if(typeof options==="undefined"){options={};}if(typeof options==="string"){options={content:options,title:(option2)?option2:false};}var putDefaultButtons=!(options.buttons==false);if(typeof options.buttons!="object"){options.buttons={};}if(Object.keys(options.buttons).length==0&&putDefaultButtons){var buttons=$.extend(true,{},jconfirm.pluginDefaults.defaultButtons,(jconfirm.defaults||{}).defaultButtons||{});var firstBtn=Object.keys(buttons)[0];options.buttons[firstBtn]=buttons[firstBtn];}return jconfirm(options);};$.dialog=function(options,option2){if(typeof options==="undefined"){options={};}if(typeof options==="string"){options={content:options,title:(option2)?option2:false,closeIcon:function(){}};}options.buttons={};if(typeof options.closeIcon=="undefined"){options.closeIcon=function(){};}options.confirmKeys=[13];return jconfirm(options);};jconfirm=function(options){if(typeof options==="undefined"){options={};}var pluginOptions=$.extend(true,{},jconfirm.pluginDefaults);if(jconfirm.defaults){pluginOptions=$.extend(true,pluginOptions,jconfirm.defaults);}pluginOptions=$.extend(true,{},pluginOptions,options);var instance=new Jconfirm(pluginOptions);jconfirm.instances.push(instance);return instance;};Jconfirm=function(options){$.extend(this,options);this._init();};Jconfirm.prototype={_init:function(){var that=this;if(!jconfirm.instances.length){jconfirm.lastFocused=$("body").find(":focus");}this._id=Math.round(Math.random()*99999);this.contentParsed=$(document.createElement("div"));if(!this.lazyOpen){setTimeout(function(){that.open();},0);}},_buildHTML:function(){var that=this;this._parseAnimation(this.animation,"o");this._parseAnimation(this.closeAnimation,"c");this._parseBgDismissAnimation(this.backgroundDismissAnimation);this._parseColumnClass(this.columnClass);this._parseTheme(this.theme);this._parseType(this.type);var template=$(this.template);template.find(".jconfirm-box").addClass(this.animationParsed).addClass(this.backgroundDismissAnimationParsed).addClass(this.typeParsed);if(this.typeAnimated){template.find(".jconfirm-box").addClass("jconfirm-type-animated");}if(this.useBootstrap){template.find(".jc-bs3-row").addClass(this.bootstrapClasses.row);template.find(".jc-bs3-row").addClass("justify-content-md-center justify-content-sm-center justify-content-xs-center justify-content-lg-center");template.find(".jconfirm-box-container").addClass(this.columnClassParsed);if(this.containerFluid){template.find(".jc-bs3-container").addClass(this.bootstrapClasses.containerFluid);}else{template.find(".jc-bs3-container").addClass(this.bootstrapClasses.container);}}else{template.find(".jconfirm-box").css("width",this.boxWidth);}if(this.titleClass){template.find(".jconfirm-title-c").addClass(this.titleClass);}template.addClass(this.themeParsed);var ariaLabel="jconfirm-box"+this._id;template.find(".jconfirm-box").attr("aria-labelledby",ariaLabel).attr("tabindex",-1);template.find(".jconfirm-content").attr("id",ariaLabel);if(this.bgOpacity!==null){template.find(".jconfirm-bg").css("opacity",this.bgOpacity);}if(this.rtl){template.addClass("jconfirm-rtl");}this.$el=template.appendTo(this.container);this.$jconfirmBoxContainer=this.$el.find(".jconfirm-box-container");this.$jconfirmBox=this.$body=this.$el.find(".jconfirm-box");this.$jconfirmBg=this.$el.find(".jconfirm-bg");this.$title=this.$el.find(".jconfirm-title");this.$titleContainer=this.$el.find(".jconfirm-title-c");this.$content=this.$el.find("div.jconfirm-content");this.$contentPane=this.$el.find(".jconfirm-content-pane");this.$icon=this.$el.find(".jconfirm-icon-c");this.$closeIcon=this.$el.find(".jconfirm-closeIcon");this.$holder=this.$el.find(".jconfirm-holder");this.$btnc=this.$el.find(".jconfirm-buttons");this.$scrollPane=this.$el.find(".jconfirm-scrollpane");that.setStartingPoint();this._contentReady=$.Deferred();this._modalReady=$.Deferred();this.$holder.css({"padding-top":this.offsetTop,"padding-bottom":this.offsetBottom,});this.setTitle();this.setIcon();this._setButtons();this._parseContent();this.initDraggable();if(this.isAjax){this.showLoading(false);}$.when(this._contentReady,this._modalReady).then(function(){if(that.isAjaxLoading){setTimeout(function(){that.isAjaxLoading=false;that.setContent();that.setTitle();that.setIcon();setTimeout(function(){that.hideLoading(false);that._updateContentMaxHeight();},100);if(typeof that.onContentReady==="function"){that.onContentReady();}},50);}else{that._updateContentMaxHeight();that.setTitle();that.setIcon();if(typeof that.onContentReady==="function"){that.onContentReady();}}if(that.autoClose){that._startCountDown();}});this._watchContent();if(this.animation==="none"){this.animationSpeed=1;this.animationBounce=1;}this.$body.css(this._getCSS(this.animationSpeed,this.animationBounce));this.$contentPane.css(this._getCSS(this.animationSpeed,1));this.$jconfirmBg.css(this._getCSS(this.animationSpeed,1));this.$jconfirmBoxContainer.css(this._getCSS(this.animationSpeed,1));},_typePrefix:"jconfirm-type-",typeParsed:"",_parseType:function(type){this.typeParsed=this._typePrefix+type;},setType:function(type){var oldClass=this.typeParsed;this._parseType(type);this.$jconfirmBox.removeClass(oldClass).addClass(this.typeParsed);},themeParsed:"",_themePrefix:"jconfirm-",setTheme:function(theme){var previous=this.theme;this.theme=theme||this.theme;this._parseTheme(this.theme);if(previous){this.$el.removeClass(previous);}this.$el.addClass(this.themeParsed);this.theme=theme;},_parseTheme:function(theme){var that=this;theme=theme.split(",");$.each(theme,function(k,a){if(a.indexOf(that._themePrefix)===-1){theme[k]=that._themePrefix+$.trim(a);}});this.themeParsed=theme.join(" ").toLowerCase();},backgroundDismissAnimationParsed:"",_bgDismissPrefix:"jconfirm-hilight-",_parseBgDismissAnimation:function(bgDismissAnimation){var animation=bgDismissAnimation.split(",");var that=this;$.each(animation,function(k,a){if(a.indexOf(that._bgDismissPrefix)===-1){animation[k]=that._bgDismissPrefix+$.trim(a);}});this.backgroundDismissAnimationParsed=animation.join(" ").toLowerCase();},animationParsed:"",closeAnimationParsed:"",_animationPrefix:"jconfirm-animation-",setAnimation:function(animation){this.animation=animation||this.animation;this._parseAnimation(this.animation,"o");},_parseAnimation:function(animation,which){which=which||"o";var animations=animation.split(",");var that=this;$.each(animations,function(k,a){if(a.indexOf(that._animationPrefix)===-1){animations[k]=that._animationPrefix+$.trim(a);}});var a_string=animations.join(" ").toLowerCase();if(which==="o"){this.animationParsed=a_string;}else{this.closeAnimationParsed=a_string;}return a_string;},setCloseAnimation:function(closeAnimation){this.closeAnimation=closeAnimation||this.closeAnimation;this._parseAnimation(this.closeAnimation,"c");},setAnimationSpeed:function(speed){this.animationSpeed=speed||this.animationSpeed;},columnClassParsed:"",setColumnClass:function(colClass){if(!this.useBootstrap){console.warn("cannot set columnClass, useBootstrap is set to false");return;}this.columnClass=colClass||this.columnClass;this._parseColumnClass(this.columnClass);this.$jconfirmBoxContainer.addClass(this.columnClassParsed);},_updateContentMaxHeight:function(){var height=$(window).height()-(this.$jconfirmBox.outerHeight()-this.$contentPane.outerHeight())-(this.offsetTop+this.offsetBottom);this.$contentPane.css({"max-height":height+"px"});},setBoxWidth:function(width){if(this.useBootstrap){console.warn("cannot set boxWidth, useBootstrap is set to true");return;}this.boxWidth=width;this.$jconfirmBox.css("width",width);},_parseColumnClass:function(colClass){colClass=colClass.toLowerCase();var p;switch(colClass){case"xl":case"xlarge":p="col-md-12";break;case"l":case"large":p="col-md-8 col-md-offset-2";break;case"m":case"medium":p="col-md-6 col-md-offset-3";break;case"s":case"small":p="col-md-4 col-md-offset-4";break;case"xs":case"xsmall":p="col-md-2 col-md-offset-5";break;default:p=colClass;}this.columnClassParsed=p;},initDraggable:function(){var that=this;var $t=this.$titleContainer;this.resetDrag();if(this.draggable){$t.on("mousedown",function(e){$t.addClass("jconfirm-hand");that.mouseX=e.clientX;that.mouseY=e.clientY;that.isDrag=true;});$(window).on("mousemove."+this._id,function(e){if(that.isDrag){that.movingX=e.clientX-that.mouseX+that.initialX;that.movingY=e.clientY-that.mouseY+that.initialY;that.setDrag();}});$(window).on("mouseup."+this._id,function(){$t.removeClass("jconfirm-hand");if(that.isDrag){that.isDrag=false;that.initialX=that.movingX;that.initialY=that.movingY;}});}},resetDrag:function(){this.isDrag=false;this.initialX=0;this.initialY=0;this.movingX=0;this.movingY=0;this.mouseX=0;this.mouseY=0;this.$jconfirmBoxContainer.css("transform","translate("+0+"px, "+0+"px)");},setDrag:function(){if(!this.draggable){return;}this.alignMiddle=false;var boxWidth=this.$jconfirmBox.outerWidth();var boxHeight=this.$jconfirmBox.outerHeight();var windowWidth=$(window).width();var windowHeight=$(window).height();var that=this;var dragUpdate=1;if(that.movingX%dragUpdate===0||that.movingY%dragUpdate===0){if(that.dragWindowBorder){var leftDistance=(windowWidth/2)-boxWidth/2;var topDistance=(windowHeight/2)-boxHeight/2;topDistance-=that.dragWindowGap;leftDistance-=that.dragWindowGap;if(leftDistance+that.movingX<0){that.movingX=-leftDistance;}else{if(leftDistance-that.movingX<0){that.movingX=leftDistance;}}if(topDistance+that.movingY<0){that.movingY=-topDistance;}else{if(topDistance-that.movingY<0){that.movingY=topDistance;}}}that.$jconfirmBoxContainer.css("transform","translate("+that.movingX+"px, "+that.movingY+"px)");}},_scrollTop:function(){if(typeof pageYOffset!=="undefined"){return pageYOffset;}else{var B=document.body;var D=document.documentElement;D=(D.clientHeight)?D:B;return D.scrollTop;}},_watchContent:function(){var that=this;if(this._timer){clearInterval(this._timer);}var prevContentHeight=0;this._timer=setInterval(function(){if(that.smoothContent){var contentHeight=that.$content.outerHeight()||0;if(contentHeight!==prevContentHeight){that.$contentPane.css({height:contentHeight}).scrollTop(0);prevContentHeight=contentHeight;}var wh=$(window).height();var total=that.offsetTop+that.offsetBottom+that.$jconfirmBox.height()-that.$contentPane.height()+that.$content.height();if(total').html(that.buttons[key].text).addClass(that.buttons[key].btnClass).prop("disabled",that.buttons[key].isDisabled).css("display",that.buttons[key].isHidden?"none":"").click(function(e){e.preventDefault();var res=that.buttons[key].action.apply(that,[that.buttons[key]]);that.onAction.apply(that,[key,that.buttons[key]]);that._stopCountDown();if(typeof res==="undefined"||res){that.close();}});that.buttons[key].el=button_element;that.buttons[key].setText=function(text){button_element.html(text);};that.buttons[key].addClass=function(className){button_element.addClass(className);};that.buttons[key].removeClass=function(className){button_element.removeClass(className);};that.buttons[key].disable=function(){that.buttons[key].isDisabled=true;button_element.prop("disabled",true);};that.buttons[key].enable=function(){that.buttons[key].isDisabled=false;button_element.prop("disabled",false);};that.buttons[key].show=function(){that.buttons[key].isHidden=false;button_element.css("display","");};that.buttons[key].hide=function(){that.buttons[key].isHidden=true;button_element.css("display","none");};that["$_"+key]=that["$$"+key]=button_element;that.$btnc.append(button_element);});if(total_buttons===0){this.$btnc.hide();}if(this.closeIcon===null&&total_buttons===0){this.closeIcon=true;}if(this.closeIcon){if(this.closeIconClass){var closeHtml='';this.$closeIcon.html(closeHtml);}this.$closeIcon.click(function(e){e.preventDefault();var buttonName=false;var shouldClose=false;var str;if(typeof that.closeIcon=="function"){str=that.closeIcon();}else{str=that.closeIcon;}if(typeof str=="string"&&typeof that.buttons[str]!="undefined"){buttonName=str;shouldClose=false;}else{if(typeof str=="undefined"||!!(str)==true){shouldClose=true;}else{shouldClose=false;}}if(buttonName){var btnResponse=that.buttons[buttonName].action.apply(that);shouldClose=(typeof btnResponse=="undefined")||!!(btnResponse);}if(shouldClose){that.close();}});this.$closeIcon.show();}else{this.$closeIcon.hide();}},setTitle:function(string,force){force=force||false;if(typeof string!=="undefined"){if(typeof string=="string"){this.title=string;}else{if(typeof string=="function"){if(typeof string.promise=="function"){console.error("Promise was returned from title function, this is not supported.");}var response=string();if(typeof response=="string"){this.title=response;}else{this.title=false;}}else{this.title=false;}}}if(this.isAjaxLoading&&!force){return;}this.$title.html(this.title||"");this.updateTitleContainer();},setIcon:function(iconClass,force){force=force||false;if(typeof iconClass!=="undefined"){if(typeof iconClass=="string"){this.icon=iconClass;}else{if(typeof iconClass==="function"){var response=iconClass();if(typeof response=="string"){this.icon=response;}else{this.icon=false;}}else{this.icon=false;}}}if(this.isAjaxLoading&&!force){return;}this.$icon.html(this.icon?'':"");this.updateTitleContainer();},updateTitleContainer:function(){if(!this.title&&!this.icon){this.$titleContainer.hide();}else{this.$titleContainer.show();}},setContentPrepend:function(content,force){if(!content){return;}this.contentParsed.prepend(content);},setContentAppend:function(content){if(!content){return;}this.contentParsed.append(content);},setContent:function(content,force){force=!!force;var that=this;if(content){this.contentParsed.html("").append(content);}if(this.isAjaxLoading&&!force){return;}this.$content.html("");this.$content.append(this.contentParsed);setTimeout(function(){that.$body.find("input[autofocus]:visible:first").focus();},100);},loadingSpinner:false,showLoading:function(disableButtons){this.loadingSpinner=true;this.$jconfirmBox.addClass("loading");if(disableButtons){this.$btnc.find("button").prop("disabled",true);}},hideLoading:function(enableButtons){this.loadingSpinner=false;this.$jconfirmBox.removeClass("loading");if(enableButtons){this.$btnc.find("button").prop("disabled",false);}},ajaxResponse:false,contentParsed:"",isAjax:false,isAjaxLoading:false,_parseContent:function(){var that=this;var e=" ";if(typeof this.content=="function"){var res=this.content.apply(this);if(typeof res=="string"){this.content=res;}else{if(typeof res=="object"&&typeof res.always=="function"){this.isAjax=true;this.isAjaxLoading=true;res.always(function(data,status,xhr){that.ajaxResponse={data:data,status:status,xhr:xhr};that._contentReady.resolve(data,status,xhr);if(typeof that.contentLoaded=="function"){that.contentLoaded(data,status,xhr);}});this.content=e;}else{this.content=e;}}}if(typeof this.content=="string"&&this.content.substr(0,4).toLowerCase()==="url:"){this.isAjax=true;this.isAjaxLoading=true;var u=this.content.substring(4,this.content.length);$.get(u).done(function(html){that.contentParsed.html(html);}).always(function(data,status,xhr){that.ajaxResponse={data:data,status:status,xhr:xhr};that._contentReady.resolve(data,status,xhr);if(typeof that.contentLoaded=="function"){that.contentLoaded(data,status,xhr);}});}if(!this.content){this.content=e;}if(!this.isAjax){this.contentParsed.html(this.content);this.setContent();that._contentReady.resolve();}},_stopCountDown:function(){clearInterval(this.autoCloseInterval);if(this.$cd){this.$cd.remove();}},_startCountDown:function(){var that=this;var opt=this.autoClose.split("|");if(opt.length!==2){console.error("Invalid option for autoClose. example 'close|10000'");return false;}var button_key=opt[0];var time=parseInt(opt[1]);if(typeof this.buttons[button_key]==="undefined"){console.error("Invalid button key '"+button_key+"' for autoClose");return false;}var seconds=Math.ceil(time/1000);this.$cd=$(' ('+seconds+")").appendTo(this["$_"+button_key]);this.autoCloseInterval=setInterval(function(){that.$cd.html(" ("+(seconds-=1)+") ");if(seconds<=0){that["$$"+button_key].trigger("click");that._stopCountDown();}},1000);},_getKey:function(key){switch(key){case 192:return"tilde";case 13:return"enter";case 16:return"shift";case 9:return"tab";case 20:return"capslock";case 17:return"ctrl";case 91:return"win";case 18:return"alt";case 27:return"esc";case 32:return"space";}var initial=String.fromCharCode(key);if(/^[A-z0-9]+$/.test(initial)){return initial.toLowerCase();}else{return false;}},reactOnKey:function(e){var that=this;var a=$(".jconfirm");if(a.eq(a.length-1)[0]!==this.$el[0]){return false;}var key=e.which;if(this.$content.find(":input").is(":focus")&&/13|32/.test(key)){return false;}var keyChar=this._getKey(key);if(keyChar==="esc"&&this.escapeKey){if(this.escapeKey===true){this.$scrollPane.trigger("click");}else{if(typeof this.escapeKey==="string"||typeof this.escapeKey==="function"){var buttonKey;if(typeof this.escapeKey==="function"){buttonKey=this.escapeKey();}else{buttonKey=this.escapeKey;}if(buttonKey){if(typeof this.buttons[buttonKey]==="undefined"){console.warn("Invalid escapeKey, no buttons found with key "+buttonKey);}else{this["$_"+buttonKey].trigger("click");}}}}}$.each(this.buttons,function(key,button){if(button.keys.indexOf(keyChar)!=-1){that["$_"+key].trigger("click");}});},setDialogCenter:function(){console.info("setDialogCenter is deprecated, dialogs are centered with CSS3 tables");},_unwatchContent:function(){clearInterval(this._timer);},close:function(onClosePayload){var that=this;if(typeof this.onClose==="function"){this.onClose(onClosePayload);}this._unwatchContent();$(window).unbind("resize."+this._id);$(window).unbind("keyup."+this._id);$(window).unbind("jcKeyDown."+this._id);if(this.draggable){$(window).unbind("mousemove."+this._id);$(window).unbind("mouseup."+this._id);this.$titleContainer.unbind("mousedown");}that.$el.removeClass(that.loadedClass);$("body").removeClass("jconfirm-no-scroll-"+that._id);that.$jconfirmBoxContainer.removeClass("jconfirm-no-transition");setTimeout(function(){that.$body.addClass(that.closeAnimationParsed);that.$jconfirmBg.addClass("jconfirm-bg-h");var closeTimer=(that.closeAnimation==="none")?1:that.animationSpeed;setTimeout(function(){that.$el.remove();var l=jconfirm.instances;var i=jconfirm.instances.length-1;for(i;i>=0;i--){if(jconfirm.instances[i]._id===that._id){jconfirm.instances.splice(i,1);}}if(!jconfirm.instances.length){if(that.scrollToPreviousElement&&jconfirm.lastFocused&&jconfirm.lastFocused.length&&$.contains(document,jconfirm.lastFocused[0])){var $lf=jconfirm.lastFocused;if(that.scrollToPreviousElementAnimate){var st=$(window).scrollTop();var ot=jconfirm.lastFocused.offset().top;var wh=$(window).height();if(!(ot>st&&ot<(st+wh))){var scrollTo=(ot-Math.round((wh/3)));$("html, body").animate({scrollTop:scrollTo},that.animationSpeed,"swing",function(){$lf.focus();});}else{$lf.focus();}}else{$lf.focus();}jconfirm.lastFocused=false;}}if(typeof that.onDestroy==="function"){that.onDestroy();}},closeTimer*0.4);},50);return true;},open:function(){if(this.isOpen()){return false;}this._buildHTML();this._bindEvents();this._open();return true;},setStartingPoint:function(){var el=false;if(this.animateFromElement!==true&&this.animateFromElement){el=this.animateFromElement;jconfirm.lastClicked=false;}else{if(jconfirm.lastClicked&&this.animateFromElement===true){el=jconfirm.lastClicked;jconfirm.lastClicked=false;}else{return false;}}if(!el){return false;}var offset=el.offset();var iTop=el.outerHeight()/2;var iLeft=el.outerWidth()/2;iTop-=this.$jconfirmBox.outerHeight()/2;iLeft-=this.$jconfirmBox.outerWidth()/2;var sourceTop=offset.top+iTop;sourceTop=sourceTop-this._scrollTop();var sourceLeft=offset.left+iLeft;var wh=$(window).height()/2;var ww=$(window).width()/2;var targetH=wh-this.$jconfirmBox.outerHeight()/2;var targetW=ww-this.$jconfirmBox.outerWidth()/2;sourceTop-=targetH;sourceLeft-=targetW;if(Math.abs(sourceTop)>wh||Math.abs(sourceLeft)>ww){return false;}this.$jconfirmBoxContainer.css("transform","translate("+sourceLeft+"px, "+sourceTop+"px)");},_open:function(){var that=this;if(typeof that.onOpenBefore==="function"){that.onOpenBefore();}this.$body.removeClass(this.animationParsed);this.$jconfirmBg.removeClass("jconfirm-bg-h");this.$body.focus();that.$jconfirmBoxContainer.css("transform","translate("+0+"px, "+0+"px)");setTimeout(function(){that.$body.css(that._getCSS(that.animationSpeed,1));that.$body.css({"transition-property":that.$body.css("transition-property")+", margin"});that.$jconfirmBoxContainer.addClass("jconfirm-no-transition");that._modalReady.resolve();if(typeof that.onOpen==="function"){that.onOpen();}that.$el.addClass(that.loadedClass);},this.animationSpeed);},loadedClass:"jconfirm-open",isClosed:function(){return !this.$el||this.$el.css("display")==="";},isOpen:function(){return !this.isClosed();},toggle:function(){if(!this.isOpen()){this.open();}else{this.close();}}};jconfirm.instances=[];jconfirm.lastFocused=false;jconfirm.pluginDefaults={template:'
',title:"Hello",titleClass:"",type:"default",typeAnimated:true,draggable:true,dragWindowGap:15,dragWindowBorder:true,animateFromElement:true,alignMiddle:true,smoothContent:true,content:"Are you sure to continue?",buttons:{},defaultButtons:{ok:{action:function(){}},close:{action:function(){}}},contentLoaded:function(){},icon:"",lazyOpen:false,bgOpacity:null,theme:"light",animation:"scale",closeAnimation:"scale",animationSpeed:400,animationBounce:1,escapeKey:true,rtl:false,container:"body",containerFluid:false,backgroundDismiss:false,backgroundDismissAnimation:"shake",autoClose:false,closeIcon:null,closeIconClass:false,watchInterval:100,columnClass:"col-md-4 col-md-offset-4 col-sm-6 col-sm-offset-3 col-xs-10 col-xs-offset-1",boxWidth:"50%",scrollToPreviousElement:true,scrollToPreviousElementAnimate:true,useBootstrap:true,offsetTop:40,offsetBottom:40,bootstrapClasses:{container:"container",containerFluid:"container-fluid",row:"row"},onContentReady:function(){},onOpenBefore:function(){},onOpen:function(){},onClose:function(){},onDestroy:function(){},onAction:function(){}};var keyDown=false;$(window).on("keydown",function(e){if(!keyDown){var $target=$(e.target);var pass=false;if($target.closest(".jconfirm-box").length){pass=true;}if(pass){$(window).trigger("jcKeyDown");}keyDown=true;}});$(window).on("keyup",function(){keyDown=false;});jconfirm.lastClicked=false;$(document).on("mousedown","button, a",function(){jconfirm.lastClicked=$(this);});})(jQuery,window); \ No newline at end of file diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 2811a50c..f6510257 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -138,6 +138,31 @@ function isServerAvailable() { } } +// shiftOrConfirm(e, prompt, fn) +// e : MouseEvent +// prompt : Text to be shown as prompt. Should be a question to which "yes" is a good answer. +// fn : function to be called if the user confirms the dialog or has the shift key pressed +// +// If the user had the shift key pressed while clicking, the function fn will be executed. +// Otherwise, a confirmation dialog is shown. If the user confirms, the function fn will also +// be executed. +function shiftOrConfirm(e, prompt, fn) { + e.stopPropagation() + if (e.shiftKey) { + fn(e) + } else { + $.confirm({ theme: 'supervan', + title: prompt, + content: 'Tip: Use shift-click to skip this dialog.', + buttons: { + yes: () => { fn(e) }, + cancel: () => {} + } + }); + } +} + + function logMsg(msg, level, outputMsg) { if (outputMsg.hasChildNodes()) { outputMsg.appendChild(document.createElement('br')) @@ -887,8 +912,7 @@ function createTask(task) { task['progressBar'] = taskEntry.querySelector('.progress-bar') task['stopTask'] = taskEntry.querySelector('.stopTask') - task['stopTask'].addEventListener('click', async function(e) { - e.stopPropagation() + task['stopTask'].addEventListener('click', (e) => { shiftOrConfirm(e, "Shall this task be stopped?", async function(e) { if (task['isProcessing']) { task.isProcessing = false task.progressBar.classList.remove("active") @@ -905,7 +929,7 @@ function createTask(task) { taskEntry.remove() } - }) + })}) task['useSettings'] = taskEntry.querySelector('.useSettings') task['useSettings'].addEventListener('click', function(e) { @@ -1047,7 +1071,7 @@ async function stopAllTasks() { } } -clearAllPreviewsBtn.addEventListener('click', async function() { +clearAllPreviewsBtn.addEventListener('click', (e) => { shiftOrConfirm(e, "Remove all results and tasks from the results pane?", async function() { await stopAllTasks() let taskEntries = document.querySelectorAll('.imageTaskContainer') @@ -1057,7 +1081,7 @@ clearAllPreviewsBtn.addEventListener('click', async function() { previewTools.style.display = 'none' initialText.style.display = 'block' -}) +})}) stopImageBtn.addEventListener('click', async function() { await stopAllTasks() From 2de96d4dc9767f6514a9576a8bdf492f11e43474 Mon Sep 17 00:00:00 2001 From: Marc-Andre Ferland Date: Tue, 22 Nov 2022 11:24:36 -0500 Subject: [PATCH 04/36] Scan model once as start, then only if changed. --- ui/server.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/ui/server.py b/ui/server.py index 61635f18..d03d3113 100644 --- a/ui/server.py +++ b/ui/server.py @@ -7,7 +7,6 @@ import traceback import sys import os -import picklescan.scanner import rich SD_DIR = os.getcwd() @@ -221,6 +220,7 @@ async def setAppConfig(req : SetAppConfigRequest): def is_malicious_model(file_path): try: + import picklescan.scanner scan_result = picklescan.scanner.scan_file_path(file_path) if scan_result.issues_count > 0 or scan_result.infected_files > 0: rich.print(":warning: [bold red]Scan %s: %d scanned, %d issue, %d infected.[/bold red]" % (file_path, scan_result.scanned_files, scan_result.issues_count, scan_result.infected_files)) @@ -230,9 +230,9 @@ def is_malicious_model(file_path): return False except Exception as e: print('error while scanning', file_path, 'error:', e) - return False +known_models = {} def getModels(): models = { 'active': { @@ -255,9 +255,14 @@ def getModels(): if not file.endswith(model_extension): continue - if is_malicious_model(os.path.join(models_dir, file)): - models['scan-error'] = file - return + model_path = os.path.join(models_dir, file) + mtime = os.path.getmtime(model_path) + mod_time = known_models[model_path] if model_path in known_models else -1 + if mod_time != mtime: + if is_malicious_model(model_path): + models['scan-error'] = file + return + known_models[model_path] = mtime model_name = file[:-len(model_extension)] models['options'][model_type].append(model_name) @@ -435,6 +440,9 @@ class LogSuppressFilter(logging.Filter): return True logging.getLogger('uvicorn.access').addFilter(LogSuppressFilter()) +# Check models and prepare cache for UI open +getModels() + # Start the task_manager task_manager.default_model_to_load = resolve_ckpt_to_use() task_manager.default_vae_to_load = resolve_vae_to_use() From f1fa10baddf34e6484ae7bc8732c137841595967 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Wed, 23 Nov 2022 11:25:36 +0100 Subject: [PATCH 05/36] Show network addresses in system settings Users sometimes struggle to get the IP address of their PC. This PR adds a button to the system settings pane that will list the server's IP addresses. --- CHANGES.md | 2 ++ ui/index.html | 8 ++++++++ ui/media/css/main.css | 6 ++++++ ui/media/js/parameters.js | 18 ++++++++++++++++++ ui/server.py | 5 +++++ 5 files changed, 39 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 2b5ae762..ae67cf50 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,8 +19,10 @@ - Configuration to prevent the browser from opening on startup - Lots of minor bug fixes - A `What's New?` tab in the UI +- Button to retrieve the network addresses of the server in the systems setting dialog ### Detailed changelog +* 2.4.14 - 23 Nov 2022 - Button to retrieve the network addresses of the server in the systems setting dialog * 2.4.13 - 21 Nov 2022 - Change the modifier weight via mouse wheel, drag to reorder selected modifiers, and some more modifier-related fixes. Thanks @patriceac * 2.4.12 - 21 Nov 2022 - Another fix for improving how long images take to generate. Reduces the time taken for an enqueued task to start processing. * 2.4.11 - 21 Nov 2022 - Installer improvements: avoid crashing if the username contains a space or special characters, allow moving/renaming the folder after installation on Windows, whitespace fix on git apply diff --git a/ui/index.html b/ui/index.html index 25a94a13..21482614 100644 --- a/ui/index.html +++ b/ui/index.html @@ -249,6 +249,14 @@

System Info

+
+

Server Address

+

You can access Stable Diffusion UI from other devices in your network. To do this, enable network access in the settings + above, and open Stable Diffusion UI in a browser using the server's IP. You can use this button to get your server's address.

+ +
+
+
diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 0cf83302..41aa3cfa 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -1002,3 +1002,9 @@ button:active { button#save-system-settings-btn { padding: 4pt 8pt; } +#ip-info a { + color:var(--text-color) +} +#ip-info div { + line-height: 200%; +} diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index 2e5bc75c..f5a0e24c 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -201,6 +201,10 @@ let uiOpenBrowserOnStartField = document.querySelector("#ui_open_browser_on_star let saveSettingsBtn = document.querySelector('#save-system-settings-btn') +let getServerIPBtn = document.querySelector('#get-server-ip') +let ipInfoContainer = document.querySelector('#ip-info') + + async function changeAppConfig(configDelta) { try { let res = await fetch('/app_config', { @@ -379,3 +383,17 @@ saveSettingsBtn.addEventListener('click', function() { saveSettingsBtn.classList.add('active') asyncDelay(300).then(() => saveSettingsBtn.classList.remove('active')) }) + +getServerIPBtn.addEventListener('click', async function() { + ipInfoContainer.innerHTML = "Retrieving server addresses..." + let list = "

List of server addresses

If there is more than one result, not all of them might be reachable from other devices.

" + let res = await fetch('/get/ip_config') + let data = await res.json() + let port = listenPortField.value + // Merge hostname (field 0) into list of IPs (field 2) + data[2].push(data[0]) + data[2].forEach((addr) => { let url=`http://${addr}:${port}/`; list+=``;}) + list += "
" + ipInfoContainer.innerHTML = list +}) + diff --git a/ui/server.py b/ui/server.py index 61635f18..db85d9f2 100644 --- a/ui/server.py +++ b/ui/server.py @@ -7,6 +7,7 @@ import traceback import sys import os +import socket import picklescan.scanner import rich @@ -286,6 +287,9 @@ def getUIPlugins(): return plugins +def getIPConfig(): + return socket.gethostbyname_ex(socket.getfqdn()) + @app.get('/get/{key:path}') def read_web_data(key:str=None): if not key: # /get without parameters, stable-diffusion easter egg. @@ -305,6 +309,7 @@ def read_web_data(key:str=None): elif key == 'modifiers': return FileResponse(os.path.join(SD_UI_DIR, 'modifiers.json'), headers=NOCACHE_HEADERS) elif key == 'output_dir': return JSONResponse({ 'output_dir': outpath }, headers=NOCACHE_HEADERS) elif key == 'ui_plugins': return JSONResponse(getUIPlugins(), headers=NOCACHE_HEADERS) + elif key == 'ip_config': return JSONResponse(getIPConfig(), headers=NOCACHE_HEADERS) else: raise HTTPException(status_code=404, detail=f'Request for unknown {key}') # HTTP404 Not Found From 6b6443406d17e8961d62339a6e1584a151c11a27 Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Wed, 23 Nov 2022 02:57:07 -0800 Subject: [PATCH 06/36] Create Autoscroll.plugin.js --- ui/plugins/ui/Autoscroll.plugin.js | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 ui/plugins/ui/Autoscroll.plugin.js diff --git a/ui/plugins/ui/Autoscroll.plugin.js b/ui/plugins/ui/Autoscroll.plugin.js new file mode 100644 index 00000000..f642b76d --- /dev/null +++ b/ui/plugins/ui/Autoscroll.plugin.js @@ -0,0 +1,43 @@ +(function () { + "use strict" + + var styleSheet = document.createElement("style"); + styleSheet.textContent = ` + .auto-scroll { + float: right; + } + `; + document.head.appendChild(styleSheet); + + const autoScrollControl = document.createElement('div'); + autoScrollControl.innerHTML = ` + ` + autoScrollControl.className = "auto-scroll" + previewTools.appendChild(autoScrollControl) + prettifyInputs(document); + let autoScroll = document.querySelector("#auto_scroll") + + SETTINGS_IDS_LIST.push("auto_scroll") + initSettings() + + // observe for changes in tag list + var observer = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + console.log(mutation.target.class) + if (mutation.target.className == 'img-batch') { + Autoscroll(mutation.target) + } + }) + }) + + observer.observe(document.getElementById('preview'), { + childList: true, + subtree: true + }) + + function Autoscroll(target) { + if (autoScroll.checked && target !== null) { + target.parentElement.parentElement.parentElement.scrollIntoView(); + } + } +})() From d0b2bf736ec79efbab698b95bd580340b29bebf0 Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Wed, 23 Nov 2022 03:23:51 -0800 Subject: [PATCH 07/36] Auto-scroll off by default --- ui/plugins/ui/Autoscroll.plugin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/plugins/ui/Autoscroll.plugin.js b/ui/plugins/ui/Autoscroll.plugin.js index f642b76d..96a21cb8 100644 --- a/ui/plugins/ui/Autoscroll.plugin.js +++ b/ui/plugins/ui/Autoscroll.plugin.js @@ -10,7 +10,7 @@ document.head.appendChild(styleSheet); const autoScrollControl = document.createElement('div'); - autoScrollControl.innerHTML = ` + autoScrollControl.innerHTML = ` ` autoScrollControl.className = "auto-scroll" previewTools.appendChild(autoScrollControl) @@ -20,7 +20,7 @@ SETTINGS_IDS_LIST.push("auto_scroll") initSettings() - // observe for changes in tag list + // observe for changes in the preview pane var observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { console.log(mutation.target.class) From 6eff591df70fd57dc493ddf3f11a0ad8c5f0cbf6 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Wed, 23 Nov 2022 23:05:30 +0100 Subject: [PATCH 08/36] System settings to disable the 'Are you sure?'-dialogs --- ui/media/js/auto-save.js | 1 + ui/media/js/main.js | 6 ++++-- ui/media/js/parameters.js | 9 +++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ui/media/js/auto-save.js b/ui/media/js/auto-save.js index 29e5298a..5ea786d5 100644 --- a/ui/media/js/auto-save.js +++ b/ui/media/js/auto-save.js @@ -35,6 +35,7 @@ const SETTINGS_IDS_LIST = [ "sound_toggle", "turbo", "use_full_precision", + "confirm_dangerous_actions", "auto_save_settings" ] diff --git a/ui/media/js/main.js b/ui/media/js/main.js index f6510257..e46c530d 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -144,16 +144,18 @@ function isServerAvailable() { // fn : function to be called if the user confirms the dialog or has the shift key pressed // // If the user had the shift key pressed while clicking, the function fn will be executed. +// If the setting "confirm_dangerous_actions" in the system settings is disabled, the function +// fn will be executed. // Otherwise, a confirmation dialog is shown. If the user confirms, the function fn will also // be executed. function shiftOrConfirm(e, prompt, fn) { e.stopPropagation() - if (e.shiftKey) { + if (e.shiftKey || !confirmDangerousActionsField.checked) { fn(e) } else { $.confirm({ theme: 'supervan', title: prompt, - content: 'Tip: Use shift-click to skip this dialog.', + content: 'Tip: To skip this dialog, use shift-click or disable the setting "Confirm dangerous actions" in the systems setting.', buttons: { yes: () => { fn(e) }, cancel: () => {} diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index 2e5bc75c..dfa33246 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -114,6 +114,14 @@ var PARAMETERS = [ icon: "fa-gear", default: true, }, + { + id: "confirm_dangerous_actions", + type: ParameterType.checkbox, + label: "Confirm dangerous actions", + note: "Actions that might lead to data loss must either be clicked with the shift key pressed, or confirmed in an 'are you sure?' dialog", + icon: "fa-check-double", + default: true, + }, { id: "listen_to_network", type: ParameterType.checkbox, @@ -198,6 +206,7 @@ let listenToNetworkField = document.querySelector("#listen_to_network") let listenPortField = document.querySelector("#listen_port") let useBetaChannelField = document.querySelector("#use_beta_channel") let uiOpenBrowserOnStartField = document.querySelector("#ui_open_browser_on_start") +let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions") let saveSettingsBtn = document.querySelector('#save-system-settings-btn') From fca84e3edfbda79ef0ca8994b77fbe4725cb5068 Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Thu, 24 Nov 2022 13:47:35 -0800 Subject: [PATCH 09/36] Fix restoration of model and VAE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 😅 --- ui/media/js/dnd.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/media/js/dnd.js b/ui/media/js/dnd.js index f344863b..6ca28f32 100644 --- a/ui/media/js/dnd.js +++ b/ui/media/js/dnd.js @@ -328,6 +328,7 @@ function getModelPath(filename, extensions) filename = filename.slice(0, filename.length - ext.length) } }) + return filename } const TASK_TEXT_MAPPING = { From 472ab4a9ce00d13f215b749674c2659c0be2f2bd Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Thu, 24 Nov 2022 14:15:27 -0800 Subject: [PATCH 10/36] Fix restoration of parallel output setting --- ui/media/js/dnd.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/media/js/dnd.js b/ui/media/js/dnd.js index f344863b..bcfd5147 100644 --- a/ui/media/js/dnd.js +++ b/ui/media/js/dnd.js @@ -192,9 +192,9 @@ const TASK_MAPPING = { parse: (val) => val }, - numOutputsParallel: { name: 'Parallel Images', - setUI: (numOutputsParallel) => { - numOutputsParallelField.value = numOutputsParallel + num_outputs: { name: 'Parallel Images', + setUI: (num_outputs) => { + numOutputsParallelField.value = num_outputs }, readUI: () => parseInt(numOutputsParallelField.value), parse: (val) => val From a2efda41d340ef91cf601ae03bf1dbe5f927e03d Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Fri, 25 Nov 2022 03:50:47 -0800 Subject: [PATCH 11/36] Cleaning up the code --- ui/plugins/ui/Autoscroll.plugin.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/plugins/ui/Autoscroll.plugin.js b/ui/plugins/ui/Autoscroll.plugin.js index 96a21cb8..82b798c8 100644 --- a/ui/plugins/ui/Autoscroll.plugin.js +++ b/ui/plugins/ui/Autoscroll.plugin.js @@ -23,7 +23,6 @@ // observe for changes in the preview pane var observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { - console.log(mutation.target.class) if (mutation.target.className == 'img-batch') { Autoscroll(mutation.target) } From e02a9175692c6123b724dc5284f6cf87582c62a6 Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Fri, 25 Nov 2022 22:51:49 -0800 Subject: [PATCH 12/36] Improved logic for auto-scroll toggle insertion Updating the insertion logic to prepare for future UI improvements. --- ui/plugins/ui/Autoscroll.plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/plugins/ui/Autoscroll.plugin.js b/ui/plugins/ui/Autoscroll.plugin.js index 82b798c8..aec9523d 100644 --- a/ui/plugins/ui/Autoscroll.plugin.js +++ b/ui/plugins/ui/Autoscroll.plugin.js @@ -13,7 +13,7 @@ autoScrollControl.innerHTML = ` ` autoScrollControl.className = "auto-scroll" - previewTools.appendChild(autoScrollControl) + clearAllPreviewsBtn.parentNode.insertBefore(autoScrollControl, clearAllPreviewsBtn.nextSibling) prettifyInputs(document); let autoScroll = document.querySelector("#auto_scroll") From 9c91f57b19d7e020c77fb8ef8455137b88f3fc3c Mon Sep 17 00:00:00 2001 From: Marc-Andre Ferland Date: Sat, 26 Nov 2022 15:50:27 -0500 Subject: [PATCH 13/36] Added web manifest to allow installing the Url as a web app. --- ui/index.html | 1 + ui/media/manifest.webmanifest | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 ui/media/manifest.webmanifest diff --git a/ui/index.html b/ui/index.html index edbccf97..fb997a82 100644 --- a/ui/index.html +++ b/ui/index.html @@ -12,6 +12,7 @@ + diff --git a/ui/media/manifest.webmanifest b/ui/media/manifest.webmanifest new file mode 100644 index 00000000..bdb665e9 --- /dev/null +++ b/ui/media/manifest.webmanifest @@ -0,0 +1,8 @@ +{ + "name": "Stable Diffusion UI", + "display": "standalone", + "display_override": [ + "window-controls-overlay" + ], + "theme_color": "#000000" +} From e64e1a92e69b96b815ce34f2be72868824e7b04e Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Sun, 27 Nov 2022 12:25:46 -0800 Subject: [PATCH 14/36] Fix UI display when removing the last task Clear All button properly shows the "welcome message", but Remove the last task would just result in a blank Preview pane. --- ui/media/js/main.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 2811a50c..5c115554 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -903,7 +903,7 @@ function createTask(task) { taskQueue.splice(idx, 1) } - taskEntry.remove() + removeTask(taskEntry) } }) @@ -1047,16 +1047,22 @@ async function stopAllTasks() { } } +function removeTask(taskToRemove) { + taskToRemove.remove() + + if (document.querySelectorAll('.imageTaskContainer').length === 0) { + previewTools.style.display = 'none' + initialText.style.display = 'block' + } +} + clearAllPreviewsBtn.addEventListener('click', async function() { await stopAllTasks() let taskEntries = document.querySelectorAll('.imageTaskContainer') taskEntries.forEach(task => { - task.remove() + removeTask(task) }) - - previewTools.style.display = 'none' - initialText.style.display = 'block' }) stopImageBtn.addEventListener('click', async function() { From 99bdcfa0a592732936c34707511b3420638e508a Mon Sep 17 00:00:00 2001 From: Marc-Andre Ferland Date: Sun, 27 Nov 2022 15:49:23 -0500 Subject: [PATCH 15/36] Set theme-color from the current selected theme. --- ui/index.html | 1 + ui/media/css/themes.css | 21 ++++++++++++++++++--- ui/media/js/themes.js | 8 ++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/ui/index.html b/ui/index.html index fb997a82..482a40fc 100644 --- a/ui/index.html +++ b/ui/index.html @@ -3,6 +3,7 @@ Stable Diffusion UI + diff --git a/ui/media/css/themes.css b/ui/media/css/themes.css index d98ff0ec..6a2db310 100644 --- a/ui/media/css/themes.css +++ b/ui/media/css/themes.css @@ -30,6 +30,9 @@ --primary-button-border: none; --input-switch-padding: 1px; --input-height: 18px; + + /* Main theme color, hex color fallback. */ + --theme-color-fallback: #673AB6; } .theme-light { @@ -44,6 +47,8 @@ --input-text-color: black; --input-background-color: #f8f9fa; --input-border-color: grey; + + --theme-color-fallback: #aaaaaa; } .theme-discord { @@ -58,6 +63,8 @@ --input-border-size: 2px; --input-background-color: #202225; --input-border-color: var(--input-background-color); + + --theme-color-fallback: #202225; } .theme-cool-blue { @@ -71,8 +78,10 @@ --background-color4: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (3 * var(--value-step)))); --input-background-color: var(--background-color3); - + --accent-hue: 212; + + --theme-color-fallback: #0056b8; } @@ -87,6 +96,8 @@ --background-color4: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (3 * var(--value-step)))); --input-background-color: var(--background-color3); + + --theme-color-fallback: #5300b8; } .theme-super-dark { @@ -101,6 +112,8 @@ --input-background-color: var(--background-color3); --input-border-size: 0px; + + --theme-color-fallback: #000000; } .theme-wild { @@ -117,8 +130,8 @@ --input-border-size: 1px; --input-background-color: hsl(222, var(--main-saturation), calc(var(--value-base) - (2 * var(--value-step)))); - --input-text-color: red; - --input-border-color: green; + --input-text-color: #FF0000; + --input-border-color: #005E05; } .theme-gnomie { @@ -136,6 +149,8 @@ --input-background-color: #2a2a2a; --input-border-size: 0px; --input-border-color: var(--input-background-color); + + --theme-color-fallback: #2168bf; } .theme-gnomie .panel-box { diff --git a/ui/media/js/themes.js b/ui/media/js/themes.js index 8ffdb172..2915bc32 100644 --- a/ui/media/js/themes.js +++ b/ui/media/js/themes.js @@ -60,6 +60,7 @@ function themeFieldChanged() { body.style = ""; var theme = THEMES.find(t => t.key == theme_key); + let borderColor = undefined if (theme) { // refresh variables incase they are back referencing Array.from(DEFAULT_THEME.rule.style) @@ -67,7 +68,14 @@ function themeFieldChanged() { .forEach(cssVariable => { body.style.setProperty(cssVariable, DEFAULT_THEME.rule.style.getPropertyValue(cssVariable)); }); + borderColor = theme.rule.style.getPropertyValue('--input-border-color').trim() + if (!borderColor.startsWith('#')) { + borderColor = theme.rule.style.getPropertyValue('--theme-color-fallback') + } + } else { + borderColor = DEFAULT_THEME.rule.style.getPropertyValue('--theme-color-fallback') } + document.querySelector('meta[name="theme-color"]').setAttribute("content", borderColor) } themeField.addEventListener('change', themeFieldChanged); From d48951fe009fc1328e1c9c825c2f0001a9bd58eb Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Sun, 27 Nov 2022 16:26:01 -0800 Subject: [PATCH 16/36] Visual feedback on button click When there are too many tasks and the top of the list is not visible, there is no visual feedback that a task has been successfully added to the queue. Adding a subtle visual feedback on buttons upon click to reflect that the mouse event was taken into account. --- ui/media/css/main.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 0cf83302..6fb4f651 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -997,6 +997,9 @@ button:hover { button:active { transition-duration: 0.1s; background-color: hsl(var(--accent-hue), 100%, calc(var(--accent-lightness) + 24%)); + position: relative; + top: 1px; + left: 1px; } button#save-system-settings-btn { From 8583bb8d7baa872bc772f4f859d95d46dd290909 Mon Sep 17 00:00:00 2001 From: Marc-Andre Ferland Date: Sun, 27 Nov 2022 20:36:19 -0500 Subject: [PATCH 17/36] Improved tabs flow on small screens. --- ui/media/css/main.css | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 0cf83302..c958f5e4 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -658,11 +658,15 @@ input::file-selector-button { opacity: 1; } -/* MOBILE SUPPORT */ -@media screen and (max-width: 700px) { +/* Small screens */ +@media screen and (max-width: 1265px) { #top-nav { flex-direction: column; } +} + +/* MOBILE SUPPORT */ +@media screen and (max-width: 700px) { body { margin: 0px; } @@ -712,7 +716,7 @@ input::file-selector-button { padding-right: 0px; } #server-status { - display: none; + top: 75%; } .popup > div { padding-left: 5px !important; From 13654cb8c0c3da854f47c3252c7ce94bf0cf681c Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 28 Nov 2022 12:59:33 +0530 Subject: [PATCH 18/36] Make on_sd_start.sh executable --- scripts/on_sd_start.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/on_sd_start.sh diff --git a/scripts/on_sd_start.sh b/scripts/on_sd_start.sh old mode 100644 new mode 100755 From 14714b950de538f6f34376a2782eb147e36f3f12 Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Mon, 28 Nov 2022 00:14:12 -0800 Subject: [PATCH 19/36] Slight improvement of detection logic --- ui/media/js/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 5c115554..cfa924cc 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -1050,7 +1050,7 @@ async function stopAllTasks() { function removeTask(taskToRemove) { taskToRemove.remove() - if (document.querySelectorAll('.imageTaskContainer').length === 0) { + if (document.querySelector('.imageTaskContainer') === null) { previewTools.style.display = 'none' initialText.style.display = 'block' } From a99209b6742d45f24c2893b696894a52b95ce0dc Mon Sep 17 00:00:00 2001 From: Marc-Andre Ferland Date: Mon, 28 Nov 2022 20:22:28 -0500 Subject: [PATCH 20/36] Add a new css rule for screens smaller than 500px. --- ui/media/css/main.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ui/media/css/main.css b/ui/media/css/main.css index c958f5e4..74602b79 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -734,6 +734,15 @@ input::file-selector-button { } } +@media screen and (max-width: 500px) { + #server-status #server-status-msg { + display: none; + } + #server-status:hover #server-status-msg { + display: inline; + } +} + @media (min-width: 700px) { /* #editor { max-width: 480px; From e37be0f954015ec85402b53204cc970626c45ac4 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 29 Nov 2022 13:03:57 +0530 Subject: [PATCH 21/36] Remove the need to use yield in the core loop for streaming results. This removes the need to patch the Stable Diffusion code, which can be fragile --- scripts/on_sd_start.bat | 8 +-- scripts/on_sd_start.sh | 8 +-- ui/sd_internal/runtime.py | 114 ++++++++++++++++++--------------- ui/sd_internal/task_manager.py | 43 ++++--------- 4 files changed, 77 insertions(+), 96 deletions(-) diff --git a/scripts/on_sd_start.bat b/scripts/on_sd_start.bat index 87b6ada0..e088ed57 100644 --- a/scripts/on_sd_start.bat +++ b/scripts/on_sd_start.bat @@ -42,13 +42,9 @@ if NOT DEFINED test_sd2 set test_sd2=N if "%test_sd2%" == "N" ( @call git -c advice.detachedHead=false checkout 7f32368ed1030a6e710537047bacd908adea183a - - @call git apply --whitespace=warn ..\ui\sd_internal\ddim_callback.patch ) if "%test_sd2%" == "Y" ( - @call git -c advice.detachedHead=false checkout 6e2f82187f8ecc4ea59ac37dc239cfcc78038f6d - - @call git apply ..\ui\sd_internal\ddim_callback_sd2.patch + @call git -c advice.detachedHead=false checkout 8878d67decd3deb3c98472c1e39d2a51dc5950f9 ) @cd .. @@ -66,8 +62,6 @@ if NOT DEFINED test_sd2 set test_sd2=N @cd stable-diffusion @call git -c advice.detachedHead=false checkout 7f32368ed1030a6e710537047bacd908adea183a - @call git apply --whitespace=warn ..\ui\sd_internal\ddim_callback.patch - @cd .. ) diff --git a/scripts/on_sd_start.sh b/scripts/on_sd_start.sh index 460d58a7..199a9ae8 100755 --- a/scripts/on_sd_start.sh +++ b/scripts/on_sd_start.sh @@ -37,12 +37,8 @@ if [ -e "scripts/install_status.txt" ] && [ `grep -c sd_git_cloned scripts/insta if [ "$test_sd2" == "N" ]; then git -c advice.detachedHead=false checkout 7f32368ed1030a6e710537047bacd908adea183a - - git apply --whitespace=warn ../ui/sd_internal/ddim_callback.patch || fail "ddim patch failed" elif [ "$test_sd2" == "Y" ]; then - git -c advice.detachedHead=false checkout 992f111312afa9ec1a01beaa9733cb9728f5acd3 - - git apply --whitespace=warn ../ui/sd_internal/ddim_callback_sd2.patch || fail "sd2 ddim patch failed" + git -c advice.detachedHead=false checkout 8878d67decd3deb3c98472c1e39d2a51dc5950f9 fi cd .. @@ -58,8 +54,6 @@ else cd stable-diffusion git -c advice.detachedHead=false checkout 7f32368ed1030a6e710537047bacd908adea183a - git apply --whitespace=warn ../ui/sd_internal/ddim_callback.patch || fail "ddim patch failed" - cd .. fi diff --git a/ui/sd_internal/runtime.py b/ui/sd_internal/runtime.py index 2a989e46..69031fba 100644 --- a/ui/sd_internal/runtime.py +++ b/ui/sd_internal/runtime.py @@ -7,6 +7,7 @@ Notes: import json import os, re import traceback +import queue import torch import numpy as np from gc import collect as gc_collect @@ -392,9 +393,34 @@ def apply_filters(filter_name, image_data, model_path=None): return image_data -def mk_img(req: Request): +def is_model_reload_necessary(req: Request): + # custom model support: + # the req.use_stable_diffusion_model needs to be a valid path + # to the ckpt file (without the extension). + if not os.path.exists(req.use_stable_diffusion_model + '.ckpt'): raise FileNotFoundError(f'Cannot find {req.use_stable_diffusion_model}.ckpt') + + needs_model_reload = False + if not thread_data.model or thread_data.ckpt_file != req.use_stable_diffusion_model or thread_data.vae_file != req.use_vae_model: + thread_data.ckpt_file = req.use_stable_diffusion_model + thread_data.vae_file = req.use_vae_model + needs_model_reload = True + + if thread_data.device != 'cpu': + if (thread_data.precision == 'autocast' and (req.use_full_precision or not thread_data.model_is_half)) or \ + (thread_data.precision == 'full' and not req.use_full_precision and not thread_data.force_full_precision): + thread_data.precision = 'full' if req.use_full_precision else 'autocast' + needs_model_reload = True + + return needs_model_reload + +def reload_model(): + unload_models() + unload_filters() + load_model_ckpt() + +def mk_img(req: Request, data_queue: queue.Queue, task_temp_images: list, step_callback): try: - yield from do_mk_img(req) + return do_mk_img(req, data_queue, task_temp_images, step_callback) except Exception as e: print(traceback.format_exc()) @@ -405,12 +431,13 @@ def mk_img(req: Request): thread_data.model.model2.to("cpu") gc() # Release from memory. - yield json.dumps({ + data_queue.put(json.dumps({ "status": 'failed', "detail": str(e) - }) + })) + raise e -def update_temp_img(req, x_samples): +def update_temp_img(req, x_samples, task_temp_images: list): partial_images = [] for i in range(req.num_outputs): if thread_data.test_sd2: @@ -421,19 +448,18 @@ def update_temp_img(req, x_samples): x_sample = 255.0 * rearrange(x_sample[0].cpu().numpy(), "c h w -> h w c") x_sample = x_sample.astype(np.uint8) img = Image.fromarray(x_sample) - buf = BytesIO() - img.save(buf, format='JPEG') - buf.seek(0) + buf = img_to_buffer(img, output_format='JPEG') del img, x_sample, x_sample_ddim # don't delete x_samples, it is used in the code that called this callback thread_data.temp_images[str(req.session_id) + '/' + str(i)] = buf + task_temp_images[i] = buf partial_images.append({'path': f'/image/tmp/{req.session_id}/{i}'}) return partial_images # Build and return the apropriate generator for do_mk_img -def get_image_progress_generator(req, extra_props=None): +def get_image_progress_generator(req, data_queue: queue.Queue, task_temp_images: list, step_callback, extra_props=None): if not req.stream_progress_updates: def empty_callback(x_samples, i): return x_samples return empty_callback @@ -452,15 +478,17 @@ def get_image_progress_generator(req, extra_props=None): progress.update(extra_props) if req.stream_image_progress and i % 5 == 0: - progress['output'] = update_temp_img(req, x_samples) + progress['output'] = update_temp_img(req, x_samples, task_temp_images) - yield json.dumps(progress) + data_queue.put(json.dumps(progress)) + + step_callback() if thread_data.stop_processing: raise UserInitiatedStop("User requested that we stop processing") return img_callback -def do_mk_img(req: Request): +def do_mk_img(req: Request, data_queue: queue.Queue, task_temp_images: list, step_callback): thread_data.stop_processing = False res = Response() @@ -469,28 +497,6 @@ def do_mk_img(req: Request): thread_data.temp_images.clear() - # custom model support: - # the req.use_stable_diffusion_model needs to be a valid path - # to the ckpt file (without the extension). - if not os.path.exists(req.use_stable_diffusion_model + '.ckpt'): raise FileNotFoundError(f'Cannot find {req.use_stable_diffusion_model}.ckpt') - - needs_model_reload = False - if not thread_data.model or thread_data.ckpt_file != req.use_stable_diffusion_model or thread_data.vae_file != req.use_vae_model: - thread_data.ckpt_file = req.use_stable_diffusion_model - thread_data.vae_file = req.use_vae_model - needs_model_reload = True - - if thread_data.device != 'cpu': - if (thread_data.precision == 'autocast' and (req.use_full_precision or not thread_data.model_is_half)) or \ - (thread_data.precision == 'full' and not req.use_full_precision and not thread_data.force_full_precision): - thread_data.precision = 'full' if req.use_full_precision else 'autocast' - needs_model_reload = True - - if needs_model_reload: - unload_models() - unload_filters() - load_model_ckpt() - if thread_data.turbo != req.turbo and not thread_data.test_sd2: thread_data.turbo = req.turbo thread_data.model.turbo = req.turbo @@ -606,7 +612,7 @@ def do_mk_img(req: Request): thread_data.modelFS.to(thread_data.device) n_steps = req.num_inference_steps if req.init_image is None else t_enc - img_callback = get_image_progress_generator(req, {"total_steps": n_steps}) + img_callback = get_image_progress_generator(req, data_queue, task_temp_images, step_callback, {"total_steps": n_steps}) # run the handler try: @@ -615,13 +621,6 @@ def do_mk_img(req: Request): x_samples = _txt2img(req.width, req.height, req.num_outputs, req.num_inference_steps, req.guidance_scale, None, opt_C, opt_f, opt_ddim_eta, c, uc, opt_seed, img_callback, mask, req.sampler) else: x_samples = _img2img(init_latent, t_enc, batch_size, req.guidance_scale, c, uc, req.num_inference_steps, opt_ddim_eta, opt_seed, img_callback, mask, opt_C, req.height, req.width, opt_f) - - if req.stream_progress_updates: - yield from x_samples - if hasattr(thread_data, 'partial_x_samples'): - if thread_data.partial_x_samples is not None: - x_samples = thread_data.partial_x_samples - del thread_data.partial_x_samples except UserInitiatedStop: if not hasattr(thread_data, 'partial_x_samples'): continue @@ -666,9 +665,11 @@ def do_mk_img(req: Request): save_metadata(meta_out_path, req, prompts[0], opt_seed) if return_orig_img: - img_str = img_to_base64_str(img, req.output_format) + img_buffer = img_to_buffer(img, req.output_format) + img_str = buffer_to_base64_str(img_buffer, req.output_format) res_image_orig = ResponseImage(data=img_str, seed=opt_seed) res.images.append(res_image_orig) + task_temp_images[i] = img_buffer if req.save_to_disk_path is not None: res_image_orig.path_abs = img_out_path @@ -684,9 +685,11 @@ def do_mk_img(req: Request): filters_applied.append(req.use_upscale) if (len(filters_applied) > 0): filtered_image = Image.fromarray(img_data[i]) - filtered_img_data = img_to_base64_str(filtered_image, req.output_format) + filtered_buffer = img_to_buffer(filtered_image, req.output_format) + filtered_img_data = buffer_to_base64_str(filtered_buffer, req.output_format) response_image = ResponseImage(data=filtered_img_data, seed=opt_seed) res.images.append(response_image) + task_temp_images[i] = filtered_buffer if req.save_to_disk_path is not None: filtered_img_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], img_id, req.output_format, "_".join(filters_applied)) save_image(filtered_image, filtered_img_out_path) @@ -705,7 +708,10 @@ def do_mk_img(req: Request): print(f'memory_final = {round(torch.cuda.memory_allocated(thread_data.device) / 1e6, 2)}Mb') print('Task completed') - yield json.dumps(res.json()) + res = res.json() + data_queue.put(json.dumps(res)) + + return res def save_image(img, img_out_path): try: @@ -771,7 +777,7 @@ def _txt2img(opt_W, opt_H, opt_n_samples, opt_ddim_steps, opt_scale, start_code, sampler.make_schedule(ddim_num_steps=opt_ddim_steps, ddim_eta=opt_ddim_eta, verbose=False) - samples_ddim = sampler.sample( + samples_ddim, intermediates = sampler.sample( S=opt_ddim_steps, conditioning=c, batch_size=opt_n_samples, @@ -790,7 +796,7 @@ def _txt2img(opt_W, opt_H, opt_n_samples, opt_ddim_steps, opt_scale, start_code, if sampler_name == 'ddim': thread_data.model.make_schedule(ddim_num_steps=opt_ddim_steps, ddim_eta=opt_ddim_eta, verbose=False) - samples_ddim = thread_data.model.sample( + samples_ddim, intermediates = thread_data.model.sample( S=opt_ddim_steps, conditioning=c, seed=opt_seed, @@ -804,7 +810,7 @@ def _txt2img(opt_W, opt_H, opt_n_samples, opt_ddim_steps, opt_scale, start_code, mask=mask, sampler = sampler_name, ) - yield from samples_ddim + return samples_ddim def _img2img(init_latent, t_enc, batch_size, opt_scale, c, uc, opt_ddim_steps, opt_ddim_eta, opt_seed, img_callback, mask, opt_C=1, opt_H=1, opt_W=1, opt_f=1): # encode (scaled latent) @@ -831,7 +837,7 @@ def _img2img(init_latent, t_enc, batch_size, opt_scale, c, uc, opt_ddim_steps, o ) # decode it - samples_ddim = thread_data.model.sample( + samples_ddim, intermediates = thread_data.model.sample( t_enc, c, z_enc, @@ -842,7 +848,7 @@ def _img2img(init_latent, t_enc, batch_size, opt_scale, c, uc, opt_ddim_steps, o x_T=x_T, sampler = 'ddim' ) - yield from samples_ddim + return samples_ddim def gc(): gc_collect() @@ -910,8 +916,16 @@ def load_mask(mask_str, h0, w0, newH, newW, invert=False): # https://stackoverflow.com/a/61114178 def img_to_base64_str(img, output_format="PNG"): + buffered = img_to_buffer(img, output_format) + return buffer_to_base64_str(buffered, output_format) + +def img_to_buffer(img, output_format="PNG"): buffered = BytesIO() img.save(buffered, format=output_format) + buffered.seek(0) + return buffered + +def buffer_to_base64_str(buffered, output_format="PNG"): buffered.seek(0) img_byte = buffered.getvalue() mime_type = "image/png" if output_format.lower() == "png" else "image/jpeg" diff --git a/ui/sd_internal/task_manager.py b/ui/sd_internal/task_manager.py index bd87517b..cfee79f3 100644 --- a/ui/sd_internal/task_manager.py +++ b/ui/sd_internal/task_manager.py @@ -283,45 +283,24 @@ def thread_render(device): print(f'Session {task.request.session_id} starting task {id(task)} on {runtime.thread_data.device_name}') if not task.lock.acquire(blocking=False): raise Exception('Got locked task from queue.') try: - if runtime.thread_data.device == 'cpu' and is_alive() > 1: - # CPU is not the only device. Keep track of active time to unload resources later. - runtime.thread_data.lastActive = time.time() - # Open data generator. - res = runtime.mk_img(task.request) - if current_model_path == task.request.use_stable_diffusion_model: - current_state = ServerStates.Rendering - else: + if runtime.is_model_reload_necessary(task.request): current_state = ServerStates.LoadingModel - # Start reading from generator. - dataQueue = None - if task.request.stream_progress_updates: - dataQueue = task.buffer_queue - for result in res: - if current_state == ServerStates.LoadingModel: - current_state = ServerStates.Rendering - current_model_path = task.request.use_stable_diffusion_model - current_vae_path = task.request.use_vae_model + runtime.reload_model() + current_model_path = task.request.use_stable_diffusion_model + current_vae_path = task.request.use_vae_model + + def step_callback(): if isinstance(current_state_error, SystemExit) or isinstance(current_state_error, StopAsyncIteration) or isinstance(task.error, StopAsyncIteration): runtime.thread_data.stop_processing = True if isinstance(current_state_error, StopAsyncIteration): task.error = current_state_error current_state_error = None print(f'Session {task.request.session_id} sent cancel signal for task {id(task)}') - if dataQueue: - dataQueue.put(result) - if isinstance(result, str): - result = json.loads(result) - task.response = result - if 'output' in result: - for out_obj in result['output']: - if 'path' in out_obj: - img_id = out_obj['path'][out_obj['path'].rindex('/') + 1:] - task.temp_images[int(img_id)] = runtime.thread_data.temp_images[out_obj['path'][11:]] - elif 'data' in out_obj: - buf = runtime.base64_str_to_buffer(out_obj['data']) - task.temp_images[result['output'].index(out_obj)] = buf - # Before looping back to the generator, mark cache as still alive. - task_cache.keep(task.request.session_id, TASK_TTL) + + task_cache.keep(task.request.session_id, TASK_TTL) + + current_state = ServerStates.Rendering + task.response = runtime.mk_img(task.request, task.buffer_queue, task.temp_images, step_callback) except Exception as e: task.error = e print(traceback.format_exc()) From 54322976911646c093ac2a862a69aa9371c3f62d Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 29 Nov 2022 13:14:58 +0530 Subject: [PATCH 22/36] Default to sd-v1-4 when trying to use a SD2 model with SD 1.4, and warn the user. This will eventually be unnecessary --- ui/server.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ui/server.py b/ui/server.py index fffbb43c..bc494b3a 100644 --- a/ui/server.py +++ b/ui/server.py @@ -144,12 +144,19 @@ def setConfig(config): print(traceback.format_exc()) def resolve_model_to_use(model_name:str, model_type:str, model_dir:str, model_extensions:list, default_models=[]): + config = getConfig() + model_dirs = [os.path.join(MODELS_DIR, model_dir), SD_DIR] if not model_name: # When None try user configured model. - config = getConfig() + # config = getConfig() if 'model' in config and model_type in config['model']: model_name = config['model'][model_type] if model_name: + is_sd2 = config.get('test_sd2', False) + if model_name.startswith('sd2_') and not is_sd2: # temp hack, until SD2 is unified with 1.4 + print('ERROR: Cannot use SD 2.0 models with SD 1.0 code. Using the sd-v1-4 model instead!') + model_name = 'sd-v1-4' + # Check models directory models_dir_path = os.path.join(MODELS_DIR, model_dir, model_name) for model_extension in model_extensions: From ac605e93526e3dee56bd46492a2f5a30bd8f4bbf Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 29 Nov 2022 13:30:08 +0530 Subject: [PATCH 23/36] Typos and minor fixes for sd 2 --- ui/sd_internal/runtime.py | 4 ++-- ui/sd_internal/task_manager.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/sd_internal/runtime.py b/ui/sd_internal/runtime.py index 69031fba..f2f8d169 100644 --- a/ui/sd_internal/runtime.py +++ b/ui/sd_internal/runtime.py @@ -796,7 +796,7 @@ def _txt2img(opt_W, opt_H, opt_n_samples, opt_ddim_steps, opt_scale, start_code, if sampler_name == 'ddim': thread_data.model.make_schedule(ddim_num_steps=opt_ddim_steps, ddim_eta=opt_ddim_eta, verbose=False) - samples_ddim, intermediates = thread_data.model.sample( + samples_ddim = thread_data.model.sample( S=opt_ddim_steps, conditioning=c, seed=opt_seed, @@ -837,7 +837,7 @@ def _img2img(init_latent, t_enc, batch_size, opt_scale, c, uc, opt_ddim_steps, o ) # decode it - samples_ddim, intermediates = thread_data.model.sample( + samples_ddim = thread_data.model.sample( t_enc, c, z_enc, diff --git a/ui/sd_internal/task_manager.py b/ui/sd_internal/task_manager.py index cfee79f3..ff6cbb4c 100644 --- a/ui/sd_internal/task_manager.py +++ b/ui/sd_internal/task_manager.py @@ -290,6 +290,8 @@ def thread_render(device): current_vae_path = task.request.use_vae_model def step_callback(): + global current_state_error + if isinstance(current_state_error, SystemExit) or isinstance(current_state_error, StopAsyncIteration) or isinstance(task.error, StopAsyncIteration): runtime.thread_data.stop_processing = True if isinstance(current_state_error, StopAsyncIteration): From 3d0cdc1cb68e078bb674b2c850706fd170f46aa9 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 29 Nov 2022 13:32:29 +0530 Subject: [PATCH 24/36] Bump version --- CHANGES.md | 1 + ui/index.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index baafcb18..06997faf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,6 +21,7 @@ - A `What's New?` tab in the UI ### Detailed changelog +* 2.4.16 - 29 Nov 2022 - Bug fixes for SD 2.0 - remove the need for patching, default to SD 1.4 model if trying to load an SD2 model in SD1.4. * 2.4.15 - 25 Nov 2022 - Experimental support for SD 2.0. Uses lots of memory, not optimized, probably GPU-only. * 2.4.14 - 22 Nov 2022 - Change the backend to a custom fork of Stable Diffusion * 2.4.13 - 21 Nov 2022 - Change the modifier weight via mouse wheel, drag to reorder selected modifiers, and some more modifier-related fixes. Thanks @patriceac diff --git a/ui/index.html b/ui/index.html index edbccf97..75168267 100644 --- a/ui/index.html +++ b/ui/index.html @@ -22,7 +22,7 @@
From 2706149399eb26fe6d37fef25a800cb20eb1fba7 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 29 Nov 2022 15:27:13 +0530 Subject: [PATCH 25/36] Tweak left padding of editor panel --- ui/media/css/main.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/media/css/main.css b/ui/media/css/main.css index c958f5e4..2bb4b82a 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -210,7 +210,7 @@ code { } .collapsible-content { display: block; - padding-left: 15px; + padding-left: 10px; } .collapsible-content h5 { padding: 5pt 0pt; From 0ea38db7ef056de3224fe88aaf23f18d6ed794e0 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 30 Nov 2022 12:05:46 +0530 Subject: [PATCH 26/36] Show the SD 2.0 setting only to beta users --- ui/media/js/parameters.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index dac008ef..ddc4975c 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -136,7 +136,7 @@ var PARAMETERS = [ id: "test_sd2", type: ParameterType.checkbox, label: "Test SD 2.0", - note: "Experimental! High memory usage! GPU-only! Please restart the program after changing this.", + note: "Experimental! High memory usage! GPU-only! Not the final version! Please restart the program after changing this.", icon: "fa-fire", default: false, }, @@ -242,6 +242,9 @@ async function getAppConfig() { if ('test_sd2' in config) { testSD2Field.checked = config['test_sd2'] } + + let testSD2SettingEntry = getParameterSettingsEntry('test_sd2') + testSD2SettingEntry.style.display = (config.update_branch === 'beta' ? '' : 'none') if (config.net && config.net.listen_to_network === false) { listenToNetworkField.checked = false } From ff9430b8a2521596e9762a747efaab8aa2c52bcd Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 30 Nov 2022 12:18:34 +0530 Subject: [PATCH 27/36] Tabs to 4 spaces --- ui/media/js/main.js | 6 +- ui/media/js/parameters.js | 371 +++++++++++++++++++------------------- ui/media/js/utils.js | 20 +- 3 files changed, 198 insertions(+), 199 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 2811a50c..681ca9e6 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -934,10 +934,10 @@ function getPrompts() { prompts = prompts.filter(prompt => prompt !== '') if (activeTags.length > 0) { - const promptTags = activeTags.map(x => x.name).join(", ") - prompts = prompts.map((prompt) => `${prompt}, ${promptTags}`) + const promptTags = activeTags.map(x => x.name).join(", ") + prompts = prompts.map((prompt) => `${prompt}, ${promptTags}`) } - + let promptsToMake = applySetOperator(prompts) promptsToMake = applyPermuteOperator(promptsToMake) diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index ddc4975c..1cc51c4b 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -5,9 +5,9 @@ */ var ParameterType = { checkbox: "checkbox", - select: "select", - select_multiple: "select_multiple", - custom: "custom", + select: "select", + select_multiple: "select_multiple", + custom: "custom", }; /** @@ -23,174 +23,174 @@ /** @type {Array.} */ var PARAMETERS = [ - { - id: "theme", - type: ParameterType.select, - label: "Theme", - default: "theme-default", - note: "customize the look and feel of the ui", - options: [ // Note: options expanded dynamically - { - value: "theme-default", - label: "Default" - } - ], - icon: "fa-palette" - }, - { - id: "save_to_disk", - type: ParameterType.checkbox, - label: "Auto-Save Images", - note: "automatically saves images to the specified location", - icon: "fa-download", - default: false, - }, - { - id: "diskPath", - type: ParameterType.custom, - label: "Save Location", - render: (parameter) => { - return `` - } - }, - { - id: "sound_toggle", - type: ParameterType.checkbox, - label: "Enable Sound", - note: "plays a sound on task completion", - icon: "fa-volume-low", - default: true, - }, - { - id: "ui_open_browser_on_start", - type: ParameterType.checkbox, - label: "Open browser on startup", - note: "starts the default browser on startup", - icon: "fa-window-restore", - default: true, - }, - { - id: "turbo", - type: ParameterType.checkbox, - label: "Turbo Mode", - note: "generates images faster, but uses an additional 1 GB of GPU memory", - icon: "fa-forward", - default: true, - }, - { - id: "use_cpu", - type: ParameterType.checkbox, - label: "Use CPU (not GPU)", - note: "warning: this will be *very* slow", - icon: "fa-microchip", - default: false, - }, - { - id: "auto_pick_gpus", - type: ParameterType.checkbox, - label: "Automatically pick the GPUs (experimental)", - default: false, - }, - { - id: "use_gpus", - type: ParameterType.select_multiple, - label: "GPUs to use (experimental)", - note: "to process in parallel", - default: false, - }, - { - id: "use_full_precision", - type: ParameterType.checkbox, - label: "Use Full Precision", - note: "for GPU-only. warning: this will consume more VRAM", - icon: "fa-crosshairs", - default: false, - }, - { - id: "auto_save_settings", - type: ParameterType.checkbox, - label: "Auto-Save Settings", - note: "restores settings on browser load", - icon: "fa-gear", - default: true, - }, - { - id: "listen_to_network", - type: ParameterType.checkbox, - label: "Make Stable Diffusion available on your network", - note: "Other devices on your network can access this web page", - icon: "fa-network-wired", - default: true, - }, - { - id: "listen_port", - type: ParameterType.custom, - label: "Network port", - note: "Port that this server listens to. The '9000' part in 'http://localhost:9000'", - icon: "fa-anchor", - render: (parameter) => { - return `` - } - }, - { - id: "test_sd2", - type: ParameterType.checkbox, - label: "Test SD 2.0", - note: "Experimental! High memory usage! GPU-only! Not the final version! Please restart the program after changing this.", - icon: "fa-fire", - default: false, - }, - { - id: "use_beta_channel", - type: ParameterType.checkbox, - label: "Beta channel", - note: "Get the latest features immediately (but could be less stable). Please restart the program after changing this.", - icon: "fa-fire", - default: false, - }, + { + id: "theme", + type: ParameterType.select, + label: "Theme", + default: "theme-default", + note: "customize the look and feel of the ui", + options: [ // Note: options expanded dynamically + { + value: "theme-default", + label: "Default" + } + ], + icon: "fa-palette" + }, + { + id: "save_to_disk", + type: ParameterType.checkbox, + label: "Auto-Save Images", + note: "automatically saves images to the specified location", + icon: "fa-download", + default: false, + }, + { + id: "diskPath", + type: ParameterType.custom, + label: "Save Location", + render: (parameter) => { + return `` + } + }, + { + id: "sound_toggle", + type: ParameterType.checkbox, + label: "Enable Sound", + note: "plays a sound on task completion", + icon: "fa-volume-low", + default: true, + }, + { + id: "ui_open_browser_on_start", + type: ParameterType.checkbox, + label: "Open browser on startup", + note: "starts the default browser on startup", + icon: "fa-window-restore", + default: true, + }, + { + id: "turbo", + type: ParameterType.checkbox, + label: "Turbo Mode", + note: "generates images faster, but uses an additional 1 GB of GPU memory", + icon: "fa-forward", + default: true, + }, + { + id: "use_cpu", + type: ParameterType.checkbox, + label: "Use CPU (not GPU)", + note: "warning: this will be *very* slow", + icon: "fa-microchip", + default: false, + }, + { + id: "auto_pick_gpus", + type: ParameterType.checkbox, + label: "Automatically pick the GPUs (experimental)", + default: false, + }, + { + id: "use_gpus", + type: ParameterType.select_multiple, + label: "GPUs to use (experimental)", + note: "to process in parallel", + default: false, + }, + { + id: "use_full_precision", + type: ParameterType.checkbox, + label: "Use Full Precision", + note: "for GPU-only. warning: this will consume more VRAM", + icon: "fa-crosshairs", + default: false, + }, + { + id: "auto_save_settings", + type: ParameterType.checkbox, + label: "Auto-Save Settings", + note: "restores settings on browser load", + icon: "fa-gear", + default: true, + }, + { + id: "listen_to_network", + type: ParameterType.checkbox, + label: "Make Stable Diffusion available on your network", + note: "Other devices on your network can access this web page", + icon: "fa-network-wired", + default: true, + }, + { + id: "listen_port", + type: ParameterType.custom, + label: "Network port", + note: "Port that this server listens to. The '9000' part in 'http://localhost:9000'", + icon: "fa-anchor", + render: (parameter) => { + return `` + } + }, + { + id: "test_sd2", + type: ParameterType.checkbox, + label: "Test SD 2.0", + note: "Experimental! High memory usage! GPU-only! Not the final version! Please restart the program after changing this.", + icon: "fa-fire", + default: false, + }, + { + id: "use_beta_channel", + type: ParameterType.checkbox, + label: "Beta channel", + note: "Get the latest features immediately (but could be less stable). Please restart the program after changing this.", + icon: "fa-fire", + default: false, + }, ]; function getParameterSettingsEntry(id) { - let parameter = PARAMETERS.filter(p => p.id === id) - if (parameter.length === 0) { - return - } - return parameter[0].settingsEntry + let parameter = PARAMETERS.filter(p => p.id === id) + if (parameter.length === 0) { + return + } + return parameter[0].settingsEntry } function getParameterElement(parameter) { - switch (parameter.type) { - case ParameterType.checkbox: - var is_checked = parameter.default ? " checked" : ""; - return `` - case ParameterType.select: - case ParameterType.select_multiple: - var options = (parameter.options || []).map(option => ``).join("") - var multiple = (parameter.type == ParameterType.select_multiple ? 'multiple' : '') - return `` - case ParameterType.custom: - return parameter.render(parameter) - default: - console.error(`Invalid type for parameter ${parameter.id}`); - return "ERROR: Invalid Type" - } + switch (parameter.type) { + case ParameterType.checkbox: + var is_checked = parameter.default ? " checked" : ""; + return `` + case ParameterType.select: + case ParameterType.select_multiple: + var options = (parameter.options || []).map(option => ``).join("") + var multiple = (parameter.type == ParameterType.select_multiple ? 'multiple' : '') + return `` + case ParameterType.custom: + return parameter.render(parameter) + default: + console.error(`Invalid type for parameter ${parameter.id}`); + return "ERROR: Invalid Type" + } } let parametersTable = document.querySelector("#system-settings .parameters-table") /* fill in the system settings popup table */ function initParameters() { - PARAMETERS.forEach(parameter => { - var element = getParameterElement(parameter) - var note = parameter.note ? `${parameter.note}` : ""; - var icon = parameter.icon ? `` : ""; - var newrow = document.createElement('div') - newrow.innerHTML = ` -
${icon}
-
${note}
-
${element}
` - parametersTable.appendChild(newrow) - parameter.settingsEntry = newrow - }) + PARAMETERS.forEach(parameter => { + var element = getParameterElement(parameter) + var note = parameter.note ? `${parameter.note}` : ""; + var icon = parameter.icon ? `` : ""; + var newrow = document.createElement('div') + newrow.innerHTML = ` +
${icon}
+
${note}
+
${element}
` + parametersTable.appendChild(newrow) + parameter.settingsEntry = newrow + }) } initParameters() @@ -243,14 +243,14 @@ async function getAppConfig() { testSD2Field.checked = config['test_sd2'] } - let testSD2SettingEntry = getParameterSettingsEntry('test_sd2') - testSD2SettingEntry.style.display = (config.update_branch === 'beta' ? '' : 'none') - if (config.net && config.net.listen_to_network === false) { - listenToNetworkField.checked = false - } - if (config.net && config.net.listen_port !== undefined) { - listenPortField.value = config.net.listen_port - } + let testSD2SettingEntry = getParameterSettingsEntry('test_sd2') + testSD2SettingEntry.style.display = (config.update_branch === 'beta' ? '' : 'none') + if (config.net && config.net.listen_to_network === false) { + listenToNetworkField.checked = false + } + if (config.net && config.net.listen_port !== undefined) { + listenPortField.value = config.net.listen_port + } console.log('get config status response', config) } catch (e) { @@ -278,7 +278,6 @@ function getCurrentRenderDeviceSelection() { useCPUField.addEventListener('click', function() { let gpuSettingEntry = getParameterSettingsEntry('use_gpus') let autoPickGPUSettingEntry = getParameterSettingsEntry('auto_pick_gpus') - console.log("hello", this.checked); if (this.checked) { gpuSettingEntry.style.display = 'none' autoPickGPUSettingEntry.style.display = 'none' @@ -375,23 +374,23 @@ async function getDevices() { } saveSettingsBtn.addEventListener('click', function() { - let updateBranch = (useBetaChannelField.checked ? 'beta' : 'main') + let updateBranch = (useBetaChannelField.checked ? 'beta' : 'main') - if (listenPortField.value == '') { - alert('The network port field must not be empty.') - } else if (listenPortField.value<1 || listenPortField.value>65535) { - alert('The network port must be a number from 1 to 65535') - } else { - changeAppConfig({ - 'render_devices': getCurrentRenderDeviceSelection(), - 'update_branch': updateBranch, - 'ui_open_browser_on_start': uiOpenBrowserOnStartField.checked, - 'listen_to_network': listenToNetworkField.checked, - 'listen_port': listenPortField.value, - 'test_sd2': testSD2Field.checked - }) - } + if (listenPortField.value == '') { + alert('The network port field must not be empty.') + } else if (listenPortField.value<1 || listenPortField.value>65535) { + alert('The network port must be a number from 1 to 65535') + } else { + changeAppConfig({ + 'render_devices': getCurrentRenderDeviceSelection(), + 'update_branch': updateBranch, + 'ui_open_browser_on_start': uiOpenBrowserOnStartField.checked, + 'listen_to_network': listenToNetworkField.checked, + 'listen_port': listenPortField.value, + 'test_sd2': testSD2Field.checked + }) + } - saveSettingsBtn.classList.add('active') - asyncDelay(300).then(() => saveSettingsBtn.classList.remove('active')) + saveSettingsBtn.classList.add('active') + asyncDelay(300).then(() => saveSettingsBtn.classList.remove('active')) }) diff --git a/ui/media/js/utils.js b/ui/media/js/utils.js index 6fc3c402..a76f030e 100644 --- a/ui/media/js/utils.js +++ b/ui/media/js/utils.js @@ -1,17 +1,17 @@ // https://gomakethings.com/finding-the-next-and-previous-sibling-elements-that-match-a-selector-with-vanilla-js/ function getNextSibling(elem, selector) { - // Get the next sibling element - var sibling = elem.nextElementSibling + // Get the next sibling element + var sibling = elem.nextElementSibling - // If there's no selector, return the first sibling - if (!selector) return sibling + // If there's no selector, return the first sibling + if (!selector) return sibling - // If the sibling matches our selector, use it - // If not, jump to the next sibling and continue the loop - while (sibling) { - if (sibling.matches(selector)) return sibling - sibling = sibling.nextElementSibling - } + // If the sibling matches our selector, use it + // If not, jump to the next sibling and continue the loop + while (sibling) { + if (sibling.matches(selector)) return sibling + sibling = sibling.nextElementSibling + } } From 159c3edfe3c03c1336155ef3e22e0f83e3183c1f Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 30 Nov 2022 13:33:20 +0530 Subject: [PATCH 28/36] Simplify the logic for toggling modifier cards, no need to loop through the cards, since we already have the card object in hand --- ui/media/js/image-modifiers.js | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/ui/media/js/image-modifiers.js b/ui/media/js/image-modifiers.js index 37671f72..7ba967b4 100644 --- a/ui/media/js/image-modifiers.js +++ b/ui/media/js/image-modifiers.js @@ -90,7 +90,7 @@ function createModifierGroup(modifierGroup, initiallyExpanded) { if (activeTags.map(x => x.name).includes(modifierName)) { // remove modifier from active array activeTags = activeTags.filter(x => x.name != modifierName) - toggleCardState(modifierName, false) + toggleCardState(modifierCard, false) } else { // add modifier to active array activeTags.push({ @@ -99,7 +99,7 @@ function createModifierGroup(modifierGroup, initiallyExpanded) { 'originElement': modifierCard, 'previews': modifierPreviews }) - toggleCardState(modifierName, true) + toggleCardState(modifierCard, true) } refreshTagsList() @@ -217,7 +217,7 @@ function refreshTagsList() { let idx = activeTags.indexOf(tag) if (idx !== -1 && activeTags[idx].originElement !== undefined) { - toggleCardState(activeTags[idx].name, false) + toggleCardState(activeTags[idx].originElement, false) activeTags.splice(idx, 1) refreshTagsList() @@ -230,20 +230,14 @@ function refreshTagsList() { editorModifierTagsList.appendChild(brk) } -function toggleCardState(modifierName, makeActive) { - document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(card => { - const name = card.querySelector('.modifier-card-label').innerText - if (modifierName == name) { - if(makeActive) { - card.classList.add(activeCardClass) - card.querySelector('.modifier-card-image-overlay').innerText = '-' - } - else{ - card.classList.remove(activeCardClass) - card.querySelector('.modifier-card-image-overlay').innerText = '+' - } - } - }) +function toggleCardState(card, makeActive) { + if (makeActive) { + card.classList.add(activeCardClass) + card.querySelector('.modifier-card-image-overlay').innerText = '-' + } else { + card.classList.remove(activeCardClass) + card.querySelector('.modifier-card-image-overlay').innerText = '+' + } } function changePreviewImages(val) { From c64bcd23d313541bfa5c536177828c14ee19ecb8 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 30 Nov 2022 13:38:22 +0530 Subject: [PATCH 29/36] Picklescanner is mandatory --- ui/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/server.py b/ui/server.py index 1313a0a0..fca1aca8 100644 --- a/ui/server.py +++ b/ui/server.py @@ -7,6 +7,7 @@ import traceback import sys import os +import picklescan.scanner import rich SD_DIR = os.getcwd() @@ -234,7 +235,6 @@ async def setAppConfig(req : SetAppConfigRequest): def is_malicious_model(file_path): try: - import picklescan.scanner scan_result = picklescan.scanner.scan_file_path(file_path) if scan_result.issues_count > 0 or scan_result.infected_files > 0: rich.print(":warning: [bold red]Scan %s: %d scanned, %d issue, %d infected.[/bold red]" % (file_path, scan_result.scanned_files, scan_result.issues_count, scan_result.infected_files)) From f0b3bea4e378b625b563fad5f722e7a09e98ac1d Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 30 Nov 2022 13:54:42 +0530 Subject: [PATCH 30/36] Also confirm before the 'Stop All' button acts; Tweak wording of confirm dialog --- ui/media/js/main.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index a971e02b..2a10166f 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -914,7 +914,7 @@ function createTask(task) { task['progressBar'] = taskEntry.querySelector('.progress-bar') task['stopTask'] = taskEntry.querySelector('.stopTask') - task['stopTask'].addEventListener('click', (e) => { shiftOrConfirm(e, "Shall this task be stopped?", async function(e) { + task['stopTask'].addEventListener('click', (e) => { shiftOrConfirm(e, "Are you sure? Should this task be stopped?", async function(e) { if (task['isProcessing']) { task.isProcessing = false task.progressBar.classList.remove("active") @@ -1073,7 +1073,7 @@ async function stopAllTasks() { } } -clearAllPreviewsBtn.addEventListener('click', (e) => { shiftOrConfirm(e, "Remove all results and tasks from the results pane?", async function() { +clearAllPreviewsBtn.addEventListener('click', (e) => { shiftOrConfirm(e, "Are you sure? Remove all results and tasks from the results pane?", async function() { await stopAllTasks() let taskEntries = document.querySelectorAll('.imageTaskContainer') @@ -1085,9 +1085,9 @@ clearAllPreviewsBtn.addEventListener('click', (e) => { shiftOrConfirm(e, "Remove initialText.style.display = 'block' })}) -stopImageBtn.addEventListener('click', async function() { +stopImageBtn.addEventListener('click', (e) => { shiftOrConfirm(e, "Are you sure? Do you want to stop all the tasks?", async function(e) { await stopAllTasks() -}) +})}) widthField.addEventListener('change', onDimensionChange) heightField.addEventListener('change', onDimensionChange) From 029509ebadc70e1b282bfd9ba94442ca309b1d60 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 30 Nov 2022 14:34:24 +0530 Subject: [PATCH 31/36] Unify IP info with devices, into a system_info table --- ui/index.html | 19 ++++++------ ui/media/js/main.js | 30 +------------------ ui/media/js/parameters.js | 63 +++++++++++++++++++++++++-------------- ui/server.py | 16 ++++++---- 4 files changed, 61 insertions(+), 67 deletions(-) diff --git a/ui/index.html b/ui/index.html index bce3579c..d630ff08 100644 --- a/ui/index.html +++ b/ui/index.html @@ -252,14 +252,15 @@

System Info

-
-
-
-

Server Address

-

You can access Stable Diffusion UI from other devices in your network. To do this, enable network access in the settings - above, and open Stable Diffusion UI in a browser using the server's IP. You can use this button to get your server's address.

- -
+
+ + + + + + +
 
+
@@ -358,7 +359,7 @@ async function init() { await getAppConfig() await loadModifiers() await loadUIPlugins() - await getDevices() + await getSystemInfo() setInterval(healthCheck, HEALTH_PING_INTERVAL * 1000) healthCheck() diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 2a10166f..dab71f34 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -196,34 +196,6 @@ function playSound() { }) } } -function setSystemInfo(devices) { - let cpu = devices.all.cpu.name - let allGPUs = Object.keys(devices.all).filter(d => d != 'cpu') - let activeGPUs = Object.keys(devices.active) - - function ID_TO_TEXT(d) { - let info = devices.all[d] - if ("mem_free" in info && "mem_total" in info) { - return `${info.name} (${d}) (${info.mem_free.toFixed(1)}Gb free / ${info.mem_total.toFixed(1)} Gb total)` - } else { - return `${info.name} (${d}) (no memory info)` - } - } - - allGPUs = allGPUs.map(ID_TO_TEXT) - activeGPUs = activeGPUs.map(ID_TO_TEXT) - - let systemInfo = ` - - - - - -
${cpu}
${allGPUs.join('
')}
 
${activeGPUs.join('
')}
` - - let systemInfoEl = document.querySelector('#system-info') - systemInfoEl.innerHTML = systemInfo -} async function healthCheck() { try { @@ -258,7 +230,7 @@ async function healthCheck() { break } if (serverState.devices) { - setSystemInfo(serverState.devices) + setDeviceInfo(serverState.devices) } serverState.time = Date.now() } catch (e) { diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index f8b1aaa4..62203002 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -219,9 +219,6 @@ let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_ac let saveSettingsBtn = document.querySelector('#save-system-settings-btn') -let getServerIPBtn = document.querySelector('#get-server-ip') -let ipInfoContainer = document.querySelector('#ip-info') - async function changeAppConfig(configDelta) { try { @@ -340,14 +337,45 @@ async function getDiskPath() { } } -async function getDevices() { +function setDeviceInfo(devices) { + let cpu = devices.all.cpu.name + let allGPUs = Object.keys(devices.all).filter(d => d != 'cpu') + let activeGPUs = Object.keys(devices.active) + + function ID_TO_TEXT(d) { + let info = devices.all[d] + if ("mem_free" in info && "mem_total" in info) { + return `${info.name} (${d}) (${info.mem_free.toFixed(1)}Gb free / ${info.mem_total.toFixed(1)} Gb total)` + } else { + return `${info.name} (${d}) (no memory info)` + } + } + + allGPUs = allGPUs.map(ID_TO_TEXT) + activeGPUs = activeGPUs.map(ID_TO_TEXT) + + let systemInfoEl = document.querySelector('#system-info') + systemInfoEl.querySelector('#system-info-cpu').innerText = cpu + systemInfoEl.querySelector('#system-info-gpus-all').innerHTML = allGPUs.join('
') + systemInfoEl.querySelector('#system-info-rendering-devices').innerHTML = activeGPUs.join('
') +} + +function setHostInfo(hosts) { + let port = listenPortField.value + hosts = hosts.map(addr => `http://${addr}:${port}/`).map(url => ``) + document.querySelector('#system-info-server-hosts').innerHTML = hosts.join('') +} + +async function getSystemInfo() { try { - let res = await fetch('/get/devices') + let res = await fetch('/get/system_info') if (res.status === 200) { res = await res.json() + let devices = res['devices'] + let hosts = res['hosts'] - let allDeviceIds = Object.keys(res['all']).filter(d => d !== 'cpu') - let activeDeviceIds = Object.keys(res['active']).filter(d => d !== 'cpu') + let allDeviceIds = Object.keys(devices['all']).filter(d => d !== 'cpu') + let activeDeviceIds = Object.keys(devices['active']).filter(d => d !== 'cpu') if (activeDeviceIds.length === 0) { useCPUField.checked = true @@ -365,11 +393,11 @@ async function getDevices() { useCPUField.disabled = true // no compatible GPUs, so make the CPU mandatory } - autoPickGPUsField.checked = (res['config'] === 'auto') + autoPickGPUsField.checked = (devices['config'] === 'auto') useGPUsField.innerHTML = '' allDeviceIds.forEach(device => { - let deviceName = res['all'][device]['name'] + let deviceName = devices['all'][device]['name'] let deviceOption = `` useGPUsField.insertAdjacentHTML('beforeend', deviceOption) }) @@ -380,6 +408,9 @@ async function getDevices() { } else { $('#use_gpus').val(activeDeviceIds) } + + setDeviceInfo(devices) + setHostInfo(hosts) } } catch (e) { console.log('error fetching devices', e) @@ -407,17 +438,3 @@ saveSettingsBtn.addEventListener('click', function() { saveSettingsBtn.classList.add('active') asyncDelay(300).then(() => saveSettingsBtn.classList.remove('active')) }) - -getServerIPBtn.addEventListener('click', async function() { - ipInfoContainer.innerHTML = "Retrieving server addresses..." - let list = "

List of server addresses

If there is more than one result, not all of them might be reachable from other devices.

" - let res = await fetch('/get/ip_config') - let data = await res.json() - let port = listenPortField.value - // Merge hostname (field 0) into list of IPs (field 2) - data[2].push(data[0]) - data[2].forEach((addr) => { let url=`http://${addr}:${port}/`; list+=``;}) - list += "
" - ipInfoContainer.innerHTML = list -}) - diff --git a/ui/server.py b/ui/server.py index af8cf77a..8da5a64f 100644 --- a/ui/server.py +++ b/ui/server.py @@ -307,7 +307,9 @@ def getUIPlugins(): return plugins def getIPConfig(): - return socket.gethostbyname_ex(socket.getfqdn()) + ips = socket.gethostbyname_ex(socket.getfqdn()) + ips[2].append(ips[0]) + return ips[2] @app.get('/get/{key:path}') def read_web_data(key:str=None): @@ -318,17 +320,19 @@ def read_web_data(key:str=None): if config is None: config = APP_CONFIG_DEFAULTS return JSONResponse(config, headers=NOCACHE_HEADERS) - elif key == 'devices': + elif key == 'system_info': config = getConfig() - devices = task_manager.get_devices() - devices['config'] = config.get('render_devices', "auto") - return JSONResponse(devices, headers=NOCACHE_HEADERS) + system_info = { + 'devices': task_manager.get_devices(), + 'hosts': getIPConfig(), + } + system_info['devices']['config'] = config.get('render_devices', "auto") + return JSONResponse(system_info, headers=NOCACHE_HEADERS) elif key == 'models': return JSONResponse(getModels(), headers=NOCACHE_HEADERS) elif key == 'modifiers': return FileResponse(os.path.join(SD_UI_DIR, 'modifiers.json'), headers=NOCACHE_HEADERS) elif key == 'output_dir': return JSONResponse({ 'output_dir': outpath }, headers=NOCACHE_HEADERS) elif key == 'ui_plugins': return JSONResponse(getUIPlugins(), headers=NOCACHE_HEADERS) - elif key == 'ip_config': return JSONResponse(getIPConfig(), headers=NOCACHE_HEADERS) else: raise HTTPException(status_code=404, detail=f'Request for unknown {key}') # HTTP404 Not Found From f7c04bf7a667ba5e3c06b72dd80c71251de5b8b9 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 30 Nov 2022 14:34:42 +0530 Subject: [PATCH 32/36] bump version --- ui/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/index.html b/ui/index.html index d630ff08..2563cb6c 100644 --- a/ui/index.html +++ b/ui/index.html @@ -24,7 +24,7 @@
From 43483334979a7c57b24c492e89b3c638526b848e Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 30 Nov 2022 15:45:30 +0530 Subject: [PATCH 33/36] Don't register listeners for an autosave setting, if they've already been registered --- ui/media/js/auto-save.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/media/js/auto-save.js b/ui/media/js/auto-save.js index 5ea786d5..f6db831d 100644 --- a/ui/media/js/auto-save.js +++ b/ui/media/js/auto-save.js @@ -56,6 +56,9 @@ async function initSettings() { if (!element) { console.error(`Missing settings element ${id}`) } + if (id in SETTINGS) { // don't create it again + return + } SETTINGS[id] = { key: id, element: element, From 54b5f7590592a2ac9f7d84071b89712596bf6975 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 30 Nov 2022 15:47:24 +0530 Subject: [PATCH 34/36] Rename auto-scroll to reflect its purpose --- ui/plugins/ui/Autoscroll.plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/plugins/ui/Autoscroll.plugin.js b/ui/plugins/ui/Autoscroll.plugin.js index aec9523d..0b15bb1f 100644 --- a/ui/plugins/ui/Autoscroll.plugin.js +++ b/ui/plugins/ui/Autoscroll.plugin.js @@ -11,7 +11,7 @@ const autoScrollControl = document.createElement('div'); autoScrollControl.innerHTML = ` - ` + ` autoScrollControl.className = "auto-scroll" clearAllPreviewsBtn.parentNode.insertBefore(autoScrollControl, clearAllPreviewsBtn.nextSibling) prettifyInputs(document); From af53b57047c7253f564b57c7df16e0073f844bf4 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 30 Nov 2022 15:49:47 +0530 Subject: [PATCH 35/36] Changelog --- CHANGES.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c581eda6..66283d6f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,8 +23,10 @@ - Show the network addresses of the server in the systems setting dialog ### Detailed changelog -* 2.4.17 - 30 Nov 2022 - Show the network addresses of the server in the systems setting dialog -* 2.4.17 - 30 Nov 2022 - Confirm before stopping or clearing all the tasks +* 2.4.17 - 30 Nov 2022 - Scroll to generated image. Thanks @patriceac +* 2.4.17 - 30 Nov 2022 - Show the network addresses of the server in the systems setting dialog. Thanks @JeLuf +* 2.4.17 - 30 Nov 2022 - Fix a bug where GFPGAN wouldn't work properly when multiple GPUs tried to run it at the same time. Thanks @madrang +* 2.4.17 - 30 Nov 2022 - Confirm before stopping or clearing all the tasks. Thanks @JeLuf * 2.4.16 - 29 Nov 2022 - Bug fixes for SD 2.0 - remove the need for patching, default to SD 1.4 model if trying to load an SD2 model in SD1.4. * 2.4.15 - 25 Nov 2022 - Experimental support for SD 2.0. Uses lots of memory, not optimized, probably GPU-only. * 2.4.14 - 22 Nov 2022 - Change the backend to a custom fork of Stable Diffusion From 54f7e6fcb8a94a0f7f4d79a14e1e00b2d819ac5a Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 30 Nov 2022 16:05:06 +0530 Subject: [PATCH 36/36] SD2 fix - register buffer on the correct device --- scripts/on_sd_start.bat | 2 +- scripts/on_sd_start.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/on_sd_start.bat b/scripts/on_sd_start.bat index e088ed57..3d5218d5 100644 --- a/scripts/on_sd_start.bat +++ b/scripts/on_sd_start.bat @@ -44,7 +44,7 @@ if NOT DEFINED test_sd2 set test_sd2=N @call git -c advice.detachedHead=false checkout 7f32368ed1030a6e710537047bacd908adea183a ) if "%test_sd2%" == "Y" ( - @call git -c advice.detachedHead=false checkout 8878d67decd3deb3c98472c1e39d2a51dc5950f9 + @call git -c advice.detachedHead=false checkout 5d647c5459f4cd790672512222bc41903c01bb71 ) @cd .. diff --git a/scripts/on_sd_start.sh b/scripts/on_sd_start.sh index 199a9ae8..ea1aaa93 100755 --- a/scripts/on_sd_start.sh +++ b/scripts/on_sd_start.sh @@ -38,7 +38,7 @@ if [ -e "scripts/install_status.txt" ] && [ `grep -c sd_git_cloned scripts/insta if [ "$test_sd2" == "N" ]; then git -c advice.detachedHead=false checkout 7f32368ed1030a6e710537047bacd908adea183a elif [ "$test_sd2" == "Y" ]; then - git -c advice.detachedHead=false checkout 8878d67decd3deb3c98472c1e39d2a51dc5950f9 + git -c advice.detachedHead=false checkout 5d647c5459f4cd790672512222bc41903c01bb71 fi cd ..