From ac91a15aa98ffa678c0645889a7aa9bfb0dc3fd5 Mon Sep 17 00:00:00 2001 From: "M.Hosoi" Date: Thu, 23 Mar 2023 09:20:10 +0900 Subject: [PATCH 01/17] fix: can't get file size if gnubin/stat is already installed in homebrew --- scripts/functions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/functions.sh b/scripts/functions.sh index 5b1be7f4..495e9950 100644 --- a/scripts/functions.sh +++ b/scripts/functions.sh @@ -31,7 +31,7 @@ EOF filesize() { case "$(uname -s)" in Linux*) stat -c "%s" $1;; - Darwin*) stat -f "%z" $1;; + Darwin*) /usr/bin/stat -f "%z" $1;; *) echo "Unknown OS: $OS_NAME! This script runs only on Linux or Mac" && exit esac } From 20ad0d7f8cdcc12532290277ea6f7525fa46f5b4 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 23 Mar 2023 09:48:40 +0530 Subject: [PATCH 02/17] Merge pull request #1048 from JeLuF/base36 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔥Hotfix: Avoid name clashes for autosave files --- ui/easydiffusion/utils/save_utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ui/easydiffusion/utils/save_utils.py b/ui/easydiffusion/utils/save_utils.py index b4a85538..6012bc44 100644 --- a/ui/easydiffusion/utils/save_utils.py +++ b/ui/easydiffusion/utils/save_utils.py @@ -1,11 +1,11 @@ import os import time -import base64 import re from easydiffusion.types import TaskData, GenerateImageRequest from sdkit.utils import save_images, save_dicts +from numpy import base_repr filename_regex = re.compile("[^a-zA-Z0-9._-]") @@ -121,8 +121,7 @@ def make_filename_callback(req: GenerateImageRequest, suffix=None, now=None): now = time.time() def make_filename(i): - img_id = base64.b64encode(int(now + i).to_bytes(8, "big")).decode() # Generate unique ID based on time. - img_id = img_id.translate({43: None, 47: None, 61: None})[-8:] # Remove + / = and keep last 8 chars. + img_id = base_repr(int(now * 10000), 36)[-7:] + base_repr(int(i),36) # Base 36 conversion, 0-9, A-Z prompt_flattened = filename_regex.sub("_", req.prompt)[:50] name = f"{prompt_flattened}_{img_id}" From 7e53eb658c8105fa86fc285305556d994148e390 Mon Sep 17 00:00:00 2001 From: Olivia Godone-Maresca Date: Wed, 29 Mar 2023 20:56:24 -0400 Subject: [PATCH 03/17] Allow loading/saving app.config from plugins and support custom folder/filename formats from app.config --- ui/easydiffusion/server.py | 12 +- ui/easydiffusion/utils/save_utils.py | 148 +++++++++++++++++++++-- ui/media/js/parameters.js | 172 ++++++++++++++++++++++----- ui/media/js/utils.js | 26 +++- 4 files changed, 315 insertions(+), 43 deletions(-) diff --git a/ui/easydiffusion/server.py b/ui/easydiffusion/server.py index e27f9c5b..9642d735 100644 --- a/ui/easydiffusion/server.py +++ b/ui/easydiffusion/server.py @@ -10,7 +10,7 @@ from typing import List, Union from fastapi import FastAPI, HTTPException from fastapi.staticfiles import StaticFiles from starlette.responses import FileResponse, JSONResponse, StreamingResponse -from pydantic import BaseModel +from pydantic import BaseModel, Extra from easydiffusion import app, model_manager, task_manager from easydiffusion.types import TaskData, GenerateImageRequest, MergeRequest @@ -44,7 +44,7 @@ class NoCacheStaticFiles(StaticFiles): return super().is_not_modified(response_headers, request_headers) -class SetAppConfigRequest(BaseModel): +class SetAppConfigRequest(BaseModel, extra=Extra.allow): update_branch: str = None render_devices: Union[List[str], List[int], str, int] = None model_vae: str = None @@ -136,6 +136,14 @@ def set_app_config_internal(req: SetAppConfigRequest): config["test_diffusers"] = req.test_diffusers + for property, property_value in req.dict().items(): + log.info(f"set_app_config_internal {property} === {property_value}") + if property_value is not None and property in req.__fields__: + log.info(f"set_app_config_internal {property} IS DEFINED PROPERTY") + if property_value is not None and property not in req.__fields__: + log.info(f"set_app_config_internal {property} IS ADDITIONAL PROPERTY") + config[property] = property_value + try: app.setConfig(config) diff --git a/ui/easydiffusion/utils/save_utils.py b/ui/easydiffusion/utils/save_utils.py index 950f04b0..f936ad1b 100644 --- a/ui/easydiffusion/utils/save_utils.py +++ b/ui/easydiffusion/utils/save_utils.py @@ -1,13 +1,18 @@ import os import time +import base64 import re +from easydiffusion import app from easydiffusion.types import TaskData, GenerateImageRequest +from functools import reduce +from datetime import datetime from sdkit.utils import save_images, save_dicts from numpy import base_repr filename_regex = re.compile("[^a-zA-Z0-9._-]") +img_number_regex = re.compile("([0-9]{5,})") # keep in sync with `ui/media/js/dnd.js` TASK_TEXT_MAPPING = { @@ -31,12 +36,86 @@ TASK_TEXT_MAPPING = { # "lora_alpha": "LoRA Strength", } +time_placeholders = { + "$yyyy": "%Y", + "$MM": "%m", + "$dd": "%d", + "$HH": "%H", + "$mm": "%M", + "$ss": "%S", +} + +other_placeholders = { + "$id": lambda req, task_data: filename_regex.sub("_", task_data.session_id), + "$p": lambda req, task_data: filename_regex.sub("_", req.prompt)[:50], + "$s": lambda req, task_data: str(req.seed), +} + +class ImageNumber: + _factory = None + _evaluated = False + + def __init__(self, factory): + self._factory = factory + self._evaluated = None + def __call__(self) -> int: + if self._evaluated is None: + self._evaluated = self._factory() + return self._evaluated + +def format_placeholders(format: str, req: GenerateImageRequest, task_data: TaskData, now = None): + if now is None: + now = time.time() + + for placeholder, time_format in time_placeholders.items(): + if placeholder in format: + format = format.replace(placeholder, datetime.fromtimestamp(now).strftime(time_format)) + for placeholder, replace_func in other_placeholders.items(): + if placeholder in format: + format = format.replace(placeholder, replace_func(req, task_data)) + + return format + +def format_folder_name(format: str, req: GenerateImageRequest, task_data: TaskData): + format = format_placeholders(format, req, task_data) + return filename_regex.sub("_", format) + +def format_file_name( + format: str, + req: GenerateImageRequest, + task_data: TaskData, + now: float, + batch_file_number: int, + folder_img_number: ImageNumber, +): + format = format_placeholders(format, req, task_data, now) + + if "$n" in format: + format = format.replace("$n", f"{folder_img_number():05}") + + if "$tsb64" in format: + img_id = base_repr(int(now * 10000), 36)[-7:] + base_repr(int(batch_file_number), 36) # Base 36 conversion, 0-9, A-Z + format = format.replace("$tsb64", img_id) + + if "$ts" in format: + format = format.replace("$ts", str(int(now * 1000) + batch_file_number)) + + return filename_regex.sub("_", format) def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageRequest, task_data: TaskData): now = time.time() - save_dir_path = os.path.join(task_data.save_to_disk_path, filename_regex.sub("_", task_data.session_id)) + app_config = app.getConfig() + folder_format = app_config.get("folder_format", "$id") + save_dir_path = os.path.join(task_data.save_to_disk_path, format_folder_name(folder_format, req, task_data)) metadata_entries = get_metadata_entries_for_request(req, task_data) - make_filename = make_filename_callback(req, now=now) + file_number = calculate_img_number(save_dir_path, task_data) + make_filename = make_filename_callback( + app_config.get("filename_format", "$p_$tsb64"), + req, + task_data, + file_number, + now=now, + ) if task_data.show_only_filtered_image or filtered_images is images: save_images( @@ -58,7 +137,7 @@ def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageR file_format=task_data.output_format, ) else: - make_filter_filename = make_filename_callback(req, now=now, suffix="filtered") + make_filter_filename = make_filename_callback(req, task_data, file_number, now=now, suffix="filtered") save_images( images, @@ -105,9 +184,6 @@ def get_metadata_entries_for_request(req: GenerateImageRequest, task_data: TaskD if task_data.use_lora_model is None: if "lora_alpha" in metadata: del metadata["lora_alpha"] - - from easydiffusion import app - app_config = app.getConfig() if not app_config.get("test_diffusers", False) and "use_lora_model" in metadata: del metadata["use_lora_model"] @@ -133,16 +209,66 @@ def get_printable_request(req: GenerateImageRequest): return metadata -def make_filename_callback(req: GenerateImageRequest, suffix=None, now=None): +def make_filename_callback( + filename_format: str, + req: GenerateImageRequest, + task_data: TaskData, + folder_img_number: int, + suffix=None, + now=None, +): if now is None: now = time.time() def make_filename(i): - img_id = base_repr(int(now * 10000), 36)[-7:] + base_repr(int(i),36) # Base 36 conversion, 0-9, A-Z - - prompt_flattened = filename_regex.sub("_", req.prompt)[:50] - name = f"{prompt_flattened}_{img_id}" + name = format_file_name(filename_format, req, task_data, now, i, folder_img_number) name = name if suffix is None else f"{name}_{suffix}" + return name return make_filename + +def _calculate_img_number(save_dir_path: str, task_data: TaskData): + def get_highest_img_number(accumulator: int, file: os.DirEntry) -> int: + if not file.is_file: + return accumulator + + if len(list(filter(lambda e: file.name.endswith(e), app.IMAGE_EXTENSIONS))) == 0: + return accumulator + + get_highest_img_number.number_of_images = get_highest_img_number.number_of_images + 1 + + number_match = img_number_regex.match(file.name) + if not number_match: + return accumulator + + file_number = number_match.group().lstrip('0') + + # Handle 00000 + return int(file_number) if file_number else 0 + + get_highest_img_number.number_of_images = 0 + + highest_file_number = -1 + + if os.path.isdir(save_dir_path): + existing_files = list(os.scandir(save_dir_path)) + highest_file_number = reduce(get_highest_img_number, existing_files, -1) + + calculated_img_number = max(highest_file_number, get_highest_img_number.number_of_images - 1) + + if task_data.session_id in _calculate_img_number.session_img_numbers: + calculated_img_number = max( + _calculate_img_number.session_img_numbers[task_data.session_id], + calculated_img_number, + ) + + calculated_img_number = calculated_img_number + 1 + + _calculate_img_number.session_img_numbers[task_data.session_id] = calculated_img_number + return calculated_img_number + +_calculate_img_number.session_img_numbers = {} + +def calculate_img_number(save_dir_path: str, task_data: TaskData): + return ImageNumber(lambda: _calculate_img_number(save_dir_path, task_data)) diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index baa55469..0fc89359 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -15,10 +15,13 @@ * JSDoc style * @typedef {object} Parameter * @property {string} id - * @property {ParameterType} type - * @property {string} label - * @property {?string} note + * @property {keyof ParameterType} type + * @property {string | (parameter: Parameter) => (HTMLElement | string)} label + * @property {string | (parameter: Parameter) => (HTMLElement | string) | undefined} note + * @property {(parameter: Parameter) => (HTMLElement | string) | undefined} render + * @property {string | undefined} icon * @property {number|boolean|string} default + * @property {boolean?} saveInAppConfig */ @@ -118,6 +121,7 @@ var PARAMETERS = [ note: "starts the default browser on startup", icon: "fa-window-restore", default: true, + saveInAppConfig: true, }, { id: "vram_usage_level", @@ -179,6 +183,7 @@ var PARAMETERS = [ note: "Other devices on your network can access this web page", icon: "fa-network-wired", default: true, + saveInAppConfig: true, }, { id: "listen_port", @@ -188,7 +193,8 @@ var PARAMETERS = [ icon: "fa-anchor", render: (parameter) => { return `` - } + }, + saveInAppConfig: true, }, { id: "use_beta_channel", @@ -205,6 +211,7 @@ var PARAMETERS = [ note: "Experimental! Can have bugs! Use upcoming features (like LoRA) in our new engine. Please press Save, then restart the program after changing this.", icon: "fa-bolt", default: false, + saveInAppConfig: true, }, ]; @@ -228,6 +235,10 @@ function sliderUpdate(event) { } } +/** + * @param {Parameter} parameter + * @returns {string | HTMLElement} + */ function getParameterElement(parameter) { switch (parameter.type) { case ParameterType.checkbox: @@ -243,29 +254,74 @@ function getParameterElement(parameter) { case ParameterType.custom: return parameter.render(parameter) default: - console.error(`Invalid type for parameter ${parameter.id}`); + console.error(`Invalid type ${parameter.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}
` +/** + * fill in the system settings popup table + * @param {Array | undefined} parameters + * */ +function initParameters(parameters) { + parameters.forEach(parameter => { + const element = getParameterElement(parameter) + const elementWrapper = createElement('div') + if (element instanceof Node) { + elementWrapper.appendChild(element) + } else { + elementWrapper.innerHTML = element + } + + const note = typeof parameter.note === 'function' ? parameter.note(parameter) : parameter.note + const noteElements = [] + if (note) { + const noteElement = createElement('small') + if (note instanceof Node) { + noteElement.appendChild(note) + } else { + noteElement.innerHTML = note || '' + } + noteElements.push(noteElement) + } + + const icon = parameter.icon ? [createElement('i', undefined, ['fa', parameter.icon])] : [] + + const label = typeof parameter.label === 'function' ? parameter.label(parameter) : parameter.label + const labelElement = createElement('label', { for: parameter.id }) + if (label instanceof Node) { + labelElement.appendChild(label) + } else { + labelElement.innerHTML = label + } + + const newrow = createElement( + 'div', + { 'data-setting-id': parameter.id, 'data-save-in-app-config': parameter.saveInAppConfig }, + undefined, + [ + createElement('div', undefined, undefined, icon), + createElement('div', undefined, undefined, [labelElement, ...noteElements]), + elementWrapper, + ] + ) parametersTable.appendChild(newrow) parameter.settingsEntry = newrow }) } -initParameters() +initParameters(PARAMETERS) + +// listen to parameters from plugins +PARAMETERS.addEventListener('push', (...items) => { + initParameters(items) + + if (items.find(item => item.saveInAppConfig)) { + console.log('Reloading app config for new parameters', items.map(p => p.id)) + getAppConfig() + } +}) let vramUsageLevelField = document.querySelector('#vram_usage_level') let useCPUField = document.querySelector('#use_cpu') @@ -324,9 +380,44 @@ async function getAppConfig() { document.querySelector("#lora_model_container").style.display = (testDiffusers.checked ? '' : 'none') } + Array.from(parametersTable.children).forEach(parameterRow => { + if (parameterRow.dataset.settingId in config && parameterRow.dataset.saveInAppConfig === 'true') { + const configValue = config[parameterRow.dataset.settingId] + const parameterElement = document.getElementById(parameterRow.dataset.settingId) || + parameterRow.querySelector('input') || parameterRow.querySelector('select') + + switch (parameterElement?.tagName) { + case 'INPUT': + if (parameterElement.type === 'checkbox') { + parameterElement.checked = configValue + } else { + parameterElement.value = configValue + } + parameterElement.dispatchEvent(new Event('change')) + break + case 'SELECT': + if (Array.isArray(configValue)) { + Array.from(parameterElement.options).forEach(option => { + if (configValue.includes(option.value || option.text)) { + option.selected = true + } + }) + } else { + parameterElement.value = configValue + } + parameterElement.dispatchEvent(new Event('change')) + break + } + } + }) + console.log('get config status response', config) + + return config } catch (e) { console.log('get config status error', e) + + return {} } } @@ -486,16 +577,43 @@ saveSettingsBtn.addEventListener('click', function() { alert('The network port must be a number from 1 to 65535') return } - let updateBranch = (useBetaChannelField.checked ? 'beta' : 'main') - changeAppConfig({ + const updateBranch = (useBetaChannelField.checked ? 'beta' : 'main') + + const updateAppConfigRequest = { 'render_devices': getCurrentRenderDeviceSelection(), 'update_branch': updateBranch, - 'ui_open_browser_on_start': uiOpenBrowserOnStartField.checked, - 'listen_to_network': listenToNetworkField.checked, - 'listen_port': listenPortField.value, - 'test_diffusers': testDiffusers.checked - }) - saveSettingsBtn.classList.add('active') - asyncDelay(300).then(() => saveSettingsBtn.classList.remove('active')) -}) + } + Array.from(parametersTable.children).forEach(parameterRow => { + if (parameterRow.dataset.saveInAppConfig === 'true') { + const parameterElement = document.getElementById(parameterRow.dataset.settingId) || + parameterRow.querySelector('input') || parameterRow.querySelector('select') + + switch (parameterElement?.tagName) { + case 'INPUT': + if (parameterElement.type === 'checkbox') { + updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.checked + } else { + updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value + } + break + case 'SELECT': + if (parameterElement.multiple) { + updateAppConfigRequest[parameterRow.dataset.settingId] = Array.from(parameterElement.options) + .filter(option => option.selected) + .map(option => option.value || option.text) + } else { + updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value + } + break + default: + console.error(`Setting parameter ${parameterRow.dataset.settingId} couldn't be saved to app.config - element #${parameter.id} is a <${parameterElement?.tagName} /> instead of a or a - +
diff --git a/ui/media/js/dnd.js b/ui/media/js/dnd.js index 98c5e26a..88a8b0c7 100644 --- a/ui/media/js/dnd.js +++ b/ui/media/js/dnd.js @@ -245,6 +245,14 @@ const TASK_MAPPING = { readUI: () => loraModelField.value, parse: (val) => val }, + lora_alpha: { name: 'LoRA Strength', + setUI: (lora_alpha) => { + loraAlphaField.value = lora_alpha + updateLoraAlphaSlider() + }, + readUI: () => parseFloat(loraAlphaField.value), + parse: (val) => parseFloat(val) + }, use_hypernetwork_model: { name: 'Hypernetwork model', setUI: (use_hypernetwork_model) => { const oldVal = hypernetworkModelField.value From bc711414a8e628e6cd2d39bc67a26bb6a21f3c47 Mon Sep 17 00:00:00 2001 From: Olivia Godone-Maresca Date: Sat, 1 Apr 2023 21:26:32 -0400 Subject: [PATCH 07/17] Increase the random seed range --- 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 e9f3f9d8..61838f25 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -1099,7 +1099,7 @@ function createTask(task) { function getCurrentUserRequest() { const numOutputsTotal = parseInt(numOutputsTotalField.value) const numOutputsParallel = parseInt(numOutputsParallelField.value) - const seed = (randomSeedField.checked ? Math.floor(Math.random() * 10000000) : parseInt(seedField.value)) + const seed = (randomSeedField.checked ? Math.floor(Math.random() * (2**32 - 1)) : parseInt(seedField.value)) const newTask = { batchesDone: 0, From 6aa048e3ad825ae0ea25ffea55cbbf20423fdd9c Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Sun, 2 Apr 2023 02:04:03 -0700 Subject: [PATCH 08/17] Reset the LoRA dropdown if not present in the task --- ui/media/js/dnd.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/media/js/dnd.js b/ui/media/js/dnd.js index 98c5e26a..6e78609c 100644 --- a/ui/media/js/dnd.js +++ b/ui/media/js/dnd.js @@ -340,7 +340,12 @@ function restoreTaskToUI(task, fieldsToSkip) { hypernetworkModelField.value = "" hypernetworkModelField.dispatchEvent(new Event("change")) } - + + if (!('use_lora_model' in task.reqBody)) { + loraModelField.value = "None" + loraModelField.dispatchEvent(new Event("change")) + } + // restore the original prompt if provided (e.g. use settings), fallback to prompt as needed (e.g. copy/paste or d&d) promptField.value = task.reqBody.original_prompt if (!('original_prompt' in task.reqBody)) { From 69380e352767182a2a2238c382b3064a9693f51c Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 3 Apr 2023 15:57:52 +0530 Subject: [PATCH 09/17] sdkit 1.0.63 - bug fix - samplers wouldn't get created if an inpainting was the first to load --- scripts/on_sd_start.bat | 4 ++-- scripts/on_sd_start.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/on_sd_start.bat b/scripts/on_sd_start.bat index d04eb9bb..1b9ec588 100644 --- a/scripts/on_sd_start.bat +++ b/scripts/on_sd_start.bat @@ -95,7 +95,7 @@ if "%ERRORLEVEL%" EQU "0" ( set PYTHONNOUSERSITE=1 set PYTHONPATH=%INSTALL_ENV_DIR%\lib\site-packages - call python -m pip install --upgrade sdkit==1.0.62 -q || ( + call python -m pip install --upgrade sdkit==1.0.63 -q || ( echo "Error updating sdkit" ) ) @@ -106,7 +106,7 @@ if "%ERRORLEVEL%" EQU "0" ( set PYTHONNOUSERSITE=1 set PYTHONPATH=%INSTALL_ENV_DIR%\lib\site-packages - call python -m pip install sdkit==1.0.62 || ( + call python -m pip install sdkit==1.0.63 || ( echo "Error installing sdkit. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!" pause exit /b diff --git a/scripts/on_sd_start.sh b/scripts/on_sd_start.sh index 80a61b8e..cf74349c 100755 --- a/scripts/on_sd_start.sh +++ b/scripts/on_sd_start.sh @@ -103,7 +103,7 @@ if python ../scripts/check_modules.py sdkit sdkit.models ldm transformers numpy export PYTHONNOUSERSITE=1 export PYTHONPATH="$INSTALL_ENV_DIR/lib/python3.8/site-packages" - python -m pip install --upgrade sdkit==1.0.62 -q + python -m pip install --upgrade sdkit==1.0.63 -q fi else echo "Installing sdkit: https://pypi.org/project/sdkit/" @@ -111,7 +111,7 @@ else export PYTHONNOUSERSITE=1 export PYTHONPATH="$INSTALL_ENV_DIR/lib/python3.8/site-packages" - if python -m pip install sdkit==1.0.62 ; then + if python -m pip install sdkit==1.0.63 ; then echo "Installed." else fail "sdkit install failed" From 88d415d3f9ab764cc38e3c38cea712bbfeec89e9 Mon Sep 17 00:00:00 2001 From: Olivia Godone-Maresca Date: Mon, 3 Apr 2023 19:35:37 -0400 Subject: [PATCH 10/17] Add back/forward buttons to switch between images in tasks --- ui/media/js/image-modal.js | 119 ++++++++++++++++++++++++++++++++----- ui/media/js/main.js | 25 +++++++- 2 files changed, 129 insertions(+), 15 deletions(-) diff --git a/ui/media/js/image-modal.js b/ui/media/js/image-modal.js index 498c5f24..367c754c 100644 --- a/ui/media/js/image-modal.js +++ b/ui/media/js/image-modal.js @@ -1,6 +1,28 @@ "use strict" +/** + * @typedef {object} ImageModalRequest + * @property {string} src + * @property {ImageModalRequest | () => ImageModalRequest | undefined} previous + * @property {ImageModalRequest | () => ImageModalRequest | undefined} next + */ + +/** + * @type {(() => (string | ImageModalRequest) | string | ImageModalRequest) => {}} + */ const imageModal = (function() { + const backElem = createElement( + 'i', + undefined, + ['fa-solid', 'fa-arrow-left', 'tertiaryButton'], + ) + + const forwardElem = createElement( + 'i', + undefined, + ['fa-solid', 'fa-arrow-right', 'tertiaryButton'], + ) + const zoomElem = createElement( 'i', undefined, @@ -13,7 +35,7 @@ const imageModal = (function() { ['fa-solid', 'fa-xmark', 'tertiaryButton'], ) - const menuBarElem = createElement('div', undefined, 'menu-bar', [zoomElem, closeElem]) + const menuBarElem = createElement('div', undefined, 'menu-bar', [backElem, forwardElem, zoomElem, closeElem]) const imageContainer = createElement('div', undefined, 'image-wrapper') @@ -63,15 +85,87 @@ const imageModal = (function() { () => setZoomLevel(imageContainer.querySelector('img')?.classList?.contains('natural-zoom')), ) - const close = () => { + const state = { + previous: undefined, + next: undefined, + } + + const clear = () => { imageContainer.innerHTML = '' + + Object.keys(state).forEach(key => delete state[key]) + } + + const close = () => { + clear() modalElem.classList.remove('active') document.body.style.overflow = 'initial' } - window.addEventListener('keydown', (e) => { - if (e.key === 'Escape' && modalElem.classList.contains('active')) { + /** + * @param {() => (string | ImageModalRequest) | string | ImageModalRequest} optionsFactory + */ + function init(optionsFactory) { + if (!optionsFactory) { close() + return + } + + clear() + + const options = typeof optionsFactory === 'function' ? optionsFactory() : optionsFactory + const src = typeof options === 'string' ? options : options.src + + const imgElem = createElement('img', { src }, 'natural-zoom') + imageContainer.appendChild(imgElem) + modalElem.classList.add('active') + document.body.style.overflow = 'hidden' + setZoomLevel(false) + + if (typeof options === 'object' && options.previous) { + state.previous = options.previous + backElem.style.display = 'unset' + } else { + backElem.style.display = 'none' + } + + if (typeof options === 'object' && options.next) { + state.next = options.next + forwardElem.style.display = 'unset' + } else { + forwardElem.style.display = 'none' + } + } + + const back = () => { + if (state.previous) { + init(state.previous) + } else { + backElem.style.display = 'none' + } + } + + const forward = () => { + if (state.next) { + init(state.next) + } else { + forwardElem.style.display = 'none' + } + } + + window.addEventListener('keydown', (e) => { + if (modalElem.classList.contains('active')) { + switch (e.key) { + case 'Escape': + close() + break + case 'ArrowLeft': + back() + break + case 'ArrowRight': + forward() + break + } } }) window.addEventListener('click', (e) => { @@ -86,15 +180,12 @@ const imageModal = (function() { } }) - return (optionsFactory) => { - const options = typeof optionsFactory === 'function' ? optionsFactory() : optionsFactory - const src = typeof options === 'string' ? options : options.src + backElem.addEventListener('click', back) - // TODO center it if < window size - const imgElem = createElement('img', { src }, 'natural-zoom') - imageContainer.appendChild(imgElem) - modalElem.classList.add('active') - document.body.style.overflow = 'hidden' - setZoomLevel(false) - } + forwardElem.addEventListener('click', forward) + + /** + * @param {() => (string | ImageModalRequest) | string | ImageModalRequest} optionsFactory + */ + return (optionsFactory) => init(optionsFactory) })() diff --git a/ui/media/js/main.js b/ui/media/js/main.js index e9f3f9d8..f26730af 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -398,7 +398,30 @@ function showImages(reqBody, res, outputContainer, livePreview) { if ('seed' in result && !imageElem.hasAttribute('data-seed')) { const imageExpandBtn = imageItemElem.querySelector('.imgExpandBtn') imageExpandBtn.addEventListener('click', function() { - imageModal(imageElem.src) + function previousImage(img) { + const allImages = Array.from(outputContainer.parentNode.querySelectorAll('.imgItem img')) + const index = allImages.indexOf(img) + return allImages.slice(0, index).reverse()[0] + } + + function nextImage(img) { + const allImages = Array.from(outputContainer.parentNode.querySelectorAll('.imgItem img')) + const index = allImages.indexOf(img) + return allImages.slice(index + 1)[0] + } + + function imageModalParameter(img) { + const previousImg = previousImage(img) + const nextImg = nextImage(img) + + return { + src: img.src, + previous: previousImg ? () => imageModalParameter(previousImg) : undefined, + next: nextImg ? () => imageModalParameter(nextImg) : undefined, + } + } + + imageModal(imageModalParameter(imageElem)) }) const req = Object.assign({}, reqBody, { From 73d947c4a6c1aaaf19beec41cfad38324a612e75 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 4 Apr 2023 16:10:16 +0530 Subject: [PATCH 11/17] Don't show LoRA options in the main branch --- 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 cc12f32b..022b9a7f 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -321,7 +321,10 @@ async function getAppConfig() { if (config.net && config.net.listen_port !== undefined) { listenPortField.value = config.net.listen_port } - if (config.test_diffusers !== undefined) { + if (config.test_diffusers === undefined) { + document.querySelector("#lora_model_container").style.display = 'none' + document.querySelector("#lora_alpha_container").style.display = 'none' + } else { testDiffusers.checked = config.test_diffusers && config.update_branch === 'beta' document.querySelector("#lora_model_container").style.display = (testDiffusers.checked ? '' : 'none') document.querySelector("#lora_alpha_container").style.display = (testDiffusers.checked && loraModelField.value !== "" ? '' : 'none') From 84737eb2716ec07d586f6ed234d99e625afe5540 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 4 Apr 2023 16:14:07 +0530 Subject: [PATCH 12/17] Disable LoRA in the main branch --- ui/media/js/parameters.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index 022b9a7f..455844da 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -321,11 +321,11 @@ async function getAppConfig() { if (config.net && config.net.listen_port !== undefined) { listenPortField.value = config.net.listen_port } - if (config.test_diffusers === undefined) { + if (config.test_diffusers === undefined || config.update_branch === 'main') { document.querySelector("#lora_model_container").style.display = 'none' document.querySelector("#lora_alpha_container").style.display = 'none' } else { - testDiffusers.checked = config.test_diffusers && config.update_branch === 'beta' + testDiffusers.checked = config.test_diffusers && config.update_branch !== 'main' document.querySelector("#lora_model_container").style.display = (testDiffusers.checked ? '' : 'none') document.querySelector("#lora_alpha_container").style.display = (testDiffusers.checked && loraModelField.value !== "" ? '' : 'none') } From f3a0ab24c1144c9524ed0aa9380358b765cb6b5c Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 4 Apr 2023 16:26:16 +0530 Subject: [PATCH 13/17] Tag the Test Diffusers changelog entries to beta-only --- CHANGES.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 201a0249..074506f1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,16 +21,16 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog -* 2.5.30 - 1 Apr 2023 - Slider to control the strength of the LoRA model. +* 2.5.30 - 1 Apr 2023 - (beta-only) Slider to control the strength of the LoRA model. * 2.5.30 - 28 Mar 2023 - Refactor task entry config to use a generating method. Added ability for plugins to easily add to this. Removed confusing sentence from `contributing.md` * 2.5.30 - 28 Mar 2023 - Allow the user to undo the deletion of tasks or images, instead of showing a pop-up each time. The new `Undo` button will be present at the top of the UI. Thanks @JeLuf. * 2.5.30 - 28 Mar 2023 - Support saving lossless WEBP images. Thanks @ogmaresca. * 2.5.30 - 28 Mar 2023 - Lots of bug fixes for the UI (Read LoRA flag in metadata files, new prompt weight format with scrollwheel, fix overflow with lots of tabs, clear button in image editor, shorter filenames in download). Thanks @patriceac, @JeLuf and @ogmaresca. -* 2.5.29 - 27 Mar 2023 - Fix a bug where some non-square images would fail while inpainting with a `The size of tensor a must match size of tensor b` error. -* 2.5.29 - 27 Mar 2023 - Fix the `incorrect number of channels` error, when given a PNG image with an alpha channel in `Test Diffusers`. -* 2.5.29 - 27 Mar 2023 - Fix broken inpainting in `Test Diffusers` (beta). -* 2.5.28 - 24 Mar 2023 - Support for weighted prompts and long prompt lengths (not limited to 77 tokens). This change requires enabling the `Test Diffusers` setting in beta (in the Settings tab), and restarting the program. -* 2.5.27 - 21 Mar 2023 - LoRA support, accessible by enabling the `Test Diffusers` setting (in the Settings tab in the UI). This change switches the internal engine to diffusers (if the `Test Diffusers` setting is enabled). If the `Test Diffusers` flag is disabled, it'll have no impact for the user. +* 2.5.29 - 27 Mar 2023 - (beta-only) Fix a bug where some non-square images would fail while inpainting with a `The size of tensor a must match size of tensor b` error. +* 2.5.29 - 27 Mar 2023 - (beta-only) Fix the `incorrect number of channels` error, when given a PNG image with an alpha channel in `Test Diffusers`. +* 2.5.29 - 27 Mar 2023 - (beta-only) Fix broken inpainting in `Test Diffusers`. +* 2.5.28 - 24 Mar 2023 - (beta-only) Support for weighted prompts and long prompt lengths (not limited to 77 tokens). This change requires enabling the `Test Diffusers` setting in beta (in the Settings tab), and restarting the program. +* 2.5.27 - 21 Mar 2023 - (beta-only) LoRA support, accessible by enabling the `Test Diffusers` setting (in the Settings tab in the UI). This change switches the internal engine to diffusers (if the `Test Diffusers` setting is enabled). If the `Test Diffusers` flag is disabled, it'll have no impact for the user. * 2.5.26 - 15 Mar 2023 - Allow styling the buttons displayed on an image. Update the API to allow multiple buttons and text labels in a single row. Thanks @ogmaresca. * 2.5.26 - 15 Mar 2023 - View images in full-screen, by either clicking on the image, or clicking the "Full screen" icon next to the Seed number on the image. Thanks @ogmaresca for the internal API. * 2.5.25 - 14 Mar 2023 - Button to download all the images, and all the metadata as a zip file. This is available at the top of the UI, as well as on each image. Thanks @JeLuf. From 899125cc41f9b454567875d14c47e6504ad6619e Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 5 Apr 2023 08:07:53 +0530 Subject: [PATCH 14/17] sdkit 1.0.64 - bug fix - metadata embedding would ignore the jpeg quality --- scripts/on_sd_start.bat | 4 ++-- scripts/on_sd_start.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/on_sd_start.bat b/scripts/on_sd_start.bat index 1b9ec588..c9b3235f 100644 --- a/scripts/on_sd_start.bat +++ b/scripts/on_sd_start.bat @@ -95,7 +95,7 @@ if "%ERRORLEVEL%" EQU "0" ( set PYTHONNOUSERSITE=1 set PYTHONPATH=%INSTALL_ENV_DIR%\lib\site-packages - call python -m pip install --upgrade sdkit==1.0.63 -q || ( + call python -m pip install --upgrade sdkit==1.0.64 -q || ( echo "Error updating sdkit" ) ) @@ -106,7 +106,7 @@ if "%ERRORLEVEL%" EQU "0" ( set PYTHONNOUSERSITE=1 set PYTHONPATH=%INSTALL_ENV_DIR%\lib\site-packages - call python -m pip install sdkit==1.0.63 || ( + call python -m pip install sdkit==1.0.64 || ( echo "Error installing sdkit. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!" pause exit /b diff --git a/scripts/on_sd_start.sh b/scripts/on_sd_start.sh index cf74349c..78bcf0ee 100755 --- a/scripts/on_sd_start.sh +++ b/scripts/on_sd_start.sh @@ -103,7 +103,7 @@ if python ../scripts/check_modules.py sdkit sdkit.models ldm transformers numpy export PYTHONNOUSERSITE=1 export PYTHONPATH="$INSTALL_ENV_DIR/lib/python3.8/site-packages" - python -m pip install --upgrade sdkit==1.0.63 -q + python -m pip install --upgrade sdkit==1.0.64 -q fi else echo "Installing sdkit: https://pypi.org/project/sdkit/" @@ -111,7 +111,7 @@ else export PYTHONNOUSERSITE=1 export PYTHONPATH="$INSTALL_ENV_DIR/lib/python3.8/site-packages" - if python -m pip install sdkit==1.0.63 ; then + if python -m pip install sdkit==1.0.64 ; then echo "Installed." else fail "sdkit install failed" From 687da5b64a7ab0b06ae48fa2e0ea863c2f9244d4 Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Thu, 6 Apr 2023 03:12:48 -0700 Subject: [PATCH 15/17] Undo UI cleanup (#1106) * Moving to InvokeAI attention weighting syntax * Fix restoration of disabled image tags Fix the restoration inactive image tags. * Undo feature UX cleanup Just show the undo button when there's no task for a more consistent UI. * cleanup code * Revert "cleanup code" This reverts commit 03199c5a4f9bf7dd798645a280735ff0f9f6a17a. * Update image-modifiers.js * Update image-modifiers.js --- ui/index.html | 12 ++++++------ ui/media/css/main.css | 4 ++++ ui/media/js/main.js | 6 ++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ui/index.html b/ui/index.html index fed2b3b9..702a4c1c 100644 --- a/ui/index.html +++ b/ui/index.html @@ -293,6 +293,12 @@
+
+ Type a prompt and press the "Make Image" button.

You can set an "Initial Image" if you want to guide the AI.

+ You can also add modifiers like "Realistic", "Pencil Sketch", "ArtStation" etc by browsing through the "Image Modifiers" section + and selecting the desired modifiers.

+ Click "Image Settings" for additional settings like seed, image size, number of images to generate etc.

Enjoy! :) +
@@ -326,12 +332,6 @@
-
- Type a prompt and press the "Make Image" button.

You can set an "Initial Image" if you want to guide the AI.

- You can also add modifiers like "Realistic", "Pencil Sketch", "ArtStation" etc by browsing through the "Image Modifiers" section - and selecting the desired modifiers.

- Click "Image Settings" for additional settings like seed, image size, number of images to generate etc.

Enjoy! :) -
diff --git a/ui/media/css/main.css b/ui/media/css/main.css index e6a95cda..08dea664 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -238,6 +238,10 @@ code { #stopImage:hover { background: rgb(177, 27, 0); } +#undo { + float: right; + margin-left: 5px; +} div#render-buttons { gap: 3px; diff --git a/ui/media/js/main.js b/ui/media/js/main.js index b22da732..0720b988 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -1310,13 +1310,15 @@ async function stopAllTasks() { function updateInitialText() { if (document.querySelector('.imageTaskContainer') === null) { - if (undoBuffer.length == 0) { - previewTools.classList.add('displayNone') + if (undoBuffer.length > 0) { + initialText.prepend(undoButton) } + previewTools.classList.add('displayNone') initialText.classList.remove('displayNone') } else { initialText.classList.add('displayNone') previewTools.classList.remove('displayNone') + document.querySelector('div.display-settings').prepend(undoButton) } } From 0d570b3fae85eb6c664a3456bc0515ed178ced77 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 6 Apr 2023 16:21:07 +0530 Subject: [PATCH 16/17] version --- ui/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/index.html b/ui/index.html index 702a4c1c..bb79f3be 100644 --- a/ui/index.html +++ b/ui/index.html @@ -30,7 +30,7 @@

Easy Diffusion - v2.5.30 + v2.5.31

From c115a9aa3ddbb2c98bd5ef8397f150a0ec59a08e Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 6 Apr 2023 16:39:20 +0530 Subject: [PATCH 17/17] changelog --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 074506f1..bde70f1e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,6 +21,9 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog +* 2.5.31 - 6 Apr 2023 - Allow seeds upto `4,294,967,295`. Thanks @ogmaresca. +* 2.5.31 - 6 Apr 2023 - Buttons to show the previous/next image in the image popup. Thanks @ogmaresca. +* 2.5.30 - 5 Apr 2023 - Fix a bug where the JPEG image quality wasn't being respected when embedding the metadata into it. Thanks @JeLuf. * 2.5.30 - 1 Apr 2023 - (beta-only) Slider to control the strength of the LoRA model. * 2.5.30 - 28 Mar 2023 - Refactor task entry config to use a generating method. Added ability for plugins to easily add to this. Removed confusing sentence from `contributing.md` * 2.5.30 - 28 Mar 2023 - Allow the user to undo the deletion of tasks or images, instead of showing a pop-up each time. The new `Undo` button will be present at the top of the UI. Thanks @JeLuf.